Star 历史趋势
数据来源: GitHub API · 生成自 Stargazers.cn
README.md

How Does It Work?

Code is bound to states (graph nodes), not transitions (graph edges in FSMs). When a state "Foo" activates, it checks the FooEnter(e) method first, then runs FooState(e). When it deactivates, checks the FooExit(e) method, then runs FooEnd(e). FooState(e) binds context and collects data, then forks background processes bound to that instance of the "Foo" state. Code executed during a transition is dynamically composed of deactivating and activating states. Transitions between states are not defined, and many states can be active simultaneously. It should be used to solve complexity in time.


Go Reference website pkg lines of code tools lines of code tests pkg tests machine coverage pkg coverage tools coverage GitHub release last commit Reddit

asyncmachine-go

Transition Lifecycle

[!NOTE] State machines communicate through states.

asyncmachine-go is a distributed workflow engine and a declarative execution model, which implements AOP and actor model through a clock-based state machine. It features atomic transitions, relations, transparent RPC, TUI debugger, telemetry, REPL, selective distribution, and diagrams (plus support for WASM and AI).

As a control flow library, it decides about running of predefined bits of code (transition handlers) - their order and which ones to run, according to currently active states (flags). Thanks to a novel state machine, the number of handlers can be minimized while maximizing scenario coverage. It's lightweight, fault-tolerant by design, has rule-based mutations, and can target virtually any step-in-time, in any workflow. It's a low-level tool with acceptable performance.

asyncmachine-go takes care of context, select, and panic, while allowing for graph-structured concurrency with goroutine cancelation. The history log and relations have vector formats. It aims to create autonomous workflows with organic control flow and stateful APIs.

[!NOTE] git clone https://github.com/pancsta/asyncmachine-go.git

Each state represents

  • binary flag
  • node with relations
  • AOP aspect
  • logical clock
  • subscription topic
  • multiple methods
  • metric
  • trace
  • lock
  • breakpoint

Besides the main use-case of workflows, it can be used for stateful applications of any size - daemons, UIs, stateful RPC UIs, configs, live sessions, bots, firewalls, synchronization consensus, games, smart graphs, microservice orchestration, robots, contracts, streams, DI containers, message broking, test scenarios, simulators, as well as "real-time" systems which rely on instant cancelation.

[!NOTE] Flow is state, and state is flow, in a graph.

Samples

Minimal - an untyped definition of 2 states and 1 relation, then 1 mutation and a check.

import am "github.com/pancsta/asyncmachine-go/pkg/machine"
// ...
mach := am.New(nil, am.Schema{
    "Foo": {Require: am.S{"Bar"}},
    "Bar": {},
}, nil)
mach.Add1("Foo", nil)
mach.Is1("Foo") // false

Complicated - wait on a multi state (event) and the Ready state with a 1s timeout, then mutate with typed args, on top of a state context.

// state ctx is an expiration ctx
ctx := client.Mach.NewStateCtx(ssC.WorkerReady)
// clock-based subscription
whenPayload := client.Mach.WhenTicks(ssC.WorkerPayload, 1, ctx)
// state mutation
client.RpcWorker.NetMach.Add1(ssW.WorkRequested, Pass(&A{
    Input: 2}))
// WaitFor* wraps select statements
err := amhelp.WaitForAll(ctx, time.Second,
    // post-mutation subscription
    mach.When1(ss.BasicStatesDef.Ready, nil),
    // pre-mutation subscription
    whenPayload)
// check cancelation
if ctx.Err() != nil {
    return // state ctx expired
}
// check error
if err != nil {
    // error state mutation
    client.Mach.AddErr(err, nil)
    return // no err required
}
// client/WorkerPayload and mach/Ready activated

[!NOTE] Clock-based navigation in time.

Handlers - Aspect Oriented transition handlers.

// can Foo activate?
func (h *Handlers) FooEnter(e *am.Event) bool {
    return true
}
// with Foo active, can Bar activate?
func (h *Handlers) FooBar(e *am.Event) bool {
    return true
}
// Foo activates
func (h *Handlers) FooState(e *am.Event) {
    h.foo = NewConn()
}
// Foo de-activates
func (h *Handlers) FooEnd(e *am.Event) {
    h.foo.Close()
}

Schemas - relational schemas (aRPC server).

var ServerSchema = am.Schema{
    ssS.ClientConnected: {Require: S{RpcReady}},
    ssS.ErrDelivery:     {Require: S{Exception}},
    ssS.ErrHandlerTimeout: {
        Add:     S{Exception},
        Multi:   true,
        Require: S{Exception},
    },
    ssS.ErrNetwork: {
        Remove:  S{ClientConnected},
        Require: S{Exception},
    },
    ssS.ErrNetworkTimeout: {Require: S{Exception}},
    ssS.ErrOnClient:       {Require: S{Exception}},
    ssS.ErrProviding:      {Require: S{Exception}},
    ssS.ErrRpc:            {Require: S{Exception}},
    ssS.ErrSendPayload:    {Require: S{Exception}},
    ssS.Exception:         {Multi: true},
    ssS.HandshakeDone: {
        Remove:  S{Handshaking, ssS.HandshakeDone, ssS.Exception},
        Require: S{Start, ssS.ClientConnected},
    },
    ssS.Handshaking: {
        Remove:  S{Handshaking, ssS.HandshakeDone},
        Require: S{Start},
    },
    ssS.Healthcheck: {Multi: true},
    ssS.Heartbeat:   {},
    ssS.MetricSync:  {Multi: true},
    ssS.Ready: {
        Auto:    true,
        Require: S{HandshakeDone, ssS.RpcReady},
    },
    ssS.RpcAccepting: {
        Remove:  S{RpcStarting, ssS.RpcAccepting, ssS.RpcReady},
        Require: S{Start},
    },
    ssS.RpcReady: {
        Remove:  S{RpcStarting, ssS.RpcAccepting, ssS.RpcReady},
        Require: S{Start},
    },
    ssS.RpcStarting: {
        Remove:  S{RpcStarting, ssS.RpcAccepting, ssS.RpcReady},
        Require: S{Start},
    },
    ssS.SendPayload:     {Multi: true},
    ssS.Start:           {Add: S{RpcStarting}},
    ssS.WebSocketTunnel: {},
}

All examples and benchmarks can be found in /examples.

Getting Started

Packages

This monorepo offers the following importable packages, especially:

  • 🦾 /pkg/machine State machine, dependency free, semver compatible.
  • /pkg/states Reusable state schemas, handlers, and piping.
  • /pkg/helpers Useful functions when working with async state machines.
  • /pkg/telemetry Telemetry exporters for dbg, metrics, traces, and logs.

Other packages:

  • /pkg/rpc Remote state machines, with the same API as local ones.
  • /pkg/history History tracking and traversal in mem, KV, and SQL.
  • /pkg/integrations Integrations for JSON, NATS, MCP, and yaegi.
  • /pkg/graph Directional multigraph of connected state machines.
  • /pkg/node Distributed worker pools with supervisors.
  • /pkg/pubsub Decentralized PubSub based on libp2p gossipsub.

Devtools

[!NOTE] Inspecting cause-and-effect in distributed systems.

Apps

asyncmachine-go synchronizes state for the following projects:

Documentation

Goals

  • scale up, not down
  • defaults work by default
  • everything can be traced and debugged
  • automation is evolution
  • state != data

Community

OpenTelemetry traces in Jaeger

[!NOTE] Hundreds of clones.

Status

Under development, status depends on each package. The bottom layers seem prod grade, the top ones are alpha or testing.

[!NOTE] Managing distributed concurrency.

Development

  • good first issues
  • before
    • ./scripts/dep-taskfile.sh
    • task install-deps
  • after
    • task test
    • task format
    • task lint
    • task precommit

Roadmap

  • more tooling, diagrams, integrations
  • bug fixes and optimizations
  • network security with ACLs
  • ROADMAP.md

[!NOTE] Step by step.

FAQ

How does asyncmachine work?

Code is bound to states (graph nodes), not transitions (graph edges in FSMs). When a state "Foo" activates, it checks the FooEnter(e) method first, then runs FooState(e). When it deactivates, checks the FooExit(e) method, then runs FooEnd(e). FooState(e) binds context and collects data, then forks background processes bound to that instance of the "Foo" state. Code executed during a transition is dynamically composed of deactivating and activating states. Transitions between states are not defined, and many states can be active simultaneously. It should be used to solve complexity in time.

What is a "state" in asyncmachine?

State is a binary ID as in status / switch / flag, eg "process RUNNING" or "car BROKEN".

What does "clock-based" mean?

Each state has a counter of activations & deactivations, and all state counters create "machine time". These are logical clocks, and the queue is also (partially) counted.

What's the difference between states and events?

The same event happening many times will cause only 1 state activation, until the state becomes inactive.

The complete FAQ is available at FAQ.md.

Changes

TUI Debugger

[!NOTE] Don't lose your sync.

关于 About

state machine which runs code
actor-modelaopconcurrencyconsensuscontrol-flowdeclarativedistributedexecutiongolanggraphlibp2pmodelnegotiationorchestratorp2ppubsubrpcstate-machinesyncworkflows

语言 Languages

Go99.1%
HTML0.7%
Go Template0.1%
Shell0.1%
Dockerfile0.0%

提交活跃度 Commit Activity

代码提交热力图
过去 52 周的开发活跃度
220
Total Commits
峰值: 27次/周
Less
More

核心贡献者 Contributors