├── .github
├── BUMP
└── workflows
│ ├── releaser.yml
│ ├── go-basic.yml
│ ├── linter.yml
│ ├── go-tools.yml
│ ├── go-node.yml
│ ├── go-rpc.yml
│ ├── go-pubsub.yml
│ ├── go-states.yml
│ ├── go-helpers.yml
│ ├── go-history.yml
│ ├── go-machine.yml
│ └── go-telemetry.yml
├── config
├── env
│ ├── debug-simple.env
│ ├── debug-pubsub.env
│ ├── debug-node.env
│ ├── debug-telemetry-dbg.env
│ ├── tests-remote.env
│ ├── debug-tests.env
│ └── debug-rpc.env
├── .mdlrc
├── cli
│ └── debug-am-dbg.txt
├── .mdl_style.rb
├── dashboards
│ ├── dash-repl.kdl
│ ├── dash-repl-web.kdl
│ ├── dash-wide-narrow.kdl
│ ├── dash-wide-double-narrow.kdl
│ ├── dash-full.kdl
│ └── dash-wide-narrow-repl.kdl
└── terminalizer.yml
├── examples
├── benchmark_state_source
│ ├── bench
│ │ ├── .gitignore
│ │ ├── Dockerfile
│ │ ├── Caddyfile-root
│ │ ├── Caddyfile-root-2
│ │ ├── Caddyfile-root-2-4
│ │ ├── Caddyfile-root-2-6
│ │ └── go-wrk.sh
│ ├── Dockerfile
│ └── Taskfile.yml
├── arpc
│ ├── Procfile
│ ├── Taskfile.yml
│ ├── states
│ │ ├── states_utils.go
│ │ └── ss_example.go
│ ├── client
│ │ └── main.go
│ └── server
│ │ └── main.go
├── .editorconfig
├── tree_state_source
│ ├── Procfile
│ ├── config.env
│ ├── states
│ │ └── states_utils.go
│ ├── gen-grafana.sh
│ ├── gen_states
│ │ └── gen_states.go
│ ├── Taskfile.yml
│ └── README.md
├── mach_template
│ ├── gen-states.sh
│ ├── example.env
│ └── states
│ │ ├── states_utils.go
│ │ └── ss_mach_template.go
├── relations_playground
│ └── relations_playground_test.go
├── repl
│ ├── states
│ │ ├── states_utils.go
│ │ └── ss_example.go
│ └── main.go
├── benchmark_grpc
│ ├── proto
│ │ └── worker.proto
│ ├── states
│ │ ├── states_utils.go
│ │ └── ss_worker.go
│ ├── worker_proto
│ │ └── worker.proto
│ ├── client_local_test.go
│ ├── worker_states
│ │ └── worker_states.go
│ ├── worker.go
│ ├── server_grpc.go
│ └── server_arpc.go
├── asynq_fileprocessing
│ ├── client
│ │ └── client.go
│ └── worker
│ │ └── worker.go
├── nfa
│ └── states
│ │ ├── states_utils.go
│ │ └── ss_nfa.go
├── temporal_fileprocessing
│ ├── fileprocessing_test.go
│ └── states
│ │ └── ss_fileprocessing.go
├── temporal_expense
│ └── states
│ │ └── ss_expense.go
├── path_watcher
│ └── states
│ │ └── ss_watcher.go
├── pipes
│ └── example_pipes.go
├── fan_out_in
│ └── example_fan_out_in.go
├── subscriptions
│ └── example_subscriptions.go
├── raw_strings
│ └── raw_strings.go
└── fsm
│ └── fsm_test.go
├── pkg
├── history
│ ├── hist_example_test.go
│ ├── test
│ │ ├── history_test.go
│ │ └── test_hist.go
│ └── bbolt
│ │ └── bbolt_test.go
├── states
│ ├── states_utils.go
│ ├── global
│ │ └── states_utils.go
│ ├── ss_basic.go
│ └── ss_disposed.go
├── rpc
│ ├── utils_test.go
│ └── states
│ │ ├── ss_rpc_consumer.go
│ │ ├── ss_rpc_worker.go
│ │ ├── ss_mux.go
│ │ ├── ss_rpc_shared.go
│ │ ├── ss_rpc_server.go
│ │ └── ss_rpc_client.go
├── helpers
│ └── help_test.go
├── telemetry
│ ├── otel_test.go
│ └── telemetry.go
├── node
│ ├── states
│ │ ├── ss_bootstrap.go
│ │ └── ss_node_client.go
│ └── test
│ │ └── worker
│ │ └── node_test_worker.go
├── x
│ ├── history
│ │ └── frostdb
│ │ │ └── frostdb_test.go
│ └── helpers
│ │ └── x_help_test.go
└── integrations
│ └── README.md
├── .gitmodules
├── tools
├── debugger
│ ├── testdata
│ │ └── am-dbg-sim.gob.br
│ ├── utils_test.go
│ └── types
│ │ └── dbg_types.go
├── visualizer
│ ├── visualizer_test.go
│ └── states
│ │ └── ss_visualizer.go
├── cmd
│ ├── am-relay
│ │ ├── cmd_relay.go
│ │ └── README.md
│ └── arpc
│ │ └── cmd_arpc.go
├── relay
│ ├── types
│ │ └── relay_cli.go
│ └── states
│ │ └── ss_relay.go
└── generator
│ └── states
│ └── ss_generator.go
├── scripts
├── test_loop
│ ├── test-loop-replay.sh
│ ├── test-loop-record.sh
│ └── test-loop.sh
├── dep-taskfile.sh
├── compact_number
│ └── compact_number.go
├── gen_jsonschema
│ └── gen_jsonschema.go
├── gen_website
│ └── sitemap
│ │ └── sitemap.go
└── extract_mermaid
│ └── extract_mermaid.go
├── deploy
├── web-metrics
│ ├── prometheus.yaml
│ ├── otel-collector-config.yaml
│ ├── loki.yaml
│ └── docker-compose.yml
└── web-am-dbg
│ └── Dockerfile
├── .editorconfig
├── .github_changelog_generator
├── ROADMAP.md
├── .dockerignore
├── .golangci.yml
├── docs
├── jsonschema
│ ├── msg_kind_req.json
│ ├── msg_kind_resp.json
│ ├── mutation_resp.json
│ ├── mutation_req.json
│ ├── waiting_req.json
│ ├── waiting_resp.json
│ ├── getter_req.json
│ └── getter_resp.json
└── diagrams-examples.md
├── LICENSE
├── internal
├── testing
│ ├── states
│ │ └── ss_rel.go
│ └── cmd
│ │ └── am-dbg-worker
│ │ └── main_dbg_worker.go
└── utils
│ └── utils.go
├── .gitignore
├── codecov.yaml
└── .goreleaser.yml
/.github/BUMP:
--------------------------------------------------------------------------------
1 | 31
--------------------------------------------------------------------------------
/config/env/debug-simple.env:
--------------------------------------------------------------------------------
1 | AM_DEBUG=1
2 |
--------------------------------------------------------------------------------
/examples/benchmark_state_source/bench/.gitignore:
--------------------------------------------------------------------------------
1 | Caddyfile
--------------------------------------------------------------------------------
/config/.mdlrc:
--------------------------------------------------------------------------------
1 | style "#{File.dirname(__FILE__)}/.mdl_style.rb"
--------------------------------------------------------------------------------
/config/env/debug-pubsub.env:
--------------------------------------------------------------------------------
1 | AM_PUBSUB_LOG=1
2 | AM_PUBSUB_DBG=1
3 |
--------------------------------------------------------------------------------
/config/cli/debug-am-dbg.txt:
--------------------------------------------------------------------------------
1 | --am-dbg-addr=localhost:9913
2 | --log-level=2
--------------------------------------------------------------------------------
/examples/arpc/Procfile:
--------------------------------------------------------------------------------
1 | client: task client
2 | server: task server
3 |
--------------------------------------------------------------------------------
/config/env/debug-node.env:
--------------------------------------------------------------------------------
1 | AM_NODE_LOG_SUPERVISOR=1
2 | AM_NODE_LOG_CLIENT=1
3 |
--------------------------------------------------------------------------------
/examples/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.go]
2 | indent_size = 4
3 | max_line_length = 120
4 |
--------------------------------------------------------------------------------
/pkg/history/hist_example_test.go:
--------------------------------------------------------------------------------
1 | package history
2 |
3 | // TODO ExampleTrack
4 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "assets"]
2 | path = assets
3 | url = https://github.com/pancsta/assets.git
4 |
--------------------------------------------------------------------------------
/config/env/debug-telemetry-dbg.env:
--------------------------------------------------------------------------------
1 | AM_DBG_ADDR=localhost:6831
2 | AM_LOG=3
3 | AM_LOG_FULL=1
4 | AM_HEALTHCHECK=1
5 |
--------------------------------------------------------------------------------
/config/env/tests-remote.env:
--------------------------------------------------------------------------------
1 | AM_DBG_WORKER_RPC_ADDR=localhost:53480
2 | AM_DBG_WORKER_TELEMETRY_ADDR=localhost:53470
3 |
--------------------------------------------------------------------------------
/config/env/debug-tests.env:
--------------------------------------------------------------------------------
1 | AM_DEBUG=1
2 | AM_DBG_ADDR=1
3 | AM_LOG=3
4 | AM_LOG_FULL=1
5 | AM_TEST_DEBUG=1
6 | AM_DETECT_EVAL=1
--------------------------------------------------------------------------------
/tools/debugger/testdata/am-dbg-sim.gob.br:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pancsta/asyncmachine-go/HEAD/tools/debugger/testdata/am-dbg-sim.gob.br
--------------------------------------------------------------------------------
/config/env/debug-rpc.env:
--------------------------------------------------------------------------------
1 | AM_RPC_LOG_SERVER=1
2 | AM_RPC_LOG_CLIENT=1
3 | AM_RPC_LOG_MUX=1
4 | AM_RPC_DBG=1
5 | AM_REPL_ADDR=1
6 | AM_REPL_DIR=tmp
7 |
--------------------------------------------------------------------------------
/examples/benchmark_state_source/bench/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.20-alpine
2 |
3 | WORKDIR /app
4 | RUN go install github.com/tsliwowicz/go-wrk@latest
5 |
6 | CMD ["/app/go-wrk.sh"]
7 |
--------------------------------------------------------------------------------
/scripts/test_loop/test-loop-replay.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #set -x
4 | LOCATION=~/.local/share/rr/rpc.test-2/
5 |
6 | dlv replay $LOCATION --headless --listen=:2345 --log --api-version=2
7 |
--------------------------------------------------------------------------------
/examples/benchmark_state_source/bench/Caddyfile-root:
--------------------------------------------------------------------------------
1 | :80 {
2 | #
3 | # root only
4 | #
5 | reverse_proxy /* root:18700
6 |
7 | log {
8 | output discard
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/tree_state_source/Procfile:
--------------------------------------------------------------------------------
1 | root: task root
2 |
3 | rep-1: task rep-1
4 | rep-2: task rep-2
5 |
6 | rep-1-1: task rep-1-1
7 | rep-1-2: task rep-1-2
8 | rep-2-1: task rep-2-1
9 | rep-2-2: task rep-2-2
10 |
--------------------------------------------------------------------------------
/deploy/web-metrics/prometheus.yaml:
--------------------------------------------------------------------------------
1 | global:
2 | scrape_interval: 1s
3 |
4 | scrape_configs:
5 | - job_name: 'pushgateway'
6 | honor_labels: true
7 | scrape_interval: 5s
8 | static_configs:
9 | - targets: [ 'pushgateway:9091' ]
10 |
--------------------------------------------------------------------------------
/examples/benchmark_state_source/bench/Caddyfile-root-2:
--------------------------------------------------------------------------------
1 | :80 {
2 | #
3 | # 2 direct replicants
4 | #
5 | reverse_proxy /* rep-1:18700 rep-2:18700 {
6 | lb_policy least_conn
7 | }
8 |
9 | log {
10 | output discard
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/mach_template/gen-states.sh:
--------------------------------------------------------------------------------
1 | cd states
2 | am-gen states-file \
3 | --states 'ErrExample:require(Exception),Foo:require(Bar),Bar,Baz:multi,BazDone:multi,Channel' \
4 | --inherit basic,connected,disposed \
5 | --name machTemplate \
6 | --groups Group1,Group2 \
7 | --force
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.go]
4 | indent_style = tab
5 | indent_size = 2
6 | max_line_length = 80
7 |
8 | [*.md]
9 | indent_style = space
10 | indent_size = 4
11 | max_line_length = 120
12 |
13 | [*.{yml|yaml}]
14 | indent_style = space
15 | indent_size = 4
16 | max_line_length = 120
17 |
--------------------------------------------------------------------------------
/.github_changelog_generator:
--------------------------------------------------------------------------------
1 | # https://github.com/github-changelog-generator/github-changelog-generator/wiki/Advanced-change-log-generation-examples
2 | issues=false
3 | compare-link=false
4 | usernames-as-github-logins=true
5 | pr-label=
6 | since-tag=v0.1.0
7 | user=pancsta
8 | project=asyncmachine-go
9 | unreleased=true
--------------------------------------------------------------------------------
/examples/benchmark_state_source/bench/Caddyfile-root-2-4:
--------------------------------------------------------------------------------
1 | :80 {
2 | #
3 | # 4 indirect replicants
4 | #
5 | reverse_proxy /* rep-1-1:18700 rep-1-2:18700 rep-2-1:18700 rep-2-2:18700 {
6 | lb_policy least_conn
7 | }
8 |
9 | log {
10 | output discard
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/relations_playground/relations_playground_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | // simple check for panics
8 | func TestAll(t *testing.T) {
9 | FooBar()
10 | FileProcessed()
11 | DryWaterWet()
12 | RemoveByAdd()
13 | AddOptionalRemoveMandatory()
14 | Mutex()
15 | Quiz()
16 | }
17 |
--------------------------------------------------------------------------------
/scripts/dep-taskfile.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | # check if installed
4 | if command -v task >/dev/null; then echo "OK" && exit; fi
5 |
6 | echo "Visit https://taskfile.dev/installation/ for more info"
7 | echo "----- ----- -----"
8 |
9 | sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
10 |
--------------------------------------------------------------------------------
/examples/benchmark_state_source/bench/Caddyfile-root-2-6:
--------------------------------------------------------------------------------
1 | :80 {
2 | #
3 | # 6 indirect replicants
4 | #
5 | reverse_proxy /* rep-1-1:18700 rep-1-2:18700 rep-1-3:18700 rep-2-1:18700 rep-2-2:18700 rep-2-3:18700 {
6 | lb_policy least_conn
7 | }
8 |
9 | log {
10 | output discard
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/benchmark_state_source/bench/go-wrk.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | echo "----- WARMUP"
4 | echo "----- WARMUP"
5 | echo "----- WARMUP"
6 | go-wrk -c 512 -d 1 "http://caddy"
7 | sleep 3
8 |
9 | echo "----- BENCHMARK"
10 | echo "----- BENCHMARK"
11 | echo "----- BENCHMARK"
12 | echo $(date)
13 | go-wrk -c 512 -d 10 "http://caddy"
14 |
--------------------------------------------------------------------------------
/ROADMAP.md:
--------------------------------------------------------------------------------
1 | # Roadmap
2 |
3 | - history layer - SQL, KV, ...
4 | - am-relay tool for proxying, conversions, duck-taping
5 | - RPC handlers for distributed state-machines
6 | - scheduler via the handler loop
7 | - go1.22 traces
8 | - inference
9 | - optimizations
10 | - tutorials
11 |
12 | See also [issues](https://github.com/pancsta/asyncmachine-go/issues).
13 |
--------------------------------------------------------------------------------
/examples/arpc/Taskfile.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | dotenv: [ '.env' ]
4 |
5 | tasks:
6 | client:
7 | cmd: go run ./client
8 |
9 | server:
10 | cmd: go run ./server
11 |
12 | start:
13 | desc: Start the example
14 | cmd: goreman start
15 |
16 | deps:
17 | desc: Install dependencies
18 | cmd: go install github.com/mattn/goreman@latest
19 |
--------------------------------------------------------------------------------
/examples/tree_state_source/config.env:
--------------------------------------------------------------------------------
1 | # copy to .env and paste the token
2 |
3 | GRAFANA_URL=http://localhost:3000
4 | GRAFANA_TOKEN=
5 |
6 | # metrics
7 |
8 | PUSH_GATEWAY_URL=http://localhost:9091
9 | LOKI_ADDR=localhost:3100
10 | AM_LOG=3
11 |
12 | #AM_DEBUG=1
13 | #AM_DBG_ADDR=:6831
14 | #AM_HEALTHCHECK=1
15 | #
16 | #AM_RPC_LOG_SERVER=1
17 | #AM_RPC_LOG_CLIENT=1
18 | #AM_RPC_LOG_MUX=1
19 | #
20 | #AM_NODE_LOG_SUPERVISOR=1
21 | #AM_NODE_LOG_CLIENT=1
22 | #AM_NODE_LOG_WORKER=1
23 | #
24 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | assets*
2 |
3 | # Ignore the .git directory
4 | .git
5 |
6 | # Ignore the node_modules directory
7 | node_modules
8 |
9 | # Ignore temporary files
10 | *.log
11 | *.tmp
12 |
13 | # Ignore build output directories
14 | dist
15 | build
16 | tmp
17 |
18 | # Ignore Dockerfile and docker-compose.yml
19 | Dockerfile
20 | docker-compose.yml
21 |
22 | # Ignore Go build artifacts
23 | *.exe
24 | *.exe~
25 | *.dll
26 | *.so
27 | *.dylib
28 | *.test
29 | *.out
30 |
31 | # Ignore IDE/editor specific files
32 | .vscode
33 | .idea
34 | *.swp
35 | *~
36 |
--------------------------------------------------------------------------------
/config/.mdl_style.rb:
--------------------------------------------------------------------------------
1 | all
2 | rule 'MD013', :line_length => 120
3 | # list style
4 | rule 'MD029', :style => :ordered
5 | # dollar sign
6 | exclude_rule 'MD014'
7 | # HTML blocks
8 | exclude_rule 'MD033'
9 | # block quotes
10 | exclude_rule 'MD028'
11 | # first line H1
12 | exclude_rule 'MD002'
13 | # first line H1
14 | exclude_rule 'MD041'
15 | # ???
16 | exclude_rule 'MD007'
17 | # header question mark
18 | exclude_rule 'MD026'
19 | # Header levels should only increment by one level at a time
20 | # not compatible with hashtags
21 | exclude_rule 'MD001'
22 | # Spaces inside emphasis markers (buggy)
23 | exclude_rule 'MD037'
24 |
--------------------------------------------------------------------------------
/examples/arpc/states/states_utils.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import am "github.com/pancsta/asyncmachine-go/pkg/machine"
4 |
5 | // S is a type alias for a list of state names.
6 | type S = am.S
7 |
8 | // SAdd is a func alias for merging lists of states.
9 | var SAdd = am.SAdd
10 |
11 | // StateAdd is a func alias for adding to an existing state definition.
12 | var StateAdd = am.StateAdd
13 |
14 | // StateSet is a func alias for replacing parts of an existing state
15 | // definition.
16 | var StateSet = am.StateSet
17 |
18 | // SchemaMerge is a func alias for extending an existing state structure.
19 | var SchemaMerge = am.SchemaMerge
20 |
--------------------------------------------------------------------------------
/examples/repl/states/states_utils.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import am "github.com/pancsta/asyncmachine-go/pkg/machine"
4 |
5 | // S is a type alias for a list of state names.
6 | type S = am.S
7 |
8 | // SAdd is a func alias for merging lists of states.
9 | var SAdd = am.SAdd
10 |
11 | // StateAdd is a func alias for adding to an existing state definition.
12 | var StateAdd = am.StateAdd
13 |
14 | // StateSet is a func alias for replacing parts of an existing state
15 | // definition.
16 | var StateSet = am.StateSet
17 |
18 | // SchemaMerge is a func alias for extending an existing state structure.
19 | var SchemaMerge = am.SchemaMerge
20 |
--------------------------------------------------------------------------------
/examples/benchmark_grpc/proto/worker.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package proto;
4 | option go_package = "github.com/pancsta/asyncmachine-go/examples/grpc-benchmark/proto";
5 |
6 | service WorkerService {
7 | rpc Start (Empty) returns (Empty);
8 | rpc CallOp (CallOpRequest) returns (CallOpResponse);
9 | rpc Subscribe (Empty) returns (stream Empty);
10 | rpc GetValue (Empty) returns (GetValueResponse);
11 | }
12 |
13 | message CallOpRequest {
14 | int32 op = 1;
15 | }
16 |
17 | message CallOpResponse {
18 | bool success = 1;
19 | }
20 |
21 | message Empty {
22 | }
23 |
24 | message GetValueResponse {
25 | int32 value = 1;
26 | }
--------------------------------------------------------------------------------
/examples/benchmark_grpc/states/states_utils.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import am "github.com/pancsta/asyncmachine-go/pkg/machine"
4 |
5 | // S is a type alias for a list of state names.
6 | type S = am.S
7 |
8 | // SAdd is a func alias for merging lists of states.
9 | var SAdd = am.SAdd
10 |
11 | // StateAdd is a func alias for adding to an existing state definition.
12 | var StateAdd = am.StateAdd
13 |
14 | // StateSet is a func alias for replacing parts of an existing state
15 | // definition.
16 | var StateSet = am.StateSet
17 |
18 | // SchemaMerge is a func alias for extending an existing state structure.
19 | var SchemaMerge = am.SchemaMerge
20 |
--------------------------------------------------------------------------------
/scripts/test_loop/test-loop-record.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -x
4 | PKG=pkg/rpc
5 | TEST=TestRetryConn
6 |
7 | while true; do
8 | echo "Running tests..."
9 | task clean
10 |
11 | # compile
12 | go test ./$PKG -gcflags 'all=-N -l' -c
13 | # go test ./$PKG -c
14 |
15 | # run & record
16 | env AM_TEST_RUNNER=1 \
17 | rr record rpc.test -- -test.failfast -test.parallel 1 -test.v -test.run ^${TEST}\$
18 | # rr record rpc.test -- -test.failfast -test.parallel 1 -test.v
19 | # rr record rpc.test
20 |
21 | # stop
22 | if [ $? -ne 0 ]; then
23 | echo "encountered non-zero exit code: $?";
24 | exit;
25 | fi
26 |
27 | done
--------------------------------------------------------------------------------
/examples/benchmark_grpc/worker_proto/worker.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package worker;
4 | option go_package = "github.com/pancsta/asyncmachine-go/examples/grpc-benchmark/worker_proto";
5 |
6 | service WorkerService {
7 | rpc Start (Empty) returns (Empty);
8 | rpc CallOp (CallOpRequest) returns (CallOpResponse);
9 | rpc Subscribe (Empty) returns (stream Empty);
10 | rpc GetValue (Empty) returns (GetValueResponse);
11 | }
12 |
13 | message CallOpRequest {
14 | int32 op = 1;
15 | }
16 |
17 | message CallOpResponse {
18 | bool success = 1;
19 | }
20 |
21 | message Empty {
22 | }
23 |
24 | message GetValueResponse {
25 | int32 value = 1;
26 | }
--------------------------------------------------------------------------------
/examples/benchmark_state_source/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use the official Golang image as the base image
2 | FROM golang:1.23-alpine AS builder
3 |
4 | # deps
5 | WORKDIR /app
6 | COPY go.mod .
7 | RUN go mod tidy
8 |
9 | # code
10 | COPY . .
11 | RUN go build -o main ./examples/tree_state_source
12 |
13 | # Start a new stage from scratch
14 | FROM alpine:latest
15 |
16 | # Set the Current Working Directory inside the container
17 | WORKDIR /root/
18 |
19 | # Copy the Pre-built binary file from the previous stage
20 | COPY --from=builder /app/main .
21 |
22 | # Expose ports
23 | EXPOSE 19700
24 | EXPOSE 18700
25 |
26 | # Command to run the executable
27 | CMD ["./main"]
28 |
--------------------------------------------------------------------------------
/scripts/compact_number/compact_number.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strconv"
7 |
8 | "github.com/nkall/compactnumber"
9 | )
10 |
11 | func main() {
12 | var input string
13 |
14 | if len(os.Args) > 1 {
15 | input = os.Args[1]
16 | } else {
17 | fmt.Scanln(&input) //nolint:errcheck
18 | }
19 |
20 | number, err := strconv.Atoi(input)
21 | if err != nil {
22 | fmt.Printf("Invalid number: %s\n", input)
23 | return
24 | }
25 |
26 | formatter := compactnumber.NewFormatter("en-US", compactnumber.Short)
27 | out, err := formatter.Format(number)
28 | if err != nil {
29 | panic(err)
30 | }
31 |
32 | fmt.Println(out)
33 | }
--------------------------------------------------------------------------------
/deploy/web-metrics/otel-collector-config.yaml:
--------------------------------------------------------------------------------
1 | receivers:
2 | otlp:
3 | protocols:
4 | grpc:
5 | endpoint: 0.0.0.0:4317
6 | http:
7 | endpoint: 0.0.0.0:4318
8 | cors:
9 | allowed_origins:
10 | - "http://*"
11 | - "https://*"
12 |
13 | processors:
14 | batch:
15 |
16 | exporters:
17 | # debug:
18 | # verbosity: detailed
19 | otlphttp:
20 | endpoint: http://loki:3100/otlp
21 | tls:
22 | insecure: true
23 |
24 | service:
25 | # telemetry:
26 | # logs:
27 | # level: debug
28 | pipelines:
29 | logs:
30 | receivers: [otlp]
31 | processors: [batch]
32 | exporters: [otlphttp]
--------------------------------------------------------------------------------
/examples/asynq_fileprocessing/client/client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/hibiken/asynq"
7 |
8 | tasks "github.com/pancsta/asyncmachine-go/examples/asynq_fileprocessing"
9 | )
10 |
11 | const redisAddr = "127.0.0.1:6379"
12 |
13 | func main() {
14 | client := asynq.NewClient(asynq.RedisClientOpt{Addr: redisAddr})
15 | defer client.Close()
16 |
17 | task, err := tasks.NewFileProcessingTask("foo.txt")
18 | if err != nil {
19 | log.Fatalf("could not create task: %v", err)
20 | }
21 | info, err := client.Enqueue(task)
22 | if err != nil {
23 | log.Fatalf("could not enqueue task: %v", err)
24 | }
25 | log.Printf("enqueued task: id=%s queue=%s", info.ID, info.Queue)
26 | }
27 |
--------------------------------------------------------------------------------
/.github/workflows/releaser.yml:
--------------------------------------------------------------------------------
1 | name: goreleaser
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | goreleaser:
13 | runs-on: ubuntu-latest
14 | steps:
15 | -
16 | name: Checkout
17 | uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0
20 | -
21 | name: Set up Go
22 | uses: actions/setup-go@v4
23 | -
24 | name: Run GoReleaser
25 | uses: goreleaser/goreleaser-action@v5
26 | with:
27 | distribution: goreleaser
28 | version: latest
29 | args: release --clean
30 | env:
31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/scripts/test_loop/test-loop.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #set -x
4 |
5 | while true; do
6 | echo "Running tests..."
7 | task clean
8 | output=$(env AM_TEST_RUNNER=1 go test ./... -failfast -p 1 -parallel 1 -race -v 2>&1) # Run tests and capture output
9 |
10 | if echo "$output" | grep -q "WARNING: DATA RACE"; then
11 | echo "Race condition detected!"
12 | echo "$output" # Print output for debugging
13 | exit 1
14 | fi
15 |
16 | if echo "$output" | grep -q "FAIL"; then
17 | echo "Failure detected!"
18 | echo "$output" # Print output for debugging
19 | exit 1
20 | fi
21 |
22 | # echo "No race detected. Re-running tests..."
23 | done
--------------------------------------------------------------------------------
/examples/mach_template/example.env:
--------------------------------------------------------------------------------
1 | # Docs available at https://asyncmachine.dev/config/env/README.md
2 |
3 | # telemetry
4 | AM_TRACE_FILTER=/home
5 | AM_HOSTNAME=dev
6 | AM_SERVICE=agent
7 | AM_PROM_PUSH_URL=http://localhost:9091
8 | AM_GRAFANA_URL=http://localhost:3000
9 | AM_GRAFANA_TOKEN=
10 | AM_OTEL_TRACE=1
11 | # AM_OTEL_TRACE_TXS=1
12 |
13 | # debug
14 | AM_DEBUG=1
15 | AM_DBG_ADDR=:6831
16 | #AM_LOG=2
17 | AM_LOG=3
18 | AM_HEALTHCHECK=1
19 | #
20 | #AM_RPC_LOG_SERVER=1
21 | #AM_RPC_LOG_CLIENT=1
22 | #AM_RPC_LOG_MUX=1
23 | #AM_RPC_DBG=1
24 | #
25 | #AM_NODE_LOG_SUPERVISOR=1
26 | #AM_NODE_LOG_CLIENT=1
27 | #AM_NODE_LOG_WORKER=1
28 | #
29 | #AM_PUBSUB_LOG=1
30 | #AM_PUBSUB_DBG=1
31 | #
32 | AM_REPL_ADDR=1
33 | AM_REPL_DIR=tmp
34 | # go test debug
35 | # -gcflags="-N -l" -v
36 |
--------------------------------------------------------------------------------
/pkg/states/states_utils.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import am "github.com/pancsta/asyncmachine-go/pkg/machine"
4 |
5 | // S is a type alias for a list of state names.
6 | type S = am.S
7 |
8 | // State is a type alias for a state definition. See [am.State].
9 | type State = am.State
10 |
11 | // SAdd is a func alias for merging lists of states.
12 | var SAdd = am.SAdd
13 |
14 | // StateAdd is a func alias for adding to an existing state definition.
15 | var StateAdd = am.StateAdd
16 |
17 | // StateSet is a func alias for replacing parts of an existing state
18 | // definition.
19 | var StateSet = am.StateSet
20 |
21 | // SchemaMerge is a func alias for extending an existing state structure.
22 | var SchemaMerge = am.SchemaMerge
23 |
24 | // Exception is a type alias for the exception state.
25 | var Exception = am.StateException
26 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | linters:
3 | enable:
4 | - lll
5 | settings:
6 | lll:
7 | line-length: 80
8 | tab-width: 2
9 | staticcheck:
10 | dot-import-whitelist:
11 | - "github.com/pancsta/asyncmachine-go/pkg/states/global"
12 | checks:
13 | - '-SA1000' # err comments
14 | exclusions:
15 | generated: lax
16 | presets:
17 | - comments
18 | - common-false-positives
19 | - legacy
20 | - std-error-handling
21 | paths:
22 | - vendor
23 | - third_party
24 | - pkg/pubsub/uds
25 | - third_party$
26 | - builtin$
27 | - ^examples
28 | - ^scripts
29 | formatters:
30 | exclusions:
31 | generated: lax
32 | paths:
33 | - third_party$
34 | - builtin$
35 | - ^examples
36 | - ^scripts
37 |
--------------------------------------------------------------------------------
/examples/tree_state_source/states/states_utils.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import am "github.com/pancsta/asyncmachine-go/pkg/machine"
4 |
5 | // S is a type alias for a list of state names. See [am.S].
6 | type S = am.S
7 |
8 | // State is a type alias for a state definition. See [am.State].
9 | type State = am.State
10 |
11 | // SAdd is a func alias for merging lists of states. See [am.SAdd].
12 | var SAdd = am.SAdd
13 |
14 | // StateAdd is a func alias for adding to an existing state definition. See [am.StateAdd].
15 | var StateAdd = am.StateAdd
16 |
17 | // StateSet is a func alias for replacing parts of an existing state
18 | // definition. See [am.StateSet].
19 | var StateSet = am.StateSet
20 |
21 | // SchemaMerge is a func alias for extending an existing state structure. See [am.SchemaMerge].
22 | var SchemaMerge = am.SchemaMerge
23 |
--------------------------------------------------------------------------------
/examples/nfa/states/states_utils.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import am "github.com/pancsta/asyncmachine-go/pkg/machine"
4 |
5 | // S is a type alias for a list of state names.
6 | type S = am.S
7 |
8 | // State is a type alias for a state definition. See [am.State].
9 | type State = am.State
10 |
11 | // SAdd is a func alias for merging lists of states.
12 | var SAdd = am.SAdd
13 |
14 | // StateAdd is a func alias for adding to an existing state definition.
15 | var StateAdd = am.StateAdd
16 |
17 | // StateSet is a func alias for replacing parts of an existing state
18 | // definition.
19 | var StateSet = am.StateSet
20 |
21 | // SchemaMerge is a func alias for extending an existing state structure.
22 | var SchemaMerge = am.SchemaMerge
23 |
24 | // Exception is a type alias for the exception state.
25 | var Exception = am.StateException
26 |
--------------------------------------------------------------------------------
/examples/mach_template/states/states_utils.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import am "github.com/pancsta/asyncmachine-go/pkg/machine"
4 |
5 | // S is a type alias for a list of state names.
6 | type S = am.S
7 |
8 | // State is a type alias for a state definition. See [am.State].
9 | type State = am.State
10 |
11 | // SAdd is a func alias for merging lists of states.
12 | var SAdd = am.SAdd
13 |
14 | // StateAdd is a func alias for adding to an existing state definition.
15 | var StateAdd = am.StateAdd
16 |
17 | // StateSet is a func alias for replacing parts of an existing state
18 | // definition.
19 | var StateSet = am.StateSet
20 |
21 | // SchemaMerge is a func alias for extending an existing state structure.
22 | var SchemaMerge = am.SchemaMerge
23 |
24 | // Exception is a type alias for the exception state.
25 | var Exception = am.StateException
26 |
--------------------------------------------------------------------------------
/tools/visualizer/visualizer_test.go:
--------------------------------------------------------------------------------
1 | package visualizer
2 |
3 | // func TestUpdates(t *testing.T) {
4 | // sel := &Fragment{
5 | // MachId: "cook",
6 | // States: am.S{"Exception", "StoryCookingStarted", "StoryJoke", "Ready",
7 | // "Requesting", "StoryStartAgain", "Start", "BaseDBStarting",
8 | // "CheckStories"},
9 | // // Active: am.S{"StoryJoke"},
10 | // Active: am.S{"Ready", "StoryStartAgain", "Start", "CheckStories"},
11 | // }
12 | //
13 | // path := "testdata/sample.svg"
14 | // file, err := os.Open(path)
15 | // if err != nil {
16 | // t.Fatal(err)
17 | // }
18 | // defer file.Close()
19 | //
20 | // dom, err := goquery.NewDocumentFromReader(file)
21 | // if err != nil {
22 | // t.Fatal(err)
23 | // }
24 | //
25 | // err = UpdateCache(context.Background(), path, dom, sel)
26 | // if err != nil {
27 | // t.Fatal(err)
28 | // }
29 | // }
30 |
--------------------------------------------------------------------------------
/pkg/rpc/utils_test.go:
--------------------------------------------------------------------------------
1 | package rpc
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/lithammer/dedent"
8 | "github.com/stretchr/testify/assert"
9 |
10 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
11 | )
12 |
13 | // TODO use /internal
14 |
15 | func assertStates(t *testing.T, m am.Api, expected am.S,
16 | msgAndArgs ...interface{},
17 | ) {
18 | // TODO ignore Healthcheck
19 | assert.ElementsMatch(t, expected, m.ActiveStates(nil), msgAndArgs...)
20 | }
21 |
22 | func assertTime(t *testing.T, m am.Api, states am.S, time am.Time,
23 | msgAndArgs ...interface{},
24 | ) {
25 | assert.Subset(t, m.Time(states), time, msgAndArgs...)
26 | }
27 |
28 | func assertString(
29 | t *testing.T, m am.Api, expected string, states am.S,
30 | ) {
31 | assert.Equal(t,
32 | strings.Trim(dedent.Dedent(expected), "\n"),
33 | strings.Trim(m.Inspect(states), "\n"))
34 | }
35 |
--------------------------------------------------------------------------------
/docs/jsonschema/msg_kind_req.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json-schema.org/draft/2020-12/schema",
3 | "$id": "https://github.com/pancsta/asyncmachine-go/pkg/integrations/msg-kind-req",
4 | "$ref": "#/$defs/MsgKindReq",
5 | "$defs": {
6 | "Kind": {
7 | "properties": {
8 | "Value": {
9 | "type": "string"
10 | }
11 | },
12 | "additionalProperties": false,
13 | "type": "object",
14 | "required": [
15 | "Value"
16 | ]
17 | },
18 | "MsgKindReq": {
19 | "properties": {
20 | "kind": {
21 | "$ref": "#/$defs/Kind",
22 | "description": "The kind of the request."
23 | }
24 | },
25 | "additionalProperties": false,
26 | "type": "object",
27 | "required": [
28 | "kind"
29 | ],
30 | "description": "MsgKindReq is a decoding helper."
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/docs/jsonschema/msg_kind_resp.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json-schema.org/draft/2020-12/schema",
3 | "$id": "https://github.com/pancsta/asyncmachine-go/pkg/integrations/msg-kind-resp",
4 | "$ref": "#/$defs/MsgKindResp",
5 | "$defs": {
6 | "Kind": {
7 | "properties": {
8 | "Value": {
9 | "type": "string"
10 | }
11 | },
12 | "additionalProperties": false,
13 | "type": "object",
14 | "required": [
15 | "Value"
16 | ]
17 | },
18 | "MsgKindResp": {
19 | "properties": {
20 | "kind": {
21 | "$ref": "#/$defs/Kind",
22 | "description": "The kind of the response."
23 | }
24 | },
25 | "additionalProperties": false,
26 | "type": "object",
27 | "required": [
28 | "kind"
29 | ],
30 | "description": "MsgKindResp is a decoding helper."
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/pkg/rpc/states/ss_rpc_consumer.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | )
6 |
7 | // ConsumerStatesDef contains all the states of the Consumer state machine.
8 | type ConsumerStatesDef struct {
9 | *am.StatesBase
10 | Exception string
11 |
12 | // WorkerPayload RPC server delivers the requested payload to the Client.
13 | WorkerPayload string
14 | }
15 |
16 | // ConsumerSchema represents all relations and properties of ConsumerStates.
17 | var ConsumerSchema = am.Schema{
18 | ssCo.WorkerPayload: {Multi: true},
19 | }
20 |
21 | // ConsumerHandlers is the required interface for Consumer's state handlers.
22 | type ConsumerHandlers interface {
23 | WorkerPayloadState(e *am.Event)
24 | }
25 |
26 | // EXPORTS AND GROUPS
27 |
28 | var (
29 | // ssCo is Consumer states from ConsumerStatesDef.
30 | ssCo = am.NewStates(ConsumerStatesDef{})
31 |
32 | // ConsumerStates contains all the states for the Consumer machine.
33 | ConsumerStates = ssCo
34 | )
35 |
--------------------------------------------------------------------------------
/docs/jsonschema/mutation_resp.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json-schema.org/draft/2020-12/schema",
3 | "$id": "https://github.com/pancsta/asyncmachine-go/pkg/integrations/mutation-resp",
4 | "$ref": "#/$defs/MutationResp",
5 | "$defs": {
6 | "Kind": {
7 | "properties": {
8 | "Value": {
9 | "type": "string"
10 | }
11 | },
12 | "additionalProperties": false,
13 | "type": "object",
14 | "required": [
15 | "Value"
16 | ]
17 | },
18 | "MutationResp": {
19 | "properties": {
20 | "kind": {
21 | "$ref": "#/$defs/Kind",
22 | "description": "The kind of the request."
23 | },
24 | "result": {
25 | "type": "integer",
26 | "description": "The result of the mutation request."
27 | }
28 | },
29 | "additionalProperties": false,
30 | "type": "object",
31 | "required": [
32 | "kind",
33 | "result"
34 | ]
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/examples/asynq_fileprocessing/worker/worker.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/hibiken/asynq"
7 |
8 | tasks "github.com/pancsta/asyncmachine-go/examples/asynq_fileprocessing"
9 | )
10 |
11 | const redisAddr = "127.0.0.1:6379"
12 |
13 | func main() {
14 | srv := asynq.NewServer(
15 | asynq.RedisClientOpt{Addr: redisAddr},
16 | asynq.Config{
17 | // Specify how many concurrent workers to use
18 | Concurrency: 10,
19 | // Optionally specify multiple queues with different priority.
20 | Queues: map[string]int{
21 | "critical": 6,
22 | "default": 3,
23 | "low": 1,
24 | },
25 | // See the godoc for other configuration options
26 | },
27 | )
28 |
29 | // mux maps a type to a handler
30 | mux := asynq.NewServeMux()
31 | mux.HandleFunc(tasks.TypeFileProcessing, tasks.HandleFileProcessingTask)
32 | // ...register other handlers...
33 |
34 | if err := srv.Run(mux); err != nil {
35 | log.Fatalf("could not run server: %v", err)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/deploy/web-am-dbg/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM archlinux:latest
2 |
3 | # OS
4 |
5 | RUN pacman -Sy --noconfirm go git ca-certificates \
6 | ttyd go-task zellij xclip xsel
7 | RUN mkdir -p /app
8 | RUN cp /usr/bin/go-task /usr/bin/task
9 | RUN mkdir -p ~/.config/zellij/
10 | RUN echo "show_startup_tips false" > ~/.config/zellij/config.kdl
11 | RUN echo "show_release_notes false" >> ~/.config/zellij/config.kdl
12 |
13 | # DEPS
14 |
15 | COPY ./cview /app/cview
16 | COPY ./asyncmachine-go/go.mod /app/asyncmachine-go/
17 | COPY ./asyncmachine-go/go.sum /app/asyncmachine-go/
18 |
19 | # PREBUILD
20 |
21 | WORKDIR /app/asyncmachine-go
22 | RUN --mount=type=cache,target=/root/go/pkg/mod \
23 | go mod download
24 |
25 | # SOURCE
26 |
27 | COPY ./asyncmachine-go /app/asyncmachine-go
28 | RUN --mount=type=cache,target=/root/.cache/go-build \
29 | task build-am-dbg
30 | RUN --mount=type=cache,target=/root/.cache/go-build \
31 | task build-arpc
32 |
33 | # START
34 |
35 | # ports: am-dbg ttyd
36 | EXPOSE 6831 7681
37 | ENTRYPOINT ["go-task", "web-dashboard-repl"]
38 |
--------------------------------------------------------------------------------
/deploy/web-metrics/loki.yaml:
--------------------------------------------------------------------------------
1 | limits_config:
2 | allow_structured_metadata: true
3 | otlp_config:
4 | resource_attributes:
5 | attributes_config:
6 | - action: index_label
7 | regex: asyncmachine.id
8 |
9 | auth_enabled: false
10 |
11 | server:
12 | http_listen_port: 3100
13 |
14 | common:
15 | instance_addr: 127.0.0.1
16 | path_prefix: /loki
17 | storage:
18 | filesystem:
19 | chunks_directory: /loki/chunks
20 | rules_directory: /loki/rules
21 | replication_factor: 1
22 | ring:
23 | kvstore:
24 | store: inmemory
25 |
26 | schema_config:
27 | configs:
28 | - from: 2020-10-24
29 | store: tsdb
30 | object_store: filesystem
31 | schema: v13
32 | index:
33 | prefix: index_
34 | period: 24h
35 |
36 | ruler:
37 | alertmanager_url: http://localhost:9093
38 |
39 | analytics:
40 | reporting_enabled: false
41 |
42 | distributor:
43 | otlp_config:
44 | default_resource_attributes_as_index_labels:
45 | - service.name
46 | - asyncmachine.id
--------------------------------------------------------------------------------
/pkg/helpers/help_test.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | type Args struct {
10 | Local struct{}
11 | Log string `log:"log1"`
12 | Plain string
13 | }
14 |
15 | type ARpc struct {
16 | Log string `log:"log1"`
17 | Plain string
18 | }
19 |
20 | func TestArgsRpc(t *testing.T) {
21 | a := &Args{
22 | Local: struct{}{},
23 | Log: "log",
24 | Plain: "plain",
25 | }
26 | expected := &ARpc{
27 | Log: "log",
28 | Plain: "plain",
29 | }
30 |
31 | rpc := ArgsToArgs(a, &ARpc{})
32 |
33 | assert.IsType(t, &ARpc{}, rpc)
34 | assert.Equal(t, expected.Log, rpc.Log)
35 | assert.Equal(t, expected.Plain, rpc.Plain)
36 | }
37 |
38 | func TestArgsToLogMap(t *testing.T) {
39 | a := &Args{
40 | Local: struct{}{},
41 | Log: "log",
42 | Plain: "plain",
43 | }
44 |
45 | expected := map[string]string{
46 | "log1": "log",
47 | }
48 |
49 | logMap := ArgsToLogMap(a, 0)
50 |
51 | assert.Equal(t, expected["log1"], logMap["log1"])
52 | assert.Len(t, logMap, 1)
53 | }
54 |
--------------------------------------------------------------------------------
/pkg/states/global/states_utils.go:
--------------------------------------------------------------------------------
1 | // Package global should be imported into the package's global scope with:
2 | //
3 | // import _ "github.com/pancsta/asyncmachine-go/pkg/states/global"
4 | //
5 | // This removes the need for manual updates, with the cost of an implicit
6 | // import.
7 | package global
8 |
9 | import am "github.com/pancsta/asyncmachine-go/pkg/machine"
10 |
11 | // S is a type alias for a list of state names.
12 | type S = am.S
13 |
14 | // State is a type alias for a state definition. See [am.State].
15 | type State = am.State
16 |
17 | // SAdd is a func alias for merging lists of states.
18 | var SAdd = am.SAdd
19 |
20 | // StateAdd is a func alias for adding to an existing state definition.
21 | var StateAdd = am.StateAdd
22 |
23 | // StateSet is a func alias for replacing parts of an existing state
24 | // definition.
25 | var StateSet = am.StateSet
26 |
27 | // SchemaMerge is a func alias for extending an existing state schema.
28 | var SchemaMerge = am.SchemaMerge
29 |
30 | // Exception is a type alias for the exception state.
31 | var Exception = am.StateException
32 |
--------------------------------------------------------------------------------
/examples/benchmark_grpc/client_local_test.go:
--------------------------------------------------------------------------------
1 | package benchmark_grpc
2 |
3 | import "testing"
4 |
5 | func BenchmarkClientLocal(b *testing.B) {
6 | // init
7 | worker := &Worker{}
8 | i := 0
9 | limit := b.N
10 | end := make(chan struct{})
11 |
12 | // test sub-get-process
13 | //
14 | // 1. subscription: wait for notifications
15 | // 2. getter: get a value from the worker
16 | // 3. processing: call an operation based on the value
17 | worker.Subscribe(func() {
18 | // loop
19 | i++
20 | if i > limit {
21 | close(end)
22 | return
23 | }
24 |
25 | // value (getter)
26 | value := worker.GetValue()
27 |
28 | // call op from value (processing)
29 | switch value {
30 | case Value1:
31 | go worker.CallOp(Op1)
32 | case Value2:
33 | go worker.CallOp(Op2)
34 | case Value3:
35 | go worker.CallOp(Op3)
36 | default:
37 | // err
38 | b.Fatalf("Unknown value: %v", value)
39 | }
40 | })
41 |
42 | // reset the timer to exclude setup time
43 | b.ResetTimer()
44 |
45 | // start, wait and report
46 | worker.Start()
47 | <-end
48 | b.ReportAllocs()
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/telemetry/otel_test.go:
--------------------------------------------------------------------------------
1 | package telemetry
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "go.opentelemetry.io/otel/exporters/stdout/stdoutlog"
9 |
10 | ss "github.com/pancsta/asyncmachine-go/internal/testing/states"
11 | testutils "github.com/pancsta/asyncmachine-go/internal/testing/utils"
12 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
13 | )
14 |
15 | func TestLog(t *testing.T) {
16 | var buf strings.Builder
17 | logExporter, err := stdoutlog.New(stdoutlog.WithWriter(&buf))
18 | if err != nil {
19 | t.Fatal(err)
20 | }
21 | logProvider := NewOtelLoggerProvider(logExporter)
22 | mach := testutils.NewRels(t, nil)
23 | mach.SemLogger().SetLevel(am.LogDecisions)
24 | BindOtelLogger(mach, logProvider, "")
25 |
26 | mach.Add1(ss.C, nil)
27 | mach.Remove1(ss.A, nil)
28 |
29 | err = logProvider.ForceFlush(mach.Ctx())
30 | if err != nil {
31 | t.Fatal(err)
32 | }
33 |
34 | out := buf.String()
35 |
36 | assert.Contains(t, out, `"Value":"t-TestLog"`)
37 | assert.Contains(t, out, "[queue:add] C")
38 | assert.Contains(t, out, "[auto_] +A")
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024-present pancsta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/internal/testing/states/ss_rel.go:
--------------------------------------------------------------------------------
1 | // TODO rewrite to v2
2 |
3 | package states
4 |
5 | import (
6 | "context"
7 |
8 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
9 | )
10 |
11 | // S is a type alias for a list of state names.
12 | type S = am.S
13 |
14 | // States map defines relations and properties of states.
15 | // TODO rename to rel
16 | var States = am.Schema{
17 | A: {
18 | Auto: true,
19 | Require: S{C},
20 | },
21 | B: {
22 | Multi: true,
23 | Add: S{C},
24 | },
25 | C: {
26 | After: S{D},
27 | },
28 | D: {
29 | Add: S{C, B},
30 | },
31 | }
32 |
33 | // Groups of mutually exclusive states.
34 |
35 | // var (
36 | // GroupPlaying = S{Playing, Paused}
37 | // )
38 |
39 | // #region boilerplate defs
40 |
41 | // Names of all the states (pkg enum).
42 |
43 | const (
44 | A = "A"
45 | B = "B"
46 | C = "C"
47 | D = "D"
48 | )
49 |
50 | // Names is an ordered list of all the state names.
51 | var Names = S{
52 | am.StateException,
53 | A,
54 | B,
55 | C,
56 | D,
57 | }
58 |
59 | // #endregion
60 |
61 | func NewRel(ctx context.Context) *am.Machine {
62 | return am.New(ctx, States, nil)
63 | }
64 |
--------------------------------------------------------------------------------
/examples/benchmark_state_source/Taskfile.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | tasks:
4 |
5 | start:
6 | cmds:
7 | - cd bench && ln -sfn Caddyfile-root Caddyfile
8 | - docker-compose up --detach --force-recreate --build
9 | - docker-compose stop bench
10 |
11 | stop:
12 | cmd: docker-compose down
13 |
14 | bench:
15 | cmds:
16 | - cd bench && ln -sfn Caddyfile-root Caddyfile
17 | - docker-compose restart caddy bench
18 | - sleep 25
19 | - task: logs-bench
20 |
21 | - cd bench && ln -sfn Caddyfile-root-2 Caddyfile
22 | - docker-compose restart caddy bench
23 | - sleep 25
24 | - task: logs-bench
25 |
26 | - cd bench && ln -sfn Caddyfile-root-2-4 Caddyfile
27 | - docker-compose restart caddy bench
28 | - sleep 25
29 | - task: logs-bench
30 |
31 | - cd bench && ln -sfn Caddyfile-root-2-6 Caddyfile
32 | - docker-compose restart caddy bench
33 | - sleep 25
34 | - task: logs-bench
35 |
36 | logs:
37 | interactive: true
38 | cmd: docker-compose logs --follow
39 |
40 | logs-bench:
41 | cmd: docker-compose logs --tail 22 bench
42 |
--------------------------------------------------------------------------------
/config/dashboards/dash-repl.kdl:
--------------------------------------------------------------------------------
1 | keybinds clear-defaults=true {
2 | locked {
3 | // navi
4 | bind "Alt Left" {
5 | MoveFocusOrTab "Left"; }
6 | bind "Alt Right" {
7 | MoveFocusOrTab "Right"; }
8 | bind "Alt Down" {
9 | MoveFocus "Down"; }
10 | bind "Alt Up" {
11 | MoveFocus "Up"; }
12 | bind "Alt =" {
13 | Resize "Increase"; }
14 |
15 | // resize
16 | bind "Alt =" "Alt +" {
17 | Resize "Increase"; }
18 | bind "Alt -" {
19 | Resize "Decrease"; }
20 |
21 |
22 | bind "Ctrl q" {
23 | Quit ; }
24 | }
25 | }
26 |
27 | pane_frames false
28 | copy_on_select false
29 | pane_frames false
30 | ui {
31 | pane_frames {
32 | hide_session_name true
33 | rounded_corners true
34 | }
35 | }
36 | show_release_notes false
37 | show_startup_tips false
38 |
39 | layout {
40 | pane split_direction="horizontal" {
41 | pane size="85%" command="sh" {
42 | args "-c" "./dist/am-dbg --dir $AM_REPL_DIR -l $AM_DBG_ADDR --tail --output-clients --output-tx --max-mem 1000"
43 | }
44 | pane size="15%" command="sh" {
45 | args "-c" "sleep 5 && ./dist/arpc -d $AM_REPL_DIR --watch"
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/examples/temporal_fileprocessing/fileprocessing_test.go:
--------------------------------------------------------------------------------
1 | package temporal_fileprocessing
2 |
3 | import (
4 | "context"
5 | "os"
6 | "os/signal"
7 | "syscall"
8 | "testing"
9 |
10 | "github.com/joho/godotenv"
11 |
12 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
13 | )
14 |
15 | func init() {
16 | // load .env
17 | _ = godotenv.Load()
18 |
19 | // am-dbg is required for debugging, go run it
20 | // go run github.com/pancsta/asyncmachine-go/tools/cmd/am-dbg@latest
21 | // amhelp.EnableDebugging(false)
22 | // amhelp.SetEnvLogLevel(am.LogOps)
23 | }
24 |
25 | func TestFileProcessing(t *testing.T) {
26 | ctx, cancel := context.WithCancel(context.Background())
27 |
28 | // handle OS exit signals
29 | sigs := make(chan os.Signal, 1)
30 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
31 | go func() {
32 | <-sigs
33 | cancel()
34 | }()
35 |
36 | // start the flow and wait for the result
37 | mach, err := FileProcessingFlow(ctx, t.Logf, "foo.txt")
38 | if err != nil {
39 | t.Fatal(err)
40 | }
41 | if !mach.Is(am.S{"FileUploaded"}) {
42 | t.Fatal("not FileUploaded")
43 | }
44 |
45 | t.Log(mach.String())
46 | t.Log(mach.StringAll())
47 | t.Log(mach.Inspect(nil))
48 | }
49 |
--------------------------------------------------------------------------------
/config/dashboards/dash-repl-web.kdl:
--------------------------------------------------------------------------------
1 | keybinds clear-defaults=true {
2 | locked {
3 | // navi
4 | bind "Alt Left" {
5 | MoveFocusOrTab "Left"; }
6 | bind "Alt Right" {
7 | MoveFocusOrTab "Right"; }
8 | bind "Alt Down" {
9 | MoveFocus "Down"; }
10 | bind "Alt Up" {
11 | MoveFocus "Up"; }
12 | bind "Alt =" {
13 | Resize "Increase"; }
14 |
15 | // resize
16 | bind "Alt =" "Alt +" {
17 | Resize "Increase"; }
18 | bind "Alt -" {
19 | Resize "Decrease"; }
20 |
21 |
22 | bind "Ctrl q" {
23 | Quit ; }
24 | }
25 | }
26 |
27 | pane_frames false
28 | copy_on_select false
29 | pane_frames false
30 | ui {
31 | pane_frames {
32 | hide_session_name true
33 | rounded_corners true
34 | }
35 | }
36 | show_release_notes false
37 | show_startup_tips false
38 |
39 | layout {
40 | pane split_direction="horizontal" {
41 | pane size="85%" command="sh" {
42 | args "-c" "./dist/am-dbg --dir $AM_REPL_DIR -l $AM_DBG_ADDR --tail --output-clients --output-tx --max-mem 1000 --enable-clipboard=false"
43 | }
44 | pane size="15%" command="sh" {
45 | args "-c" "sleep 5 && ./dist/arpc -d $AM_REPL_DIR --watch"
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tools/cmd/am-relay/cmd_relay.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/alexflint/go-arg"
8 |
9 | "github.com/pancsta/asyncmachine-go/tools/relay"
10 | "github.com/pancsta/asyncmachine-go/tools/relay/states"
11 | "github.com/pancsta/asyncmachine-go/tools/relay/types"
12 | )
13 |
14 | var ss = states.RelayStates
15 | var args types.Args
16 |
17 | func main() {
18 | p := arg.MustParse(&args)
19 | ctx := context.Background()
20 | if p.Subcommand() == nil {
21 | p.Fail("missing subcommand (" + types.CmdRotateDbg + ")")
22 | }
23 |
24 | switch {
25 | case args.RotateDbg != nil:
26 | if err := rotateDbg(ctx, &args); err != nil {
27 | _ = p.FailSubcommand(err.Error(), types.CmdRotateDbg)
28 | }
29 | }
30 |
31 | }
32 |
33 | func rotateDbg(ctx context.Context, args *types.Args) error {
34 | r, err := relay.New(ctx, args, func(msg string, args ...any) {
35 | fmt.Printf(msg, args...)
36 | })
37 | if err != nil {
38 | return err
39 | }
40 | r.Mach.Add1(ss.Start, nil)
41 | <-r.Mach.WhenNot1(ss.Start, nil)
42 | if r.Mach.Is1(ss.Exception) {
43 | fmt.Printf("last err: %v\n", r.Mach.Err())
44 | }
45 | fmt.Println("exiting am-relay, bye")
46 |
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/config/dashboards/dash-wide-narrow.kdl:
--------------------------------------------------------------------------------
1 | keybinds clear-defaults=true {
2 | locked {
3 | // navi
4 | bind "Alt Left" {
5 | MoveFocusOrTab "Left"; }
6 | bind "Alt Right" {
7 | MoveFocusOrTab "Right"; }
8 | bind "Alt Down" {
9 | MoveFocus "Down"; }
10 | bind "Alt Up" {
11 | MoveFocus "Up"; }
12 | bind "Alt =" {
13 | Resize "Increase"; }
14 |
15 | // resize
16 | bind "Alt =" "Alt +" {
17 | Resize "Increase"; }
18 | bind "Alt -" {
19 | Resize "Decrease"; }
20 |
21 |
22 | bind "Ctrl q" {
23 | Quit ; }
24 | }
25 | }
26 |
27 | pane_frames false
28 | copy_on_select false
29 | pane_frames false
30 | ui {
31 | pane_frames {
32 | hide_session_name true
33 | rounded_corners true
34 | }
35 | }
36 | show_release_notes false
37 | show_startup_tips false
38 |
39 | layout {
40 | pane split_direction="vertical" {
41 | pane size="75%" command="sh" {
42 | args "-c" "sleep 2 && task am-dbg -- --dir tmp -l localhost:6831 --fwd-data localhost:6832 --output-diagrams 3 --tail --output-clients"
43 | }
44 | pane size="25%" command="sh" {
45 | args "-c" "task am-dbg -- -l localhost:6832 --view-narrow --view-timelines 0 --tail --view-timelines 1"
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/node/states/ss_bootstrap.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | ssrpc "github.com/pancsta/asyncmachine-go/pkg/rpc/states"
6 | ssam "github.com/pancsta/asyncmachine-go/pkg/states"
7 | . "github.com/pancsta/asyncmachine-go/pkg/states/global"
8 | )
9 |
10 | // BootstrapStatesDef contains all the states of the Bootstrap state machine.
11 | // The target state is WorkerAddr, activated by an aRPC client.
12 | type BootstrapStatesDef struct {
13 | *am.StatesBase
14 |
15 | // WorkerAddr - The awaited worker passed its connection details.
16 | WorkerAddr string
17 |
18 | // inherit from BasicStatesDef
19 | *ssam.BasicStatesDef
20 | // inherit from WorkerStatesDef
21 | *ssrpc.WorkerStatesDef
22 | }
23 |
24 | // BootstrapSchema represents all relations and properties of
25 | // BootstrapStatesDef.
26 | var BootstrapSchema = SchemaMerge(
27 | // inherit from BasicSchema
28 | ssam.BasicSchema,
29 | // inherit from WorkerStruct
30 | ssrpc.WorkerSchema,
31 | am.Schema{
32 | cos.WorkerAddr: {},
33 | })
34 |
35 | // EXPORTS AND GROUPS
36 |
37 | var (
38 | cos = am.NewStates(BootstrapStatesDef{})
39 |
40 | // BootstrapStates contains all the states for the Bootstrap machine.
41 | BootstrapStates = cos
42 | )
43 |
--------------------------------------------------------------------------------
/tools/relay/types/relay_cli.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import "time"
4 |
5 | const CmdRotateDbg = "rotate-dbg"
6 |
7 | type OutputFunc func(msg string, args ...any)
8 |
9 | // nolint:lll
10 | type Args struct {
11 | RotateDbg *ArgsRotateDbg `arg:"subcommand:rotate-dbg" help:"Rotate dbg protocol with fragmented dump files"`
12 | Debug bool `arg:"--debug" help:"Enable debugging for asyncmachine"`
13 | // TODO ID suffix
14 | }
15 |
16 | // ArgsRotateDbg converts dbg dumps to other formats / versions.
17 | // am-relay convert-dbg am-dbg-dump.gob.br -o am-vis
18 | // nolint:lll
19 | type ArgsRotateDbg struct {
20 | ListenAddr string `arg:"-l,--listen-addr" default:"localhost:2732" help:"Listen address for RPC server"`
21 | FwdAddr []string `arg:"-f,--fwd-addr,separate" help:"Address of an RPC server to forward data to (repeatable)"`
22 | IntervalTx int `arg:"--interval-tx" default:"10000" help:"Amount of transitions to create a dump file"`
23 | IntervalTime time.Duration `arg:"--interval-duration" default:"24h" help:"Amount of human time to create a dump file"`
24 | Filename string `arg:"-o,--output" default:"am-dbg-dump" help:"Output file base name"`
25 | Dir string `arg:"-d,--dir" default:"." help:"Output directory"`
26 | }
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Jetbrains IDEs
2 | .idea
3 |
4 | # Vim
5 | *.swp
6 | *.swn
7 | *.swo
8 |
9 | # MacOS system file
10 | .DS_Store
11 |
12 | # Environment variable files
13 | .env
14 |
15 | # PEM files
16 | *.pem
17 |
18 | # Binaries for programs and plugins
19 | *.exe
20 | *.exe~
21 | *.dll
22 | *.so
23 | *.dylib
24 |
25 | # Binaries compiled by Go
26 | build/
27 |
28 | # Test binary, built with `go test -c`
29 | *.test
30 |
31 | # Output of the go coverage tool
32 | *.new
33 | *.old
34 | *.out
35 |
36 | # Dependency directories
37 | vendor/
38 |
39 | # Redis
40 | *.rdb
41 |
42 | # Go workspace file
43 | go.work
44 |
45 | # local dev
46 | /.dev
47 | /dist
48 | /tools/cmd/am-dbg/am-dbg
49 | *.log
50 | /*.gob
51 | /assets/*.cast.yml
52 |
53 | /tmp*
54 | _py
55 | /.run
56 | /*.bz2
57 | /*.br
58 | /mem.*
59 | /cpu.*
60 | /.local
61 | /assets/video.gif
62 | /*.svg
63 | /assets/*.pdf
64 | /*.png
65 | /*.prof
66 | /am-dbg-ssh
67 | /built
68 | /log.md
69 | /coverage*.txt
70 | /tests*.txt
71 | /assets
72 | /__*
73 | /trace*
74 | *.mmd
75 | /demo
76 | /am-dbg.txt
77 | tools/visualizer/testdata/*
78 | *.sqlite
79 | *.sqlite-journal
80 | *.sqlite-shm
81 | *.sqlite-wal
82 | /pkg/history/frostdb/databases/
83 | /pkg/x/history/frostdb/databases/
84 | /pkg/history/bbolt/amhist.db
85 | /*.d2
86 | /docs/api
87 | /docs/website
88 |
--------------------------------------------------------------------------------
/codecov.yaml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - "pkg/pubsub/uds/**" # belongs to libp2p
3 |
4 | component_management:
5 |
6 | default_rules:
7 | statuses:
8 | - type: project
9 | target: auto
10 | branches:
11 | - "!main"
12 |
13 | individual_components:
14 | - component_id: machine
15 | name: pkg/machine
16 | paths:
17 | - pkg/machine/**
18 | - component_id: helpers
19 | name: pkg/helpers
20 | paths:
21 | - pkg/helpers/**
22 | - component_id: history
23 | name: pkg/history
24 | paths:
25 | - pkg/history/**
26 | - component_id: node
27 | name: pkg/node
28 | paths:
29 | - pkg/node/**
30 | - component_id: pubsub
31 | name: pkg/pubsub
32 | paths:
33 | - pkg/pubsub/**
34 | - component_id: rpc
35 | name: pkg/rpc
36 | paths:
37 | - pkg/rpc/**
38 | - component_id: states
39 | name: pkg/states
40 | paths:
41 | - pkg/states/**
42 | - component_id: telemetry
43 | name: pkg/telemetry
44 | paths:
45 | - pkg/telemetry/**
46 |
47 | - component_id: tools
48 | name: tools
49 | paths:
50 | - tools/**
51 |
52 | - component_id: internal
53 | name: internal
54 | paths:
55 | - internal/**
--------------------------------------------------------------------------------
/examples/arpc/states/ss_example.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | ssrpc "github.com/pancsta/asyncmachine-go/pkg/rpc/states"
6 | )
7 |
8 | // ExampleStatesDef contains all the states of the Example state machine.
9 | type ExampleStatesDef struct {
10 | *am.StatesBase
11 |
12 | Foo string
13 | Bar string
14 | Baz string
15 |
16 | // inherit from rpc/WorkerStatesDef
17 | *ssrpc.WorkerStatesDef
18 | }
19 |
20 | // ExampleGroupsDef contains all the state groups Example state machine.
21 | type ExampleGroupsDef struct {
22 | Mutex S
23 | }
24 |
25 | // ExampleSchema represents all relations and properties of ExampleStates.
26 | var ExampleSchema = SchemaMerge(
27 | // inherit from rpc/WorkerSchema
28 | ssrpc.WorkerSchema,
29 | am.Schema{
30 |
31 | ssE.Foo: {Remove: sgE.Mutex},
32 | ssE.Bar: {Remove: sgE.Mutex},
33 | ssE.Baz: {Remove: sgE.Mutex},
34 | })
35 |
36 | // EXPORTS AND GROUPS
37 |
38 | var (
39 | ssE = am.NewStates(ExampleStatesDef{})
40 | sgE = am.NewStateGroups(ExampleGroupsDef{
41 | Mutex: S{ssE.Foo, ssE.Bar, ssE.Baz},
42 | })
43 |
44 | // ExampleStates contains all the states for the Example machine.
45 | ExampleStates = ssE
46 | // ExampleGroups contains all the state groups for the Example machine.
47 | ExampleGroups = sgE
48 | )
49 |
--------------------------------------------------------------------------------
/examples/repl/states/ss_example.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | ssrpc "github.com/pancsta/asyncmachine-go/pkg/rpc/states"
6 | )
7 |
8 | // ExampleStatesDef contains all the states of the Example state machine.
9 | type ExampleStatesDef struct {
10 | *am.StatesBase
11 |
12 | Foo string
13 | Bar string
14 | Baz string
15 |
16 | // inherit from rpc/WorkerStatesDef
17 | *ssrpc.WorkerStatesDef
18 | }
19 |
20 | // ExampleGroupsDef contains all the state groups Example state machine.
21 | type ExampleGroupsDef struct {
22 | Mutex S
23 | }
24 |
25 | // ExampleSchema represents all relations and properties of ExampleStates.
26 | var ExampleSchema = SchemaMerge(
27 | // inherit from rpc/WorkerSchema
28 | ssrpc.WorkerSchema,
29 | am.Schema{
30 |
31 | ssE.Foo: {Remove: sgE.Mutex},
32 | ssE.Bar: {Remove: sgE.Mutex},
33 | ssE.Baz: {Remove: sgE.Mutex},
34 | })
35 |
36 | // EXPORTS AND GROUPS
37 |
38 | var (
39 | ssE = am.NewStates(ExampleStatesDef{})
40 | sgE = am.NewStateGroups(ExampleGroupsDef{
41 | Mutex: S{ssE.Foo, ssE.Bar, ssE.Baz},
42 | })
43 |
44 | // ExampleStates contains all the states for the Example machine.
45 | ExampleStates = ssE
46 | // ExampleGroups contains all the state groups for the Example machine.
47 | ExampleGroups = sgE
48 | )
49 |
--------------------------------------------------------------------------------
/examples/benchmark_grpc/worker_states/worker_states.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import am "github.com/pancsta/asyncmachine-go/pkg/machine"
4 |
5 | // S is a type alias for a list of state names.
6 | type S = am.S
7 |
8 | // State is a type alias for a single state definition.
9 | type State = am.State
10 |
11 | // States structure defines relations and properties of states.
12 | var States = am.Schema{
13 | // toggle
14 | Start: {},
15 |
16 | // ops
17 | CallOp: {
18 | Multi: true,
19 | Require: S{Start},
20 | },
21 |
22 | // events
23 | Event: {
24 | Multi: true,
25 | Require: S{Start},
26 | },
27 |
28 | // values
29 | Value1: {Remove: GroupValues},
30 | Value2: {Remove: GroupValues},
31 | Value3: {Remove: GroupValues},
32 | }
33 |
34 | // Groups of mutually exclusive states.
35 |
36 | var (
37 | GroupValues = S{Value1, Value2, Value3}
38 | )
39 |
40 | // #region boilerplate defs
41 |
42 | // Names of all the states (pkg enum).
43 |
44 | const (
45 | Start = "Start"
46 | Event = "Event"
47 | Value1 = "Value1"
48 | Value2 = "Value2"
49 | Value3 = "Value3"
50 | CallOp = "CallOp"
51 | )
52 |
53 | // Names is an ordered list of all the state names.
54 | var Names = S{
55 | am.StateException,
56 | Start,
57 | Event,
58 | Value1,
59 | Value2,
60 | Value3,
61 | CallOp,
62 | }
63 |
64 | // #endregion
65 |
--------------------------------------------------------------------------------
/pkg/rpc/states/ss_rpc_worker.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | . "github.com/pancsta/asyncmachine-go/pkg/states/global"
6 | )
7 |
8 | // WorkerStatesDef contains all the states of the Worker state machine.
9 | type WorkerStatesDef struct {
10 | *am.StatesBase
11 |
12 | // errors
13 |
14 | // ErrProviding - Worker had issues providing the requested payload.
15 | ErrProviding string
16 | // ErrSendPayload - RPC server had issues sending the requested payload to
17 | // the RPC client.
18 | ErrSendPayload string
19 |
20 | // rpc getter
21 |
22 | // SendPayload - Worker delivered the requested payload to the RPC server
23 | // using rpc.Pass, rpc.A, and rpc.ArgsPayload.
24 | SendPayload string
25 | }
26 |
27 | // WorkerSchema represents all relations and properties of WorkerStates.
28 | var WorkerSchema = SchemaMerge(
29 | am.Schema{
30 |
31 | // errors
32 |
33 | ssW.ErrProviding: {Require: S{am.StateException}},
34 | ssW.ErrSendPayload: {Require: S{am.StateException}},
35 |
36 | // rcp getter
37 |
38 | ssW.SendPayload: {Multi: true},
39 | })
40 |
41 | // EXPORTS AND GROUPS
42 |
43 | var (
44 | // ssW is worker states from WorkerStatesDef.
45 | ssW = am.NewStates(WorkerStatesDef{})
46 |
47 | // WorkerStates contains all the states for the Worker machine.
48 | WorkerStates = ssW
49 | )
50 |
--------------------------------------------------------------------------------
/config/dashboards/dash-wide-double-narrow.kdl:
--------------------------------------------------------------------------------
1 | keybinds clear-defaults=true {
2 | locked {
3 | // navi
4 | bind "Alt Left" {
5 | MoveFocusOrTab "Left"; }
6 | bind "Alt Right" {
7 | MoveFocusOrTab "Right"; }
8 | bind "Alt Down" {
9 | MoveFocus "Down"; }
10 | bind "Alt Up" {
11 | MoveFocus "Up"; }
12 | bind "Alt =" {
13 | Resize "Increase"; }
14 |
15 | // resize
16 | bind "Alt =" "Alt +" {
17 | Resize "Increase"; }
18 | bind "Alt -" {
19 | Resize "Decrease"; }
20 |
21 |
22 | bind "Ctrl q" {
23 | Quit ; }
24 | }
25 | }
26 |
27 | pane_frames false
28 | copy_on_select false
29 | pane_frames false
30 | ui {
31 | pane_frames {
32 | hide_session_name true
33 | rounded_corners true
34 | }
35 | }
36 | show_release_notes false
37 | show_startup_tips false
38 |
39 | layout {
40 | pane split_direction="vertical" {
41 | pane size="50%" command="sh" {
42 | args "-c" "sleep 2 && task am-dbg -- --dir tmp -l localhost:6831 --fwd-data localhost:6832 --fwd-data localhost:6833 --output-diagrams 3 --tail --output-clients"
43 | }
44 | pane size="25%" command="sh" {
45 | args "-c" "task am-dbg -- -l localhost:6832 --view-narrow --view-timelines 1 --tail"
46 | }
47 | pane size="25%" command="sh" {
48 | args "-c" "task am-dbg -- -l localhost:6833 --view-narrow --view-timelines 1 --tail"
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/docs/diagrams-examples.md:
--------------------------------------------------------------------------------
1 | # Diagrams from Examples
2 |
3 | ## Tree State Source
4 |
5 | - [origin](/examples/tree_state_source)
6 |
7 | ```mermaid
8 | flowchart BT
9 | Root
10 | Replicant-1 -- aRPC --> Root
11 | Replicant-2 -- aRPC --> Root
12 |
13 | Replicant-1-1 -- aRPC --> Replicant-1
14 | Replicant-1-2 -- aRPC --> Replicant-1
15 | Replicant-1-3 -- aRPC --> Replicant-1
16 |
17 | Replicant-2-1 -- aRPC --> Replicant-2
18 | Replicant-2-2 -- aRPC --> Replicant-2
19 | Replicant-2-3 -- aRPC --> Replicant-2
20 | ```
21 |
22 | ## Benchmark State Source
23 |
24 | - [origin](/examples/benchmark_state_source)
25 |
26 | ```mermaid
27 | flowchart BT
28 | Root
29 | Replicant-1 -- aRPC --> Root
30 | Replicant-2 -- aRPC --> Root
31 |
32 | Replicant-1-1 -- aRPC --> Replicant-1
33 | Replicant-1-2 -- aRPC --> Replicant-1
34 | Replicant-1-3 -- aRPC --> Replicant-1
35 |
36 | Replicant-2-1 -- aRPC --> Replicant-2
37 | Replicant-2-2 -- aRPC --> Replicant-2
38 | Replicant-2-3 -- aRPC --> Replicant-2
39 |
40 | Caddy[Caddy Load Balancer]
41 | Caddy -- HTTP --> Replicant-1-1
42 | Caddy -- HTTP --> Replicant-1-2
43 | Caddy -- HTTP --> Replicant-1-3
44 | Caddy -- HTTP --> Replicant-2-1
45 | Caddy -- HTTP --> Replicant-2-2
46 | Caddy -- HTTP --> Replicant-2-3
47 |
48 | Benchmark[Benchmark go-wrt]
49 | Benchmark -- HTTP --> Caddy
50 | ```
51 |
--------------------------------------------------------------------------------
/pkg/rpc/states/ss_mux.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | "github.com/pancsta/asyncmachine-go/pkg/states"
6 | . "github.com/pancsta/asyncmachine-go/pkg/states/global"
7 | )
8 |
9 | // MuxStatesDef contains all the states of the Mux state machine.
10 | // The target state is PortInfo, activated by an aRPC client.
11 | type MuxStatesDef struct {
12 | // shadow duplicated StatesBase
13 | *am.StatesBase
14 |
15 | // basics
16 |
17 | // Ready - mux is ready to accept new clients.
18 | Ready string
19 |
20 | ClientConnected string
21 | HasClients string
22 | // NewServerErr - new server returned an error. The mux is still running.
23 | NewServerErr string
24 |
25 | // inherit from BasicStatesDef
26 | *states.BasicStatesDef
27 | }
28 |
29 | // MuxSchema represents all relations and properties of MuxStatesDef.
30 | var MuxSchema = SchemaMerge(
31 | states.BasicSchema,
32 | am.Schema{
33 | ssD.Exception: {
34 | Multi: true,
35 | Remove: S{ssS.Ready},
36 | },
37 |
38 | ssD.Ready: {
39 | Require: S{ssS.Start},
40 | },
41 |
42 | ssD.ClientConnected: {
43 | Multi: true,
44 | Require: states.S{ssD.Start},
45 | },
46 | ssD.HasClients: {Require: states.S{ssD.Start}},
47 | ssD.NewServerErr: {},
48 | })
49 |
50 | // EXPORTS AND GROUPS
51 |
52 | var (
53 | ssD = am.NewStates(MuxStatesDef{})
54 |
55 | // MuxStates contains all the states for the Mux machine.
56 | MuxStates = ssD
57 | )
58 |
--------------------------------------------------------------------------------
/pkg/history/test/history_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 |
9 | amhist "github.com/pancsta/asyncmachine-go/pkg/history"
10 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
11 | amss "github.com/pancsta/asyncmachine-go/pkg/states"
12 | )
13 |
14 | func TestTrack(t *testing.T) {
15 | // configs
16 | rounds := 50
17 | onErr := func(err error) {
18 | t.Error(err)
19 | }
20 | cfg := amhist.Config{
21 | TrackedStates: am.S{ss.Start},
22 | }
23 |
24 | // init machine and history
25 | ctx := context.Background()
26 | mach := am.New(ctx, amss.BasicSchema, &am.Opts{Id: "MyMach1"})
27 | mem, err := amhist.NewMemory(ctx, nil, mach, cfg, onErr)
28 | require.NoError(t, err)
29 | defer func() {
30 | require.NoError(t, mem.Dispose())
31 | }()
32 |
33 | // common test
34 | AssertBasics(t, mem, rounds)
35 | }
36 |
37 | func TestTrackMany(t *testing.T) {
38 | // configs
39 | onErr := func(err error) {
40 | t.Error(err)
41 | }
42 | cfg := amhist.Config{
43 | TrackedStates: am.S{ss.Start},
44 | MaxRecords: 10e6,
45 | }
46 |
47 | // init machine and history
48 | ctx := context.Background()
49 | mach := am.New(ctx, amss.BasicSchema, &am.Opts{Id: "MyMach1"})
50 | mem, err := amhist.NewMemory(ctx, nil, mach, cfg, onErr)
51 | require.NoError(t, err)
52 | defer func() {
53 | require.NoError(t, mem.Dispose())
54 | }()
55 |
56 | // common test
57 | rounds := 50_000
58 | AssertBasics(t, mem, rounds)
59 | }
60 |
--------------------------------------------------------------------------------
/examples/temporal_expense/states/ss_expense.go:
--------------------------------------------------------------------------------
1 | // TODO update to v2 states file
2 |
3 | package states
4 |
5 | import am "github.com/pancsta/asyncmachine-go/pkg/machine"
6 |
7 | // S is a type alias for a list of state names.
8 | type S = am.S
9 |
10 | // States map defines relations and properties of states.
11 | var States = am.Schema{
12 | CreatingExpense: {Remove: GroupExpense},
13 | ExpenseCreated: {Remove: GroupExpense},
14 | WaitingForApproval: {
15 | Auto: true,
16 | Remove: GroupApproval,
17 | },
18 | ApprovalGranted: {Remove: GroupApproval},
19 | PaymentInProgress: {
20 | Auto: true,
21 | Remove: GroupPayment,
22 | },
23 | PaymentCompleted: {Remove: GroupPayment},
24 | }
25 |
26 | // Groups of mutually exclusive states.
27 |
28 | var (
29 | GroupExpense = S{CreatingExpense, ExpenseCreated}
30 | GroupApproval = S{WaitingForApproval, ApprovalGranted}
31 | GroupPayment = S{PaymentInProgress, PaymentCompleted}
32 | )
33 |
34 | // #region boilerplate defs
35 |
36 | // Names of all the states (pkg enum).
37 |
38 | const (
39 | CreatingExpense = "CreatingExpense"
40 | ExpenseCreated = "ExpenseCreated"
41 | WaitingForApproval = "WaitingForApproval"
42 | ApprovalGranted = "ApprovalGranted"
43 | PaymentInProgress = "PaymentInProgress"
44 | PaymentCompleted = "PaymentCompleted"
45 | )
46 |
47 | // Names is an ordered list of all the state names.
48 | var Names = S{CreatingExpense, ExpenseCreated, WaitingForApproval, ApprovalGranted, PaymentInProgress, PaymentCompleted}
49 |
50 | // #endregion
51 |
--------------------------------------------------------------------------------
/examples/tree_state_source/gen-grafana.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # root
4 | go ../../tools/cmd/am-gen grafana
5 | --name root
6 | --folder tree_state_source
7 | --ids root,_rm-root,_rs-root-0,_rs-root-1,_rs-root-2
8 | --grafana-url http://localhost:3000
9 | --source tree_state_source_root
10 |
11 | # replicant 1
12 | go ../../tools/cmd/am-gen grafana
13 | --name rep-1
14 | --folder tree_state_source
15 | --grafana-url http://localhost:3000
16 | --source tree_state_source_rep_1
17 |
18 | # replicant 2
19 | go ../../tools/cmd/am-gen grafana
20 | --name rep-2
21 | --folder tree_state_source
22 | --grafana-url http://localhost:3000
23 | --source tree_state_source_rep_2
24 |
25 | # replicant 1 of replicant 1
26 | go ../../tools/cmd/am-gen grafana
27 | --name rep-1-1
28 | --folder tree_state_source
29 | --grafana-url http://localhost:3000
30 | --source tree_state_source_rep_1_1
31 |
32 | # replicant 2 of replicant 1
33 | go ../../tools/cmd/am-gen grafana
34 | --name rep-1-2
35 | --folder tree_state_source
36 | --grafana-url http://localhost:3000
37 | --source tree_state_source_rep_1_2
38 |
39 | # replicant 1 of replicant 2
40 | go ../../tools/cmd/am-gen grafana
41 | --name rep-2-1
42 | --folder tree_state_source
43 | --grafana-url http://localhost:3000
44 | --source tree_state_source_rep_2_1
45 |
46 | # replicant 2 of replicant 2
47 | go ../../tools/cmd/am-gen grafana
48 | --name rep-2-2
49 | --folder tree_state_source
50 | --grafana-url http://localhost:3000
51 | --source tree_state_source_rep_2_2
52 |
--------------------------------------------------------------------------------
/tools/relay/states/ss_relay.go:
--------------------------------------------------------------------------------
1 | // Package states contains a stateful schema-v2 for Relay.
2 | // Bootstrapped with am-gen. Edit manually or re-gen & merge.
3 | package states
4 |
5 | import (
6 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
7 | ss "github.com/pancsta/asyncmachine-go/pkg/states"
8 | . "github.com/pancsta/asyncmachine-go/pkg/states/global"
9 | ssdbg "github.com/pancsta/asyncmachine-go/tools/debugger/states"
10 | )
11 |
12 | // RelayStatesDef contains all the states of the Relay state machine.
13 | type RelayStatesDef struct {
14 | *am.StatesBase
15 |
16 | // inherit from BasicStatesDef
17 | *ss.BasicStatesDef
18 | // inherit from DisposedStatesDef
19 | *ss.DisposedStatesDef
20 | // inherit from [ssdbg.ServerStatesDef]
21 | *ssdbg.ServerStatesDef
22 | }
23 |
24 | // RelayGroupsDef contains all the state groups Relay state machine.
25 | type RelayGroupsDef struct {
26 | }
27 |
28 | // RelaySchema represents all relations and properties of RelayStates.
29 | var RelaySchema = SchemaMerge(
30 | // inherit from BasicStruct
31 | ss.BasicSchema,
32 | // inherit from DisposedStruct
33 | ss.DisposedSchema,
34 | // inherit from ServerSchema
35 | ssdbg.ServerSchema,
36 | am.Schema{
37 | // TODO own states
38 | })
39 |
40 | // EXPORTS AND GROUPS
41 |
42 | var (
43 | ssV = am.NewStates(RelayStatesDef{})
44 | sgV = am.NewStateGroups(RelayGroupsDef{})
45 |
46 | // RelayStates contains all the states for the Relay machine.
47 | RelayStates = ssV
48 | // RelayGroups contains all the state groups for the Relay machine.
49 | RelayGroups = sgV
50 | )
51 |
--------------------------------------------------------------------------------
/examples/temporal_fileprocessing/states/ss_fileprocessing.go:
--------------------------------------------------------------------------------
1 | // TODO update to v2 states file
2 |
3 | package states
4 |
5 | import am "github.com/pancsta/asyncmachine-go/pkg/machine"
6 |
7 | // S is a type alias for a list of state names.
8 | type S = am.S
9 |
10 | // States map defines relations and properties of states.
11 | var States = am.Schema{
12 | DownloadingFile: {Remove: GroupFileDownloaded},
13 | FileDownloaded: {Remove: GroupFileDownloaded},
14 | ProcessingFile: {
15 | Auto: true,
16 | Require: S{FileDownloaded},
17 | Remove: GroupFileProcessed,
18 | },
19 | FileProcessed: {Remove: GroupFileProcessed},
20 | UploadingFile: {
21 | Auto: true,
22 | Require: S{FileProcessed},
23 | Remove: GroupFileUploaded,
24 | },
25 | FileUploaded: {Remove: GroupFileUploaded},
26 | }
27 |
28 | // Groups of mutually exclusive states.
29 |
30 | var (
31 | GroupFileDownloaded = S{DownloadingFile, FileDownloaded}
32 | GroupFileProcessed = S{ProcessingFile, FileProcessed}
33 | GroupFileUploaded = S{UploadingFile, FileUploaded}
34 | )
35 |
36 | // #region boilerplate defs
37 |
38 | // Names of all the states (pkg enum).
39 |
40 | const (
41 | DownloadingFile = "DownloadingFile"
42 | FileDownloaded = "FileDownloaded"
43 | ProcessingFile = "ProcessingFile"
44 | FileProcessed = "FileProcessed"
45 | UploadingFile = "UploadingFile"
46 | FileUploaded = "FileUploaded"
47 | )
48 |
49 | // Names is an ordered list of all the state names.
50 | var Names = S{DownloadingFile, FileDownloaded, ProcessingFile, FileProcessed, UploadingFile, FileUploaded}
51 |
52 | // #endregion
53 |
--------------------------------------------------------------------------------
/examples/benchmark_grpc/worker.go:
--------------------------------------------------------------------------------
1 | package benchmark_grpc
2 |
3 | import (
4 | "log"
5 | "math/rand"
6 | "os"
7 | )
8 |
9 | type Op int
10 |
11 | const (
12 | Op1 Op = iota + 1
13 | Op2
14 | Op3
15 | )
16 |
17 | type Value int
18 |
19 | const (
20 | Value1 Value = iota + 1
21 | Value2
22 | Value3
23 | )
24 |
25 | type Worker struct {
26 | ErrCount int
27 | SuccessCount int
28 | value Value
29 | evListener func()
30 | }
31 |
32 | func (s *Worker) Start() {
33 | s.CallOp(Op(rand.Intn(3) + 1))
34 | }
35 |
36 | func (s *Worker) Subscribe(lis func()) {
37 | l("worker", "Subscribe")
38 | s.evListener = lis
39 | }
40 |
41 | func (s *Worker) GetValue() Value {
42 | return s.value
43 | }
44 |
45 | func (s *Worker) CallOp(op Op) {
46 | l("worker", "Call op: %v", op)
47 |
48 | // assert the value
49 | if s.value != 0 {
50 | switch op {
51 | case Op1:
52 | if s.value != Value1 {
53 | s.ErrCount++
54 | }
55 | s.SuccessCount++
56 | case Op2:
57 | if s.value != Value2 {
58 | s.ErrCount++
59 | }
60 | s.SuccessCount++
61 | case Op3:
62 | if s.value != Value3 {
63 | s.ErrCount++
64 | }
65 | s.SuccessCount++
66 | default:
67 | // err
68 | s.ErrCount++
69 | }
70 | }
71 |
72 | // create a rand value
73 | s.value = Value(rand.Intn(3) + 1)
74 |
75 | // call an event
76 | s.notify()
77 | }
78 |
79 | func (s *Worker) notify() {
80 | l("worker", "Notify")
81 | s.evListener()
82 | }
83 |
84 | func l(src, msg string, args ...any) {
85 | if os.Getenv("BENCH_DEBUG") == "" {
86 | return
87 | }
88 | log.Printf(src+": "+msg, args...)
89 | }
90 |
--------------------------------------------------------------------------------
/tools/visualizer/states/ss_visualizer.go:
--------------------------------------------------------------------------------
1 | // Package states contains a stateful schema-v2 for Visualizer.
2 | // Bootstrapped with am-gen. Edit manually or re-gen & merge.
3 | package states
4 |
5 | import (
6 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
7 | ss "github.com/pancsta/asyncmachine-go/pkg/states"
8 | . "github.com/pancsta/asyncmachine-go/pkg/states/global"
9 | ssdbg "github.com/pancsta/asyncmachine-go/tools/debugger/states"
10 | )
11 |
12 | // VisualizerStatesDef contains all the states of the Visualizer state machine.
13 | type VisualizerStatesDef struct {
14 | *am.StatesBase
15 |
16 | // inherit from BasicStatesDef
17 | *ss.BasicStatesDef
18 | // inherit from DisposedStatesDef
19 | *ss.DisposedStatesDef
20 | // inherit from [ssdbg.ServerStatesDef]
21 | *ssdbg.ServerStatesDef
22 | }
23 |
24 | // VisualizerGroupsDef contains all the state groups Visualizer state machine.
25 | type VisualizerGroupsDef struct {
26 | }
27 |
28 | // VisualizerSchema represents all relations and properties of VisualizerStates.
29 | var VisualizerSchema = SchemaMerge(
30 | // inherit from BasicStruct
31 | ss.BasicSchema,
32 | // inherit from DisposedStruct
33 | ss.DisposedSchema,
34 | // inherit from ServerSchema
35 | ssdbg.ServerSchema,
36 | am.Schema{
37 | // TODO own states
38 | })
39 |
40 | // EXPORTS AND GROUPS
41 |
42 | var (
43 | ssV = am.NewStates(VisualizerStatesDef{})
44 | sgV = am.NewStateGroups(VisualizerGroupsDef{})
45 |
46 | // VisualizerStates contains all the states for the Visualizer machine.
47 | VisualizerStates = ssV
48 | // VisualizerGroups contains all the state groups for the Visualizer machine.
49 | VisualizerGroups = sgV
50 | )
51 |
--------------------------------------------------------------------------------
/docs/jsonschema/mutation_req.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json-schema.org/draft/2020-12/schema",
3 | "$id": "https://github.com/pancsta/asyncmachine-go/pkg/integrations/mutation-req",
4 | "$ref": "#/$defs/MutationReq",
5 | "$defs": {
6 | "Kind": {
7 | "properties": {
8 | "Value": {
9 | "type": "string"
10 | }
11 | },
12 | "additionalProperties": false,
13 | "type": "object",
14 | "required": [
15 | "Value"
16 | ]
17 | },
18 | "MutationReq": {
19 | "oneOf": [
20 | {
21 | "required": [
22 | "add"
23 | ],
24 | "title": "add"
25 | },
26 | {
27 | "required": [
28 | "remove"
29 | ],
30 | "title": "remove"
31 | }
32 | ],
33 | "properties": {
34 | "kind": {
35 | "$ref": "#/$defs/Kind",
36 | "description": "The kind of the request."
37 | },
38 | "add": {
39 | "$ref": "#/$defs/S",
40 | "description": "The states to add to the state machine."
41 | },
42 | "remove": {
43 | "$ref": "#/$defs/S",
44 | "description": "The states to remove from the state machine."
45 | },
46 | "args": {
47 | "type": "object",
48 | "description": "Arguments passed to transition handlers."
49 | }
50 | },
51 | "additionalProperties": false,
52 | "type": "object",
53 | "required": [
54 | "kind"
55 | ]
56 | },
57 | "S": {
58 | "items": {
59 | "type": "string"
60 | },
61 | "type": "array"
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/.github/workflows/go-basic.yml:
--------------------------------------------------------------------------------
1 | name: Go Test (basic)
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | go-version: [ '1.23' ]
12 |
13 | steps:
14 | # Get values for cache paths to be used in later steps
15 | - id: go-cache-paths
16 | run: |
17 | echo "::set-output name=go-build::$(go env GOCACHE)"
18 | echo "::set-output name=go-mod::$(go env GOMODCACHE)"
19 |
20 | # set up
21 | - uses: actions/checkout@v4
22 | - uses: actions/setup-go@v4
23 | with:
24 | go-version: ${{ matrix.go-version }}
25 |
26 | # Cache
27 | - name: Clear cache directory first before trying to restore from cache
28 | run: sudo rm -rf $(go env GOMODCACHE) && sudo rm -rf $(go env GOCACHE)
29 | shell: bash
30 | - name: Go Build Cache
31 | uses: actions/cache@v4
32 | with:
33 | path: ${{ steps.go-cache-paths.outputs.go-build }}
34 | key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
35 | - name: Go Mod Cache
36 | uses: actions/cache@v4
37 | with:
38 | path: ${{ steps.go-cache-paths.outputs.go-mod }}
39 | key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
40 | - name: Task Cache
41 | uses: actions/cache@v4
42 | with:
43 | path: ~/.local/bin/task
44 | key: ${{ runner.os }}
45 |
46 | # deps
47 | - name: Install Task
48 | run: ./scripts/dep-taskfile.sh
49 | - name: Install dependencies
50 | run: task install-deps-ci
51 |
52 | # run
53 | - name: Test
54 | run: task test
55 |
--------------------------------------------------------------------------------
/pkg/states/ss_basic.go:
--------------------------------------------------------------------------------
1 | // Package states provides reusable state definitions.
2 | //
3 | // - basic
4 | // - connected
5 | // - disposed
6 | package states
7 |
8 | import (
9 | _ "embed"
10 |
11 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
12 | )
13 |
14 | //go:embed states_utils.go
15 | var StatesUtilsFile string
16 |
17 | // BasicStatesDef contains all the basic states.
18 | type BasicStatesDef struct {
19 | *am.StatesBase
20 |
21 | // ErrNetwork indicates a generic network error.
22 | ErrNetwork string
23 | // ErrHandlerTimeout indicates one of the state machine handlers has timed
24 | // out.
25 | ErrHandlerTimeout string
26 |
27 | // Start indicates the machine should be working. Removing start can force
28 | // stop the machine.
29 | Start string
30 | // Ready indicates the machine meets criteria to perform work.
31 | Ready string
32 | // Healthcheck is a periodic request making sure that the machine is still
33 | // alive.
34 | Healthcheck string
35 | // Heartbeat is a periodic state that ensures the integrity of the machine.
36 | Heartbeat string
37 | }
38 |
39 | var BasicSchema = am.Schema{
40 | // Errors
41 |
42 | ssB.Exception: {Multi: true},
43 | ssB.ErrNetwork: {
44 | Multi: true,
45 | Require: S{Exception},
46 | },
47 | ssB.ErrHandlerTimeout: {
48 | Multi: true,
49 | Require: S{Exception},
50 | },
51 |
52 | // Basics
53 |
54 | ssB.Start: {},
55 | ssB.Ready: {Require: S{ssB.Start}},
56 | ssB.Healthcheck: {Multi: true},
57 | ssB.Heartbeat: {},
58 | }
59 |
60 | // EXPORTS AND GROUPS
61 |
62 | var (
63 | ssB = am.NewStates(BasicStatesDef{})
64 |
65 | // BasicStates contains all the states for the Basic machine.
66 | BasicStates = ssB
67 | )
68 |
--------------------------------------------------------------------------------
/scripts/gen_jsonschema/gen_jsonschema.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 | "path/filepath"
8 |
9 | "github.com/invopop/jsonschema"
10 |
11 | "github.com/pancsta/asyncmachine-go/pkg/integrations"
12 | )
13 |
14 | func main() {
15 | r := new(jsonschema.Reflector)
16 | if err := r.AddGoComments("github.com/pancsta/asyncmachine-go",
17 | "./pkg/integrations"); err != nil {
18 | panic(err)
19 | }
20 |
21 | // Create docs/jsonschema directory if it doesn't exist
22 | schemaDir := filepath.Join("docs", "jsonschema")
23 | if err := os.MkdirAll(schemaDir, 0755); err != nil {
24 | panic(err)
25 | }
26 |
27 | // List of structs to generate schemas for
28 | structs := []struct {
29 | name string
30 | obj interface{}
31 | }{
32 | {"msg_kind_req", &integrations.MsgKindReq{}},
33 | {"msg_kind_resp", &integrations.MsgKindResp{}},
34 | {"mutation_req", &integrations.MutationReq{}},
35 | {"mutation_resp", &integrations.MutationResp{}},
36 | {"waiting_req", &integrations.WaitingReq{}},
37 | {"waiting_resp", &integrations.WaitingResp{}},
38 | {"getter_req", &integrations.GetterReq{}},
39 | {"getter_resp", &integrations.GetterResp{}},
40 | }
41 |
42 | for _, s := range structs {
43 | schema := r.Reflect(s.obj)
44 | jsonData, err := json.MarshalIndent(schema, "", " ")
45 | if err != nil {
46 | panic(fmt.Errorf("failed to marshal schema for %s: %v", s.name, err))
47 | }
48 |
49 | filename := filepath.Join(schemaDir, s.name+".json")
50 | if err := os.WriteFile(filename, jsonData, 0644); err != nil {
51 | panic(fmt.Errorf("failed to write schema for %s: %v", s.name, err))
52 | }
53 |
54 | fmt.Printf("Generated /%s\n", filename)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/.github/workflows/linter.yml:
--------------------------------------------------------------------------------
1 | name: Linter
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | # Disable this job
9 | # if: false
10 |
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | go-version: [ '1.23' ]
15 |
16 | steps:
17 | # Get values for cache paths to be used in later steps
18 | # Using the modern GITHUB_OUTPUT method
19 | - id: go-cache-paths
20 | run: |
21 | echo "go-build=$(go env GOCACHE)" >> $GITHUB_OUTPUT
22 | echo "go-mod=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT
23 |
24 | # set up
25 | - uses: actions/checkout@v4
26 | # TODO no ruby
27 | - uses: ruby/setup-ruby@v1
28 | with:
29 | ruby-version: '3.3'
30 | - uses: actions/setup-go@v4
31 | with:
32 | go-version: ${{ matrix.go-version }}
33 |
34 | - name: GolangCI-Lint Cache
35 | uses: actions/cache@v4
36 | with:
37 | # Default cache path for golangci-lint on Linux
38 | path: ~/.cache/golangci-lint
39 | # Key on go.sum and the linter config file
40 | key: ${{ runner.os }}-golangci-lint-${{ hashFiles('**/go.sum', '.golangci.yml', '.golangci.yaml') }}
41 | restore-keys: |
42 | ${{ runner.os }}-golangci-lint-
43 |
44 | - name: Task Cache
45 | uses: actions/cache@v4
46 | with:
47 | path: ~/.local/bin/task
48 | key: ${{ runner.os }}
49 |
50 | # deps
51 | - name: Install Task
52 | run: ./scripts/dep-taskfile.sh
53 | - name: Install linter dependencies
54 | run: task install-deps-linter
55 |
56 | # run
57 | - name: Run linters
58 | run: task lint-ci
--------------------------------------------------------------------------------
/examples/nfa/states/ss_nfa.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | )
6 |
7 | // NfaStatesDef contains all the states of the Nfa state machine.
8 | type NfaStatesDef struct {
9 | *am.StatesBase
10 |
11 | Start string
12 | Ready string
13 |
14 | Input0 string
15 | Input0Done string
16 | Input1 string
17 | Input1Done string
18 |
19 | StepX string
20 | Step0 string
21 | Step1 string
22 | Step2 string
23 | Step3 string
24 | }
25 |
26 | // NfaGroupsDef contains all the state groups Nfa state machine.
27 | type NfaGroupsDef struct {
28 | Steps S
29 | }
30 |
31 | // NfaSchema represents all relations and properties of NfaStates.
32 | var NfaSchema = am.Schema{
33 |
34 | ssN.Start: {Add: S{ssN.StepX}},
35 | ssN.Ready: {Require: S{ssN.Start}},
36 |
37 | ssN.Input0: {
38 | Multi: true,
39 | Require: S{ssN.Start},
40 | },
41 | ssN.Input0Done: {
42 | Multi: true,
43 | Require: S{ssN.Start},
44 | },
45 | ssN.Input1: {
46 | Multi: true,
47 | Require: S{ssN.Start},
48 | },
49 | ssN.Input1Done: {
50 | Multi: true,
51 | Require: S{ssN.Start},
52 | },
53 |
54 | ssN.StepX: {Remove: sgN.Steps},
55 | ssN.Step0: {Remove: sgN.Steps},
56 | ssN.Step1: {Remove: sgN.Steps},
57 | ssN.Step2: {Remove: sgN.Steps},
58 | ssN.Step3: {Remove: sgN.Steps},
59 | }
60 |
61 | // EXPORTS AND GROUPS
62 |
63 | var (
64 | ssN = am.NewStates(NfaStatesDef{})
65 | sgN = am.NewStateGroups(NfaGroupsDef{
66 | Steps: S{ssN.StepX, ssN.Step0, ssN.Step1, ssN.Step2, ssN.Step3},
67 | })
68 |
69 | // NfaStates contains all the states for the Nfa machine.
70 | NfaStates = ssN
71 | // NfaGroups contains all the state groups for the Nfa machine.
72 | NfaGroups = sgN
73 | )
74 |
--------------------------------------------------------------------------------
/.github/workflows/go-tools.yml:
--------------------------------------------------------------------------------
1 | name: Go Test /tools
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | go-version: [ '1.23' ]
12 |
13 | steps:
14 | # Get values for cache paths to be used in later steps
15 | - id: go-cache-paths
16 | run: |
17 | echo "::set-output name=go-build::$(go env GOCACHE)"
18 | echo "::set-output name=go-mod::$(go env GOMODCACHE)"
19 |
20 | # set up
21 | - uses: actions/checkout@v4
22 | - uses: actions/setup-go@v4
23 | with:
24 | go-version: ${{ matrix.go-version }}
25 |
26 | # Cache
27 | - name: Clear cache directory first before trying to restore from cache
28 | run: sudo rm -rf $(go env GOMODCACHE) && sudo rm -rf $(go env GOCACHE)
29 | shell: bash
30 | - name: Go Build Cache
31 | uses: actions/cache@v4
32 | with:
33 | path: ${{ steps.go-cache-paths.outputs.go-build }}
34 | key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
35 | - name: Go Mod Cache
36 | uses: actions/cache@v4
37 | with:
38 | path: ${{ steps.go-cache-paths.outputs.go-mod }}
39 | key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
40 | - name: Task Cache
41 | uses: actions/cache@v4
42 | with:
43 | path: ~/.local/bin/task
44 | key: ${{ runner.os }}
45 |
46 | # deps
47 | - name: Install Task
48 | run: ./scripts/dep-taskfile.sh
49 | - name: Install dependencies
50 | run: task install-deps-ci
51 |
52 | # run
53 |
54 | - name: Test /tools
55 | run: task test-path -- ./tools/...
56 |
--------------------------------------------------------------------------------
/.github/workflows/go-node.yml:
--------------------------------------------------------------------------------
1 | name: Go Test /pkg/node
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | go-version: [ '1.23' ]
12 |
13 | steps:
14 | # Get values for cache paths to be used in later steps
15 | - id: go-cache-paths
16 | run: |
17 | echo "::set-output name=go-build::$(go env GOCACHE)"
18 | echo "::set-output name=go-mod::$(go env GOMODCACHE)"
19 |
20 | # set up
21 | - uses: actions/checkout@v4
22 | - uses: actions/setup-go@v4
23 | with:
24 | go-version: ${{ matrix.go-version }}
25 |
26 | # Cache
27 | - name: Clear cache directory first before trying to restore from cache
28 | run: sudo rm -rf $(go env GOMODCACHE) && sudo rm -rf $(go env GOCACHE)
29 | shell: bash
30 | - name: Go Build Cache
31 | uses: actions/cache@v4
32 | with:
33 | path: ${{ steps.go-cache-paths.outputs.go-build }}
34 | key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
35 | - name: Go Mod Cache
36 | uses: actions/cache@v4
37 | with:
38 | path: ${{ steps.go-cache-paths.outputs.go-mod }}
39 | key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
40 | - name: Task Cache
41 | uses: actions/cache@v4
42 | with:
43 | path: ~/.local/bin/task
44 | key: ${{ runner.os }}
45 |
46 | # deps
47 | - name: Install Task
48 | run: ./scripts/dep-taskfile.sh
49 | - name: Install dependencies
50 | run: task install-deps-ci
51 |
52 | # run
53 |
54 | - name: Test /pkg/node
55 | run: task test-path -- ./pkg/node/...
56 |
--------------------------------------------------------------------------------
/.github/workflows/go-rpc.yml:
--------------------------------------------------------------------------------
1 | name: Go Test /pkg/rpc
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | go-version: [ '1.23' ]
12 |
13 | steps:
14 | # Get values for cache paths to be used in later steps
15 | - id: go-cache-paths
16 | run: |
17 | echo "::set-output name=go-build::$(go env GOCACHE)"
18 | echo "::set-output name=go-mod::$(go env GOMODCACHE)"
19 |
20 | # set up
21 | - uses: actions/checkout@v4
22 | - uses: actions/setup-go@v4
23 | with:
24 | go-version: ${{ matrix.go-version }}
25 |
26 | # Cache
27 | - name: Clear cache directory first before trying to restore from cache
28 | run: sudo rm -rf $(go env GOMODCACHE) && sudo rm -rf $(go env GOCACHE)
29 | shell: bash
30 | - name: Go Build Cache
31 | uses: actions/cache@v4
32 | with:
33 | path: ${{ steps.go-cache-paths.outputs.go-build }}
34 | key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
35 | - name: Go Mod Cache
36 | uses: actions/cache@v4
37 | with:
38 | path: ${{ steps.go-cache-paths.outputs.go-mod }}
39 | key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
40 | - name: Task Cache
41 | uses: actions/cache@v4
42 | with:
43 | path: ~/.local/bin/task
44 | key: ${{ runner.os }}
45 |
46 | # deps
47 | - name: Install Task
48 | run: ./scripts/dep-taskfile.sh
49 | - name: Install dependencies
50 | run: task install-deps-ci
51 |
52 | # run
53 |
54 | - name: Test /pkg/rpc
55 | run: task test-path -- ./pkg/rpc/...
56 |
--------------------------------------------------------------------------------
/.github/workflows/go-pubsub.yml:
--------------------------------------------------------------------------------
1 | name: Go Test /pkg/pubsub
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | go-version: [ '1.23' ]
12 |
13 | steps:
14 | # Get values for cache paths to be used in later steps
15 | - id: go-cache-paths
16 | run: |
17 | echo "::set-output name=go-build::$(go env GOCACHE)"
18 | echo "::set-output name=go-mod::$(go env GOMODCACHE)"
19 |
20 | # set up
21 | - uses: actions/checkout@v4
22 | - uses: actions/setup-go@v4
23 | with:
24 | go-version: ${{ matrix.go-version }}
25 |
26 | # Cache
27 | - name: Clear cache directory first before trying to restore from cache
28 | run: sudo rm -rf $(go env GOMODCACHE) && sudo rm -rf $(go env GOCACHE)
29 | shell: bash
30 | - name: Go Build Cache
31 | uses: actions/cache@v4
32 | with:
33 | path: ${{ steps.go-cache-paths.outputs.go-build }}
34 | key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
35 | - name: Go Mod Cache
36 | uses: actions/cache@v4
37 | with:
38 | path: ${{ steps.go-cache-paths.outputs.go-mod }}
39 | key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
40 | - name: Task Cache
41 | uses: actions/cache@v4
42 | with:
43 | path: ~/.local/bin/task
44 | key: ${{ runner.os }}
45 |
46 | # deps
47 | - name: Install Task
48 | run: ./scripts/dep-taskfile.sh
49 | - name: Install dependencies
50 | run: task install-deps-ci
51 |
52 | # run
53 |
54 | - name: Test /pkg/pubsub
55 | run: task test-path -- ./pkg/pubsub/...
56 |
--------------------------------------------------------------------------------
/.github/workflows/go-states.yml:
--------------------------------------------------------------------------------
1 | name: Go Test /pkg/states
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | go-version: [ '1.23' ]
12 |
13 | steps:
14 | # Get values for cache paths to be used in later steps
15 | - id: go-cache-paths
16 | run: |
17 | echo "::set-output name=go-build::$(go env GOCACHE)"
18 | echo "::set-output name=go-mod::$(go env GOMODCACHE)"
19 |
20 | # set up
21 | - uses: actions/checkout@v4
22 | - uses: actions/setup-go@v4
23 | with:
24 | go-version: ${{ matrix.go-version }}
25 |
26 | # Cache
27 | - name: Clear cache directory first before trying to restore from cache
28 | run: sudo rm -rf $(go env GOMODCACHE) && sudo rm -rf $(go env GOCACHE)
29 | shell: bash
30 | - name: Go Build Cache
31 | uses: actions/cache@v4
32 | with:
33 | path: ${{ steps.go-cache-paths.outputs.go-build }}
34 | key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
35 | - name: Go Mod Cache
36 | uses: actions/cache@v4
37 | with:
38 | path: ${{ steps.go-cache-paths.outputs.go-mod }}
39 | key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
40 | - name: Task Cache
41 | uses: actions/cache@v4
42 | with:
43 | path: ~/.local/bin/task
44 | key: ${{ runner.os }}
45 |
46 | # deps
47 | - name: Install Task
48 | run: ./scripts/dep-taskfile.sh
49 | - name: Install dependencies
50 | run: task install-deps-ci
51 |
52 | # run
53 |
54 | - name: Test /pkg/states
55 | run: task test-path -- ./pkg/states/...
56 |
--------------------------------------------------------------------------------
/.github/workflows/go-helpers.yml:
--------------------------------------------------------------------------------
1 | name: Go Test /pkg/helpers
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | go-version: [ '1.23' ]
12 |
13 | steps:
14 | # Get values for cache paths to be used in later steps
15 | - id: go-cache-paths
16 | run: |
17 | echo "::set-output name=go-build::$(go env GOCACHE)"
18 | echo "::set-output name=go-mod::$(go env GOMODCACHE)"
19 |
20 | # set up
21 | - uses: actions/checkout@v4
22 | - uses: actions/setup-go@v4
23 | with:
24 | go-version: ${{ matrix.go-version }}
25 |
26 | # Cache
27 | - name: Clear cache directory first before trying to restore from cache
28 | run: sudo rm -rf $(go env GOMODCACHE) && sudo rm -rf $(go env GOCACHE)
29 | shell: bash
30 | - name: Go Build Cache
31 | uses: actions/cache@v4
32 | with:
33 | path: ${{ steps.go-cache-paths.outputs.go-build }}
34 | key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
35 | - name: Go Mod Cache
36 | uses: actions/cache@v4
37 | with:
38 | path: ${{ steps.go-cache-paths.outputs.go-mod }}
39 | key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
40 | - name: Task Cache
41 | uses: actions/cache@v4
42 | with:
43 | path: ~/.local/bin/task
44 | key: ${{ runner.os }}
45 |
46 | # deps
47 | - name: Install Task
48 | run: ./scripts/dep-taskfile.sh
49 | - name: Install dependencies
50 | run: task install-deps-ci
51 |
52 | # run
53 |
54 | - name: Test /pkg/helpers
55 | run: task test-path -- ./pkg/helpers/...
56 |
--------------------------------------------------------------------------------
/.github/workflows/go-history.yml:
--------------------------------------------------------------------------------
1 | name: Go Test /pkg/history
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | go-version: [ '1.23' ]
12 |
13 | steps:
14 | # Get values for cache paths to be used in later steps
15 | - id: go-cache-paths
16 | run: |
17 | echo "::set-output name=go-build::$(go env GOCACHE)"
18 | echo "::set-output name=go-mod::$(go env GOMODCACHE)"
19 |
20 | # set up
21 | - uses: actions/checkout@v4
22 | - uses: actions/setup-go@v4
23 | with:
24 | go-version: ${{ matrix.go-version }}
25 |
26 | # Cache
27 | - name: Clear cache directory first before trying to restore from cache
28 | run: sudo rm -rf $(go env GOMODCACHE) && sudo rm -rf $(go env GOCACHE)
29 | shell: bash
30 | - name: Go Build Cache
31 | uses: actions/cache@v4
32 | with:
33 | path: ${{ steps.go-cache-paths.outputs.go-build }}
34 | key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
35 | - name: Go Mod Cache
36 | uses: actions/cache@v4
37 | with:
38 | path: ${{ steps.go-cache-paths.outputs.go-mod }}
39 | key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
40 | - name: Task Cache
41 | uses: actions/cache@v4
42 | with:
43 | path: ~/.local/bin/task
44 | key: ${{ runner.os }}
45 |
46 | # deps
47 | - name: Install Task
48 | run: ./scripts/dep-taskfile.sh
49 | - name: Install dependencies
50 | run: task install-deps-ci
51 |
52 | # run
53 |
54 | - name: Test /pkg/history
55 | run: task test-path -- ./pkg/history/...
56 |
--------------------------------------------------------------------------------
/.github/workflows/go-machine.yml:
--------------------------------------------------------------------------------
1 | name: Go Test /pkg/machine
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | go-version: [ '1.23' ]
12 |
13 | steps:
14 | # Get values for cache paths to be used in later steps
15 | - id: go-cache-paths
16 | run: |
17 | echo "::set-output name=go-build::$(go env GOCACHE)"
18 | echo "::set-output name=go-mod::$(go env GOMODCACHE)"
19 |
20 | # set up
21 | - uses: actions/checkout@v4
22 | - uses: actions/setup-go@v4
23 | with:
24 | go-version: ${{ matrix.go-version }}
25 |
26 | # Cache
27 | - name: Clear cache directory first before trying to restore from cache
28 | run: sudo rm -rf $(go env GOMODCACHE) && sudo rm -rf $(go env GOCACHE)
29 | shell: bash
30 | - name: Go Build Cache
31 | uses: actions/cache@v4
32 | with:
33 | path: ${{ steps.go-cache-paths.outputs.go-build }}
34 | key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
35 | - name: Go Mod Cache
36 | uses: actions/cache@v4
37 | with:
38 | path: ${{ steps.go-cache-paths.outputs.go-mod }}
39 | key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
40 | - name: Task Cache
41 | uses: actions/cache@v4
42 | with:
43 | path: ~/.local/bin/task
44 | key: ${{ runner.os }}
45 |
46 | # deps
47 | - name: Install Task
48 | run: ./scripts/dep-taskfile.sh
49 | - name: Install dependencies
50 | run: task install-deps-ci
51 |
52 | # run
53 |
54 | - name: Test /pkg/machine
55 | run: task test-path -- ./pkg/machine/...
56 |
--------------------------------------------------------------------------------
/.github/workflows/go-telemetry.yml:
--------------------------------------------------------------------------------
1 | name: Go Test /pkg/telemetry
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 | strategy:
10 | matrix:
11 | go-version: [ '1.23' ]
12 |
13 | steps:
14 | # Get values for cache paths to be used in later steps
15 | - id: go-cache-paths
16 | run: |
17 | echo "::set-output name=go-build::$(go env GOCACHE)"
18 | echo "::set-output name=go-mod::$(go env GOMODCACHE)"
19 |
20 | # set up
21 | - uses: actions/checkout@v4
22 | - uses: actions/setup-go@v4
23 | with:
24 | go-version: ${{ matrix.go-version }}
25 |
26 | # Cache
27 | - name: Clear cache directory first before trying to restore from cache
28 | run: sudo rm -rf $(go env GOMODCACHE) && sudo rm -rf $(go env GOCACHE)
29 | shell: bash
30 | - name: Go Build Cache
31 | uses: actions/cache@v4
32 | with:
33 | path: ${{ steps.go-cache-paths.outputs.go-build }}
34 | key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}
35 | - name: Go Mod Cache
36 | uses: actions/cache@v4
37 | with:
38 | path: ${{ steps.go-cache-paths.outputs.go-mod }}
39 | key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
40 | - name: Task Cache
41 | uses: actions/cache@v4
42 | with:
43 | path: ~/.local/bin/task
44 | key: ${{ runner.os }}
45 |
46 | # deps
47 | - name: Install Task
48 | run: ./scripts/dep-taskfile.sh
49 | - name: Install dependencies
50 | run: task install-deps-ci
51 |
52 | # run
53 |
54 | - name: Test /pkg/telemetry
55 | run: task test-path -- ./pkg/telemetry/...
56 |
--------------------------------------------------------------------------------
/examples/path_watcher/states/ss_watcher.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import am "github.com/pancsta/asyncmachine-go/pkg/machine"
4 |
5 | // S is a type alias for a list of state names.
6 | type S = am.S
7 |
8 | // States map defines relations and properties of states (for files).
9 | var States = am.Schema{
10 | Init: {
11 | Add: S{Watching},
12 | },
13 | Watching: {
14 | Add: S{Init},
15 | After: S{Init},
16 | },
17 | ChangeEvent: {
18 | Multi: true,
19 | Require: S{Watching},
20 | },
21 | Refreshing: {
22 | Multi: true,
23 | Remove: S{AllRefreshed},
24 | },
25 | Refreshed: {
26 | Multi: true,
27 | },
28 | AllRefreshed: {},
29 | }
30 |
31 | // StatesDir map defines relations and properties of states (for directories).
32 | var StatesDir = am.Schema{
33 | Refreshing: {
34 | Remove: groupRefreshed,
35 | },
36 | Refreshed: {
37 | Remove: groupRefreshed,
38 | },
39 | DirDebounced: {
40 | Remove: groupRefreshed,
41 | },
42 | DirCached: {},
43 | }
44 |
45 | // Groups of mutually exclusive states.
46 |
47 | var groupRefreshed = S{Refreshing, Refreshed, DirDebounced}
48 |
49 | // #region boilerplate defs
50 |
51 | // Names of all the states (pkg enum).
52 |
53 | const (
54 | Init = "Init"
55 | Watching = "Watching"
56 | ChangeEvent = "ChangeEvent"
57 | Refreshing = "Refreshing"
58 | Refreshed = "Refreshed"
59 | AllRefreshed = "AllRefreshed"
60 |
61 | // dir-only states
62 |
63 | DirDebounced = "DirDebounced"
64 | DirCached = "DirCached"
65 | )
66 |
67 | // Names is an ordered list of all the state names for files.
68 | var Names = S{Init, Watching, ChangeEvent, Refreshing, Refreshed, AllRefreshed}
69 |
70 | // NamesDir is an ordered list of all the state names for directories.
71 | var NamesDir = S{Refreshing, Refreshed, DirDebounced, DirCached}
72 |
73 | // #endregion
74 |
--------------------------------------------------------------------------------
/examples/pipes/example_pipes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/joho/godotenv"
8 |
9 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
10 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
11 | ampipe "github.com/pancsta/asyncmachine-go/pkg/states/pipes"
12 | )
13 |
14 | func init() {
15 | // load .env
16 | _ = godotenv.Load()
17 |
18 | // am-dbg is required for debugging, go run it
19 | // go run github.com/pancsta/asyncmachine-go/tools/cmd/am-dbg@latest
20 | // amhelp.EnableDebugging(false)
21 | // amhelp.SetEnvLogLevel(am.LogOps)
22 | }
23 |
24 | func main() {
25 | ctx := context.Background()
26 |
27 | // init state machines
28 | mach1 := am.New(ctx, am.Schema{
29 | "Ready": {},
30 | "Foo": {},
31 | "Bar": {},
32 | "Custom": {},
33 | "Healthcheck": {Multi: true},
34 | }, &am.Opts{LogLevel: am.LogOps, Id: "source"})
35 | mach2 := am.New(ctx, am.Schema{
36 | "Ready": {},
37 | "Custom": {},
38 | "Healthcheck": {Multi: true},
39 | }, &am.Opts{LogLevel: am.LogOps, Id: "destination"})
40 | amhelp.MachDebugEnv(mach1)
41 | amhelp.MachDebugEnv(mach2)
42 |
43 | // pipe conventional states
44 | err := ampipe.BindReady(mach1, mach2, "", "")
45 | if err != nil {
46 | panic(err)
47 | }
48 | err = ampipe.BindErr(mach1, mach2, "")
49 | if err != nil {
50 | panic(err)
51 | }
52 |
53 | // pipe custom states (anon handlers)
54 | pipeCustom := &struct {
55 | CustomState am.HandlerFinal
56 | CustomEnd am.HandlerFinal
57 | }{
58 | CustomState: ampipe.Add(mach1, mach2, "Custom", ""),
59 | CustomEnd: ampipe.Remove(mach1, mach2, "Custom", ""),
60 | }
61 |
62 | // bind and handle dispose
63 | if err := mach1.BindHandlers(pipeCustom); err != nil {
64 | panic(err)
65 | }
66 |
67 | // debug
68 | time.Sleep(time.Second)
69 | }
70 |
--------------------------------------------------------------------------------
/tools/generator/states/ss_generator.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | . "github.com/pancsta/asyncmachine-go/pkg/states/global"
6 | )
7 |
8 | // GeneratorStatesDef contains all the states of the Generator state machine.
9 | type GeneratorStatesDef struct {
10 | *am.StatesBase
11 |
12 | // pkg/states
13 | InheritBasic string
14 | InheritConnected string
15 | InheritDisposed string
16 |
17 | // pkg/*
18 | InheritRpcWorker string
19 | InheritNodeWorker string
20 |
21 | // rest
22 | Inherit string
23 | GroupsLocal string
24 | GroupsInherited string
25 | Groups string
26 | }
27 |
28 | // GeneratorGroupsDef contains all the state groups of the Generator state
29 | // machine.
30 | type GeneratorGroupsDef struct {
31 | Inherit S
32 | }
33 |
34 | // GeneratorSchema represents all relations and properties of GeneratorStates.
35 | var GeneratorSchema = am.Schema{
36 | ssG.InheritBasic: {},
37 | ssG.InheritConnected: {Add: S{ssG.GroupsInherited}},
38 | ssG.InheritDisposed: {},
39 |
40 | ssG.InheritRpcWorker: {},
41 | ssG.InheritNodeWorker: {
42 | Add: S{ssG.GroupsInherited},
43 | Remove: S{ssG.InheritRpcWorker},
44 | },
45 |
46 | ssG.Inherit: {Auto: true},
47 | ssG.GroupsLocal: {},
48 | ssG.GroupsInherited: {},
49 | ssG.Groups: {Auto: true},
50 | }
51 |
52 | // EXPORTS AND GROUPS
53 |
54 | var (
55 | ssG = am.NewStates(GeneratorStatesDef{})
56 | sgG = am.NewStateGroups(GeneratorGroupsDef{
57 | Inherit: S{ssG.InheritBasic, ssG.InheritConnected, ssG.InheritRpcWorker,
58 | ssG.InheritNodeWorker, ssG.InheritDisposed},
59 | })
60 |
61 | // GeneratorStates contains all the states for the Generator machine.
62 | GeneratorStates = ssG
63 | // GeneratorGroups contains all the state groups for the Generator machine.
64 | GeneratorGroups = sgG
65 | )
66 |
--------------------------------------------------------------------------------
/config/dashboards/dash-full.kdl:
--------------------------------------------------------------------------------
1 | keybinds clear-defaults=true {
2 | locked {
3 | // navi
4 | bind "Alt Left" {
5 | MoveFocusOrTab "Left"; }
6 | bind "Alt Right" {
7 | MoveFocusOrTab "Right"; }
8 | bind "Alt Down" {
9 | MoveFocus "Down"; }
10 | bind "Alt Up" {
11 | MoveFocus "Up"; }
12 | bind "Alt =" {
13 | Resize "Increase"; }
14 |
15 | // resize
16 | bind "Alt =" "Alt +" {
17 | Resize "Increase"; }
18 | bind "Alt -" {
19 | Resize "Decrease"; }
20 |
21 |
22 | bind "Ctrl q" {
23 | Quit ; }
24 | }
25 | }
26 |
27 | pane_frames false
28 | copy_on_select false
29 | pane_frames false
30 | ui {
31 | pane_frames {
32 | hide_session_name true
33 | rounded_corners true
34 | }
35 | }
36 | show_release_notes false
37 | show_startup_tips false
38 |
39 | layout {
40 | pane split_direction="vertical" {
41 |
42 | pane size="50%" command="sh" {
43 | args "-c" "sleep 2 && task am-dbg -- --dir tmp -l localhost:6831 --fwd-data localhost:6832,localhost:6833,localhost:6834 --output-diagrams 3 --tail --output-clients"
44 | }
45 |
46 | pane size="50%" split_direction="horizontal" {
47 | pane size="50%" split_direction="vertical" {
48 | pane size="50%" command="sh" {
49 | args "-c" "task am-dbg -- -l localhost:6832 --view-narrow --view-timelines 0 --view matrix --tail"
50 | }
51 | pane size="50%" command="sh" {
52 | args "-c" "task am-dbg -- -l localhost:6833 --view-narrow --view-timelines 0 --view-rain --view matrix --tail"
53 | }
54 | }
55 | pane size="50%" command="sh" {
56 | args "-c" "task am-dbg -- -l localhost:6834 --view-narrow --view-timelines 0 --tail --view-timelines 1 --view-reader"
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/config/dashboards/dash-wide-narrow-repl.kdl:
--------------------------------------------------------------------------------
1 | keybinds clear-defaults=true {
2 | locked {
3 | // navi
4 | bind "Alt Left" {
5 | MoveFocusOrTab "Left"; }
6 | bind "Alt Right" {
7 | MoveFocusOrTab "Right"; }
8 | bind "Alt Down" {
9 | MoveFocus "Down"; }
10 | bind "Alt Up" {
11 | MoveFocus "Up"; }
12 | bind "Alt =" {
13 | Resize "Increase"; }
14 |
15 | // resize
16 | bind "Alt =" "Alt +" {
17 | Resize "Increase"; }
18 | bind "Alt -" {
19 | Resize "Decrease"; }
20 |
21 |
22 | bind "Ctrl q" {
23 | Quit ; }
24 | }
25 | }
26 |
27 | pane_frames false
28 | copy_on_select false
29 | pane_frames false
30 | ui {
31 | pane_frames {
32 | hide_session_name true
33 | rounded_corners true
34 | }
35 | }
36 | show_release_notes false
37 | show_startup_tips false
38 |
39 | layout {
40 | pane split_direction="vertical" {
41 | pane size="75%" split_direction="horizontal" {
42 | pane size="85%" command="sh" {
43 | // args "-c" "sleep 2 && task am-dbg -- --dir tmp -l localhost:6831 --fwd-data localhost:6832 --output-diagrams 3 --tail --output-clients"
44 | // args "-c" "sleep 2 && task am-dbg -- --dir tmp -l localhost:6831 --fwd-data localhost:6832 --output-diagrams 3 --tail --output-clients --import-data am-dbg-dump.gob.br"
45 | args "-c" "sleep 2 && task am-dbg -- --dir tmp -l localhost:6831 --fwd-data localhost:6832 --output-diagrams 3 --tail --output-clients --am-dbg-addr localhost:9913"
46 | }
47 |
48 | pane size="15%" command="sh" {
49 | args "-c" "task arpc -- -d $AM_REPL_DIR --watch"
50 | // args "-c" "task arpc -- -d $AM_REPL_DIR --watch --am-dbg-addr localhost:9913 --log-level 2"
51 | }
52 | }
53 |
54 | pane size="25%" command="sh" {
55 | args "-c" "task am-dbg -- -l localhost:6832 --view-narrow --view-timelines 0 --tail --view-timelines 1"
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/docs/jsonschema/waiting_req.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json-schema.org/draft/2020-12/schema",
3 | "$id": "https://github.com/pancsta/asyncmachine-go/pkg/integrations/waiting-req",
4 | "$ref": "#/$defs/WaitingReq",
5 | "$defs": {
6 | "Kind": {
7 | "properties": {
8 | "Value": {
9 | "type": "string"
10 | }
11 | },
12 | "additionalProperties": false,
13 | "type": "object",
14 | "required": [
15 | "Value"
16 | ]
17 | },
18 | "S": {
19 | "items": {
20 | "type": "string"
21 | },
22 | "type": "array"
23 | },
24 | "Time": {
25 | "items": {
26 | "type": "integer"
27 | },
28 | "type": "array"
29 | },
30 | "WaitingReq": {
31 | "oneOf": [
32 | {
33 | "required": [
34 | "states"
35 | ],
36 | "title": "states"
37 | },
38 | {
39 | "required": [
40 | "states_not"
41 | ],
42 | "title": "statesNot"
43 | }
44 | ],
45 | "properties": {
46 | "kind": {
47 | "$ref": "#/$defs/Kind",
48 | "description": "The kind of the request."
49 | },
50 | "states": {
51 | "$ref": "#/$defs/S",
52 | "description": "The states to wait for, the default is to all states being active simultaneously (if no time passed)."
53 | },
54 | "states_not": {
55 | "$ref": "#/$defs/S",
56 | "description": "The states names to wait for to be inactive. Ignores the Time field."
57 | },
58 | "time": {
59 | "$ref": "#/$defs/Time",
60 | "description": "The specific (minimal) time to wait for."
61 | }
62 | },
63 | "additionalProperties": false,
64 | "type": "object",
65 | "required": [
66 | "kind"
67 | ]
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/tools/cmd/arpc/cmd_arpc.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "os"
6 | "os/signal"
7 | "time"
8 |
9 | "github.com/pancsta/asyncmachine-go/internal/utils"
10 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
11 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
12 | "github.com/pancsta/asyncmachine-go/tools/repl"
13 | "github.com/pancsta/asyncmachine-go/tools/repl/states"
14 | )
15 |
16 | var ss = states.ReplStates
17 | var randomId = false
18 |
19 | func init() {
20 | // amhelp.EnableDebugging(false)
21 | // amhelp.EnableDebugging(true)
22 | }
23 |
24 | type S = am.S
25 | type T = am.Time
26 |
27 | func main() {
28 | ctx := context.Background()
29 |
30 | var cliArgs []string
31 | var connArgs []string
32 |
33 | osArgs := os.Args[1:]
34 | for i, v := range osArgs {
35 | if v == "--" && len(os.Args) > i+1 {
36 | cliArgs = os.Args[i+2:]
37 | connArgs = os.Args[1 : i+1]
38 | break
39 | }
40 | }
41 |
42 | // repl
43 | id := "repl"
44 | if randomId {
45 | id = "repl-" + utils.RandId(4)
46 | }
47 | r, err := repl.New(ctx, id)
48 | if err != nil {
49 | panic(err)
50 | }
51 | rootCmd := repl.NewRootCommand(r, cliArgs, osArgs)
52 | if len(connArgs) > 0 {
53 | rootCmd.SetArgs(connArgs)
54 | }
55 | r.Cmd = rootCmd
56 |
57 | // cobra
58 | err = rootCmd.Execute()
59 | if err != nil {
60 | if amhelp.IsTelemetry() {
61 | time.Sleep(time.Second)
62 | os.Exit(1)
63 | }
64 | }
65 |
66 | sigCh := make(chan os.Signal, 1)
67 | signal.Notify(sigCh, os.Interrupt)
68 |
69 | // waits
70 | select {
71 |
72 | // signal
73 | case <-sigCh:
74 | r.Mach.Add1(ss.Disposing, nil)
75 | // CLI
76 | case <-r.Mach.WhenNot1(ss.ReplMode, nil):
77 | // exit
78 | case <-r.Mach.When1(ss.Disposed, nil):
79 | }
80 |
81 | // fmt.Println("bye")
82 | r.Mach.Dispose()
83 |
84 | if amhelp.IsTelemetry() {
85 | time.Sleep(time.Second)
86 | }
87 |
88 | os.Exit(0)
89 | }
90 |
--------------------------------------------------------------------------------
/docs/jsonschema/waiting_resp.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json-schema.org/draft/2020-12/schema",
3 | "$id": "https://github.com/pancsta/asyncmachine-go/pkg/integrations/waiting-resp",
4 | "$ref": "#/$defs/WaitingResp",
5 | "$defs": {
6 | "Kind": {
7 | "properties": {
8 | "Value": {
9 | "type": "string"
10 | }
11 | },
12 | "additionalProperties": false,
13 | "type": "object",
14 | "required": [
15 | "Value"
16 | ]
17 | },
18 | "S": {
19 | "items": {
20 | "type": "string"
21 | },
22 | "type": "array"
23 | },
24 | "Time": {
25 | "items": {
26 | "type": "integer"
27 | },
28 | "type": "array"
29 | },
30 | "WaitingResp": {
31 | "oneOf": [
32 | {
33 | "required": [
34 | "states",
35 | "states_not"
36 | ],
37 | "title": "states"
38 | }
39 | ],
40 | "properties": {
41 | "kind": {
42 | "$ref": "#/$defs/Kind",
43 | "description": "The kind of the response."
44 | },
45 | "mach_id": {
46 | "type": "string",
47 | "description": "The ID of the state machine."
48 | },
49 | "states": {
50 | "$ref": "#/$defs/S",
51 | "description": "The active states waited for. If time is empty, all these states are active simultaneously."
52 | },
53 | "states_not": {
54 | "$ref": "#/$defs/S",
55 | "description": "The inactive states waited for."
56 | },
57 | "time": {
58 | "$ref": "#/$defs/Time",
59 | "description": "The requested machine time (the current one may be higher)."
60 | }
61 | },
62 | "additionalProperties": false,
63 | "type": "object",
64 | "required": [
65 | "kind",
66 | "mach_id"
67 | ]
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/examples/benchmark_grpc/states/ss_worker.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | ssrpc "github.com/pancsta/asyncmachine-go/pkg/rpc/states"
6 | ssam "github.com/pancsta/asyncmachine-go/pkg/states"
7 | )
8 |
9 | // WorkerStatesDef contains all the states of the Worker state machine.
10 | type WorkerStatesDef struct {
11 | *am.StatesBase
12 |
13 | Event string
14 | Value1 string
15 | Value2 string
16 | Value3 string
17 | CallOp string
18 |
19 | // inherit from BasicSchema
20 | *ssam.BasicStatesDef
21 | // inherit from WorkerStatesDef
22 | *ssrpc.WorkerStatesDef
23 | }
24 |
25 | // WorkerGroupsDef contains all the state groups of the Worker state machine.
26 | type WorkerGroupsDef struct {
27 |
28 | // Values group contains mutually exclusive values.
29 | Values S
30 | }
31 |
32 | // WorkerSchema represents all relations and properties of WorkerStates.
33 | var WorkerSchema = SchemaMerge(
34 | // inherit from BasicSchema
35 | ssam.BasicSchema,
36 | // inherit from WorkerSchema
37 | ssrpc.WorkerSchema,
38 | am.Schema{
39 |
40 | // ops
41 | ws.CallOp: {
42 | Multi: true,
43 | Require: S{ws.Start},
44 | },
45 |
46 | // events
47 | ws.Event: {
48 | Multi: true,
49 | Require: S{ws.Start},
50 | },
51 |
52 | // values
53 | ws.Value1: {Remove: wg.Values},
54 | ws.Value2: {Remove: wg.Values},
55 | ws.Value3: {Remove: wg.Values},
56 | })
57 |
58 | // EXPORTS AND GROUPS
59 |
60 | var (
61 | // ws is worker states from WorkerStatesDef.
62 | ws = am.NewStates(WorkerStatesDef{})
63 |
64 | // wg is worker groups from WorkerGroupsDef.
65 | wg = am.NewStateGroups(WorkerGroupsDef{
66 | Values: S{ws.Value1, ws.Value2, ws.Value3},
67 | })
68 |
69 | // WorkerStates contains all the states for the Worker machine.
70 | WorkerStates = ws
71 |
72 | // WorkerGroups contains all the state groups for the Worker machine.
73 | WorkerGroups = wg
74 | )
75 |
--------------------------------------------------------------------------------
/pkg/x/history/frostdb/frostdb_test.go:
--------------------------------------------------------------------------------
1 | package frostdb
2 |
3 | import (
4 | "context"
5 | "testing"
6 | "time"
7 |
8 | "github.com/stretchr/testify/require"
9 |
10 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
11 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
12 | )
13 |
14 | func TestFrostdbTrack(t *testing.T) {
15 | if amhelp.IsTestRunner() {
16 | t.Skip("no experimental in test runner")
17 | }
18 |
19 | // Create memory storage
20 | ctx := context.Background()
21 | mem, err := NewMemory(ctx)
22 | require.NoError(t, err)
23 |
24 | // Create a machine with ss states
25 | mach := am.New(ctx, BasicSchema, &am.Opts{Id: "MyMach1"})
26 | onErr := func(err error) {
27 | t.Error(err)
28 | }
29 |
30 | // TODO NewCommon, DebugMachEnv
31 | tr, err := mem.Track(onErr, mach, nil, nil, 10^6)
32 | require.NoError(t, err)
33 |
34 | rounds := 500
35 | start := time.Now()
36 |
37 | for i := range rounds {
38 | if i%100 == 0 {
39 | t.Logf("i: %d", i)
40 | }
41 | mach.Toggle1(ssB.Start, nil)
42 | }
43 | t.Logf("elapsed1: %s", time.Since(start))
44 |
45 | // wait for the Tracer to finish
46 | require.Eventually(t, func() bool {
47 | return tr.Active.Load() == 0
48 | }, time.Second*10, time.Millisecond*100, "too slow")
49 |
50 | t.Logf("elapsed2: %s", time.Since(start))
51 |
52 | err = tr.db.Snapshot(ctx)
53 | require.NoError(t, err)
54 | err = tr.db.Close()
55 | require.NoError(t, err)
56 |
57 | // // Create a new query engine to retrieve data and print the results
58 | // engine := query.NewEngine(memory.DefaultAllocator,
59 | // database.TableProvider())
60 | // _ = engine.ScanTable("simple_table").
61 | // Project(logicalplan.DynCol("names")).
62 | // Filter(
63 | // logicalplan.Col("names.first_name").Eq(
64 | // logicalplan.Literal("Frederic")),
65 | // ).Execute(context.Background(),
66 | // func(_ context.Context, r arrow.RecordBatch) error {
67 | // fmt.Println(r)
68 | // return nil
69 | // })
70 | }
71 |
--------------------------------------------------------------------------------
/examples/benchmark_grpc/server_grpc.go:
--------------------------------------------------------------------------------
1 | package benchmark_grpc
2 |
3 | import (
4 | "context"
5 | "sync"
6 |
7 | pb "github.com/pancsta/asyncmachine-go/examples/benchmark_grpc/proto"
8 | )
9 |
10 | type WorkerServiceServer struct {
11 | pb.UnimplementedWorkerServiceServer
12 | mu sync.Mutex
13 | worker *Worker
14 | subscriber chan struct{}
15 | ready chan struct{}
16 | calls int
17 | }
18 |
19 | func NewWorkerServiceServer(worker *Worker) *WorkerServiceServer {
20 | s := &WorkerServiceServer{
21 | worker: worker,
22 | ready: make(chan struct{}),
23 | }
24 |
25 | worker.Subscribe(func() {
26 | s.subscriber <- struct{}{}
27 | })
28 |
29 | return s
30 | }
31 |
32 | func (s *WorkerServiceServer) CallOp(ctx context.Context, req *pb.CallOpRequest) (*pb.CallOpResponse, error) {
33 | s.mu.Lock()
34 | defer s.mu.Unlock()
35 |
36 | op := Op(req.Op)
37 | l("grpc-server", "op: %v", op)
38 |
39 | s.worker.CallOp(op)
40 |
41 | return &pb.CallOpResponse{Success: true}, nil
42 | }
43 |
44 | func (s *WorkerServiceServer) Subscribe(req *pb.Empty, stream pb.WorkerService_SubscribeServer) error {
45 | l("grpc-server", "Subscribe")
46 | ch := make(chan struct{}, 10)
47 | s.mu.Lock()
48 | s.subscriber = ch
49 | close(s.ready)
50 | s.mu.Unlock()
51 |
52 | for range ch {
53 | l("grpc-server", "notify")
54 | s.calls++
55 | if err := stream.Send(&pb.Empty{}); err != nil {
56 | return err
57 | }
58 | }
59 |
60 | return nil
61 | }
62 |
63 | func (s *WorkerServiceServer) GetValue(ctx context.Context, req *pb.Empty) (*pb.GetValueResponse, error) {
64 | s.mu.Lock()
65 | defer s.mu.Unlock()
66 |
67 | l("grpc-server", "GetValue")
68 |
69 | return &pb.GetValueResponse{Value: int32(s.worker.GetValue())}, nil
70 | }
71 |
72 | func (s *WorkerServiceServer) Start(ctx context.Context, req *pb.Empty) (*pb.Empty, error) {
73 | <-s.ready
74 |
75 | s.mu.Lock()
76 | defer s.mu.Unlock()
77 |
78 | l("grpc-server", "Start")
79 | s.worker.Start()
80 |
81 | return &pb.Empty{}, nil
82 | }
83 |
--------------------------------------------------------------------------------
/examples/fan_out_in/example_fan_out_in.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/joho/godotenv"
8 |
9 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
10 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
11 | amxhelp "github.com/pancsta/asyncmachine-go/pkg/x/helpers"
12 | )
13 |
14 | func init() {
15 | // load .env
16 | _ = godotenv.Load()
17 |
18 | // am-dbg is required for debugging, go run it
19 | // go run github.com/pancsta/asyncmachine-go/tools/cmd/am-dbg@latest
20 | amhelp.EnableDebugging(false)
21 | amhelp.SetEnvLogLevel(am.LogChanges)
22 | }
23 |
24 | func main() {
25 | ctx := context.Background()
26 |
27 | states := am.Schema{
28 | // task start state
29 | "Task": {Require: am.S{"Start"}},
30 | // task done state
31 | "TaskDone": {},
32 |
33 | // rest
34 |
35 | "Start": {},
36 | "Ready": {
37 | Auto: true,
38 | Require: am.S{"TaskDone"},
39 | },
40 | "Healthcheck": {Multi: true},
41 | }
42 | names := am.S{"Start", "Task", "TaskDone", "Ready", "Healthcheck", am.StateException}
43 |
44 | // init state machine
45 | mach, err := am.NewCommon(ctx, "fan", states, names, &am.ExceptionHandler{}, nil, &am.Opts{
46 | LogLevel: am.LogOps,
47 | })
48 | if err != nil {
49 | panic(err)
50 | }
51 | amhelp.MachDebugEnv(mach)
52 |
53 | // define a task func
54 | fn := func(num int, state, stateDone string) {
55 | ctx := mach.NewStateCtx(state)
56 | go func() {
57 | if ctx.Err() != nil {
58 | return // expired
59 | }
60 | amhelp.Wait(ctx, time.Second)
61 | if ctx.Err() != nil {
62 | return // expired
63 | }
64 | mach.Add1(stateDone, nil)
65 | }()
66 | }
67 |
68 | // create task states
69 | // 10 tasks, 3 running concurrently
70 | _, err = amxhelp.FanOutIn(mach, "Task", 15, 3, fn)
71 | if err != nil {
72 | panic(err)
73 | }
74 |
75 | // start and wait
76 | mach.Add(am.S{"Start", "Task"}, nil)
77 | <-mach.When1("Ready", nil)
78 |
79 | // end
80 | println("done")
81 |
82 | // debug
83 | time.Sleep(time.Second)
84 | }
85 |
--------------------------------------------------------------------------------
/pkg/rpc/states/ss_rpc_shared.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | "github.com/pancsta/asyncmachine-go/pkg/states"
6 | . "github.com/pancsta/asyncmachine-go/pkg/states/global"
7 | )
8 |
9 | // SharedStatesDef contains all the states of the Worker state machine.
10 | type SharedStatesDef struct {
11 |
12 | // errors
13 |
14 | ErrNetworkTimeout string
15 | ErrRpc string
16 | ErrDelivery string
17 |
18 | // connection
19 |
20 | HandshakeDone string
21 | Handshaking string
22 |
23 | // inherit from BasicStatesDef
24 | *states.BasicStatesDef
25 | }
26 |
27 | // SharedGroupsDef contains all the state groups of the Worker state machine.
28 | type SharedGroupsDef struct {
29 |
30 | // Work represents work-related states, 1 active at a time.
31 | Handshake S
32 | }
33 |
34 | // SharedSchema represents all relations and properties of WorkerStates.
35 | var SharedSchema = SchemaMerge(
36 | // inherit from BasicStruct
37 | states.BasicSchema,
38 | am.Schema{
39 |
40 | // Errors
41 | s.ErrNetworkTimeout: {
42 | Add: S{s.Exception},
43 | Require: S{s.Exception},
44 | },
45 | s.ErrRpc: {
46 | Add: S{s.Exception},
47 | Require: S{s.Exception},
48 | },
49 | s.ErrDelivery: {
50 | Add: S{s.Exception},
51 | Require: S{s.Exception},
52 | },
53 |
54 | // Handshake
55 | s.Handshaking: {
56 | Require: S{s.Start},
57 | Remove: g.Handshake,
58 | },
59 | s.HandshakeDone: {
60 | Require: S{s.Start},
61 | Remove: g.Handshake,
62 | },
63 | })
64 |
65 | // EXPORTS AND GROUPS
66 |
67 | var (
68 | // ws is worker states from SharedStatesDef.
69 | s = am.NewStates(SharedStatesDef{})
70 |
71 | // wg is worker groups from SharedGroupsDef.
72 | g = am.NewStateGroups(SharedGroupsDef{
73 | Handshake: S{s.Handshaking, s.HandshakeDone},
74 | })
75 |
76 | // SharedStates contains all the states shared RPC states.
77 | SharedStates = s
78 |
79 | // SharedGroups contains all the shared state groups for RPC.
80 | SharedGroups = g
81 | )
82 |
--------------------------------------------------------------------------------
/docs/jsonschema/getter_req.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json-schema.org/draft/2020-12/schema",
3 | "$id": "https://github.com/pancsta/asyncmachine-go/pkg/integrations/getter-req",
4 | "$ref": "#/$defs/GetterReq",
5 | "$defs": {
6 | "GetterReq": {
7 | "properties": {
8 | "kind": {
9 | "$ref": "#/$defs/Kind",
10 | "description": "The kind of the request."
11 | },
12 | "time": {
13 | "$ref": "#/$defs/S",
14 | "description": "Request ticks of the passed states"
15 | },
16 | "time_sum": {
17 | "$ref": "#/$defs/S",
18 | "description": "Request the sum of ticks of the passed states"
19 | },
20 | "clocks": {
21 | "$ref": "#/$defs/S",
22 | "description": "Request named clocks of the passed states"
23 | },
24 | "tags": {
25 | "type": "boolean",
26 | "description": "Request the tags of the state machine"
27 | },
28 | "export": {
29 | "type": "boolean",
30 | "description": "Request an importable version of the state machine"
31 | },
32 | "id": {
33 | "type": "boolean",
34 | "description": "Request the ID of the state machine"
35 | },
36 | "parent_id": {
37 | "type": "boolean",
38 | "description": "Request the ID of the parent state machine"
39 | }
40 | },
41 | "additionalProperties": false,
42 | "type": "object",
43 | "required": [
44 | "kind"
45 | ],
46 | "description": "GetterReq is a generic request, which results in GetterResp with respective fields filled out."
47 | },
48 | "Kind": {
49 | "properties": {
50 | "Value": {
51 | "type": "string"
52 | }
53 | },
54 | "additionalProperties": false,
55 | "type": "object",
56 | "required": [
57 | "Value"
58 | ]
59 | },
60 | "S": {
61 | "items": {
62 | "type": "string"
63 | },
64 | "type": "array"
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/examples/mach_template/states/ss_mach_template.go:
--------------------------------------------------------------------------------
1 | // Package states contains a stateful schema-v2 for MachTemplate.
2 | // Bootstrapped with am-gen. Edit manually or re-gen & merge.
3 | package states
4 |
5 | import (
6 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
7 | ss "github.com/pancsta/asyncmachine-go/pkg/states"
8 | )
9 |
10 | // MachTemplateStatesDef contains all the states of the MachTemplate state machine.
11 | type MachTemplateStatesDef struct {
12 | *am.StatesBase
13 |
14 | ErrExample string
15 | Foo string
16 | Bar string
17 | Baz string
18 | BazDone string
19 | Channel string
20 |
21 | // inherit from BasicStatesDef
22 | *ss.BasicStatesDef
23 | // inherit from ConnectedStatesDef
24 | *ss.ConnectedStatesDef
25 | // inherit from DisposedStatesDef
26 | *ss.DisposedStatesDef
27 | }
28 |
29 | // MachTemplateGroupsDef contains all the state groups MachTemplate state machine.
30 | type MachTemplateGroupsDef struct {
31 | *ss.ConnectedGroupsDef
32 | Group1 S
33 | Group2 S
34 | }
35 |
36 | // MachTemplateSchema represents all relations and properties of MachTemplateStates.
37 | var MachTemplateSchema = SchemaMerge(
38 | // inherit from BasicSchema
39 | ss.BasicSchema,
40 | // inherit from ConnectedSchema
41 | ss.ConnectedSchema,
42 | // inherit from DisposedSchema
43 | ss.DisposedSchema,
44 | am.Schema{
45 |
46 | ssM.ErrExample: {
47 | Require: S{ssM.Exception},
48 | },
49 | ssM.Foo: {
50 | Require: S{ssM.Bar},
51 | },
52 | ssM.Bar: {},
53 | ssM.Baz: {
54 | Multi: true,
55 | },
56 | ssM.BazDone: {
57 | Multi: true,
58 | },
59 | ssM.Channel: {},
60 | })
61 |
62 | // EXPORTS AND GROUPS
63 |
64 | var (
65 | ssM = am.NewStates(MachTemplateStatesDef{})
66 | sgM = am.NewStateGroups(MachTemplateGroupsDef{
67 | Group1: S{},
68 | Group2: S{},
69 | }, ss.ConnectedGroups)
70 |
71 | // MachTemplateStates contains all the states for the MachTemplate machine.
72 | MachTemplateStates = ssM
73 | // MachTemplateGroups contains all the state groups for the MachTemplate machine.
74 | MachTemplateGroups = sgM
75 | )
76 |
--------------------------------------------------------------------------------
/scripts/gen_website/sitemap/sitemap.go:
--------------------------------------------------------------------------------
1 | package sitemap
2 |
3 | type Entry struct {
4 | Url string
5 | Path string
6 | SkipMenu bool
7 | }
8 |
9 | var MainMenu = []Entry{
10 | {Url: "", Path: "README.md"},
11 | {Url: "examples", Path: "examples/README.md"},
12 | {Url: "manual", Path: "docs/manual.md"},
13 | {"", "", false},
14 | {Url: "machine", Path: "pkg/machine/README.md"},
15 | {Url: "states", Path: "pkg/states/README.md"},
16 | {Url: "helpers", Path: "pkg/helpers/README.md"},
17 | {Url: "telemetry", Path: "pkg/telemetry/README.md"},
18 | {Url: "history", Path: "pkg/history/README.md"},
19 | {"", "", false},
20 | {Url: "rpc", Path: "pkg/rpc/README.md"},
21 | {Url: "integrations", Path: "pkg/integrations/README.md"},
22 | {Url: "node", Path: "pkg/node/README.md"},
23 | {Url: "pubsub", Path: "pkg/pubsub/README.md"},
24 | {"", "", false},
25 | {Url: "am-gen", Path: "tools/cmd/am-gen/README.md"},
26 | {Url: "am-dbg", Path: "tools/cmd/am-dbg/README.md"},
27 | {Url: "arpc", Path: "tools/cmd/arpc/README.md"},
28 | {Url: "am-vis", Path: "tools/cmd/am-vis/README.md"},
29 | {Url: "am-relay", Path: "tools/cmd/am-relay/README.md"},
30 |
31 | // rest (non-menu)
32 | {Url: "faq", Path: "FAQ.md", SkipMenu: true},
33 | {Url: "changelog", Path: "CHANGELOG.md", SkipMenu: true},
34 | {Url: "env-configs", Path: "docs/env-configs.md", SkipMenu: true},
35 | {Url: "tree-state-source",
36 | Path: "examples/tree_state_source/README.md", SkipMenu: true},
37 | {Url: "benchmark-grpc",
38 | Path: "examples/benchmark_grpc/README.md", SkipMenu: true},
39 | {Url: "benchmark-libp2p-pubsub",
40 | Path: "examples/benchmark_libp2p_pubsub/README.md", SkipMenu: true},
41 | {Url: "tools-debugger",
42 | Path: "tools/debugger/README.md", SkipMenu: true},
43 | {Url: "benchmark-state-source",
44 | Path: "examples/benchmark_state_source/README.md", SkipMenu: true},
45 | {Url: "arpc-howto", Path: "pkg/rpc/HOWTO.md", SkipMenu: true},
46 | {Url: "cookbook", Path: "docs/cookbook.md", SkipMenu: true},
47 | {Url: "diagrams", Path: "docs/diagrams.md", SkipMenu: true},
48 | {Url: "roadmap", Path: "ROADMAP.md", SkipMenu: true},
49 | {Url: "breaking-changes", Path: "BREAKING.md", SkipMenu: true},
50 | }
51 |
--------------------------------------------------------------------------------
/scripts/extract_mermaid/extract_mermaid.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 | "regexp"
8 | "slices"
9 | "strings"
10 | )
11 |
12 | func main() {
13 | err := extractMermaidDiagrams(os.Args[1], os.Args[2])
14 | if err != nil {
15 | fmt.Println("Error:", err)
16 | }
17 | }
18 |
19 | func extractMermaidDiagrams(inputFile string, reqIndexes string) error {
20 | // Open the Markdown file
21 | file, err := os.Open(inputFile)
22 | if err != nil {
23 | return fmt.Errorf("failed to open file: %v", err)
24 | }
25 | defer file.Close()
26 |
27 | ri := strings.Split(reqIndexes, " ")
28 |
29 | // Regex to detect mermaid code blocks
30 | startRegex := regexp.MustCompile("^```mermaid$")
31 | endRegex := regexp.MustCompile("^```$")
32 |
33 | // Scanner to read the file line by line
34 | scanner := bufio.NewScanner(file)
35 |
36 | isInMermaidBlock := false
37 | diagramNumber := 1
38 | var diagramContent strings.Builder // Store diagram content temporarily
39 |
40 | for scanner.Scan() {
41 | line := scanner.Text()
42 |
43 | // Check for the start of a mermaid block
44 | if startRegex.MatchString(line) {
45 | isInMermaidBlock = true
46 | diagramContent.Reset() // Clear the previous content
47 | continue
48 | }
49 |
50 | // Check for the end of a mermaid block
51 | if isInMermaidBlock && endRegex.MatchString(line) {
52 | // skip if not requested
53 | if !slices.Contains(ri, fmt.Sprintf("%d", diagramNumber)) {
54 | continue
55 | }
56 |
57 | isInMermaidBlock = false
58 |
59 | // Write the extracted diagram to a .mmd file
60 | outputFile := fmt.Sprintf("diagram_%d.mmd", diagramNumber)
61 | err := os.WriteFile(outputFile, []byte(diagramContent.String()), 0644)
62 | if err != nil {
63 | return fmt.Errorf("failed to write diagram file: %v", err)
64 | }
65 | fmt.Printf("Extracted diagram %d into %s\n", diagramNumber, outputFile)
66 |
67 | diagramNumber++
68 | continue
69 | }
70 |
71 | // Collect lines for the mermaid block
72 | if isInMermaidBlock {
73 | diagramContent.WriteString(line + "\n")
74 | }
75 | }
76 |
77 | // Check for scanner errors
78 | if err := scanner.Err(); err != nil {
79 | return fmt.Errorf("error reading file: %v", err)
80 | }
81 |
82 | return nil
83 | }
84 |
--------------------------------------------------------------------------------
/pkg/x/helpers/x_help_test.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import (
4 | "context"
5 | "testing"
6 | "time"
7 |
8 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
9 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | type S = am.S
14 | type T = am.Time
15 |
16 | func TestTimeMatrix(t *testing.T) {
17 | // m1 init
18 | m1 := NewNoRels(t, nil)
19 | statesStruct := m1.Schema()
20 | statesStruct["B"] = am.State{Multi: true}
21 | statesStruct["Bump1"] = am.State{}
22 | err := m1.SetSchema(statesStruct,
23 | S{"A", "B", "C", "D", "Bump1", am.StateException})
24 | assert.NoError(t, err)
25 |
26 | // mutate & assert
27 | m1.Add(S{"A", "B"}, nil)
28 | m1.Add(S{"A", "B"}, nil)
29 | assertTime(t, m1, S{"A", "B", "C", "D"}, T{1, 3, 0, 0},
30 | "m1 clocks mismatch")
31 |
32 | // m2 init
33 | m2 := NewNoRels(t, nil)
34 | statesStruct = m1.Schema()
35 | statesStruct["B"] = am.State{Multi: true}
36 | statesStruct["Bump1"] = am.State{}
37 | err = m2.SetSchema(statesStruct, S{"A", "B", "C", "D", "Bump1",
38 | am.StateException})
39 | assert.NoError(t, err)
40 |
41 | // mutate & assert
42 | m2.Add(S{"A", "B"}, nil)
43 | m2.Add(S{"A", "B"}, nil)
44 | m2.Add(S{"A", "B", "C"}, nil)
45 | m2.Set(S{"D"}, nil)
46 | assertTime(t, m2, S{"A", "B", "C", "D"}, T{2, 6, 2, 1},
47 | "m2 clocks mismatch")
48 |
49 | matrix, err := TimeMatrix([]*am.Machine{m1, m2})
50 | assert.NoError(t, err)
51 | assert.Equal(t, T{1, 3, 0, 0, 0, 0}, matrix[0])
52 | assert.Equal(t, T{2, 6, 2, 1, 0, 0}, matrix[1])
53 | }
54 |
55 | // /// helpers
56 | // TODO extract test helpers to internal/testing
57 |
58 | // NewNoRels creates a new machine with no relations between states.
59 | func NewNoRels(t *testing.T, initialState am.S) *am.Machine {
60 | m := am.New(context.Background(), am.Schema{
61 | "A": {},
62 | "B": {},
63 | "C": {},
64 | "D": {},
65 | }, nil)
66 | m.SemLogger().SetLogger(func(i am.LogLevel, msg string, args ...any) {
67 | t.Logf(msg, args...)
68 | })
69 | if amhelp.IsDebug() {
70 | m.SemLogger().SetLevel(am.LogEverything)
71 | m.HandlerTimeout = 2 * time.Minute
72 | }
73 | if initialState != nil {
74 | m.Set(initialState, nil)
75 | }
76 | return m
77 | }
78 |
79 | func assertTime(t *testing.T, m *am.Machine, states S, time T,
80 | msgAndArgs ...interface{},
81 | ) {
82 | assert.Equal(t, m.Time(states), time, msgAndArgs...)
83 | }
84 |
--------------------------------------------------------------------------------
/pkg/node/test/worker/node_test_worker.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "flag"
6 | "log/slog"
7 | "os"
8 | "time"
9 |
10 | testutils "github.com/pancsta/asyncmachine-go/internal/testing/utils"
11 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
12 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
13 | "github.com/pancsta/asyncmachine-go/pkg/node"
14 | "github.com/pancsta/asyncmachine-go/pkg/node/states"
15 | "github.com/pancsta/asyncmachine-go/pkg/rpc"
16 | )
17 |
18 | var ssW = states.WorkerStates
19 |
20 | func main() {
21 | ctx := context.Background()
22 | fA := flag.String("a", "", "addr")
23 | flag.Parse()
24 | if fA == nil || *fA == "" {
25 | panic("addr is required")
26 | }
27 | addr := *fA
28 |
29 | slog.Info("fork worker", "addr", addr)
30 |
31 | // worker
32 |
33 | // machine init
34 | mach := am.New(context.Background(), testutils.RelsNodeWorkerSchema, &am.Opts{
35 | Id: "t-worker-" + addr})
36 | err := mach.VerifyStates(testutils.RelsNodeWorkerStates)
37 | if err != nil {
38 | panic(err)
39 | }
40 |
41 | if os.Getenv(am.EnvAmDebug) != "" {
42 | mach.SemLogger().SetLevel(am.LogEverything)
43 | mach.HandlerTimeout = 2 * time.Minute
44 | }
45 | worker, err := node.NewWorker(ctx, "NTW", mach.Schema(),
46 | mach.StateNames(), nil)
47 | if err != nil {
48 | panic(err)
49 | }
50 | err = worker.Mach.BindHandlers(&workerHandlers{Mach: mach})
51 | if err != nil {
52 | panic(err)
53 | }
54 |
55 | // connect Worker to the bootstrap machine
56 | res := worker.Start(addr)
57 | if res != am.Executed {
58 | panic(worker.Mach.Err())
59 | }
60 | err = amhelp.WaitForAll(ctx, 1*time.Second,
61 | worker.Mach.When1(ssW.RpcReady, nil))
62 | if err != nil {
63 | panic(err)
64 | }
65 |
66 | // wait for connection
67 | _ = amhelp.WaitForAll(ctx, 3*time.Second,
68 | worker.Mach.When1(ssW.SuperConnected, nil))
69 | // block until disconnected
70 | <-worker.Mach.WhenNot1(ssW.SuperConnected, nil)
71 | }
72 |
73 | type workerHandlers struct {
74 | Mach *am.Machine
75 | }
76 |
77 | func (w *workerHandlers) WorkRequestedState(e *am.Event) {
78 | input := e.Args["input"].(int)
79 |
80 | payload := &rpc.ArgsPayload{
81 | Name: w.Mach.Id(),
82 | Data: input * input,
83 | Source: e.Machine().Id(),
84 | }
85 |
86 | e.Machine().Add1(ssW.ClientSendPayload, rpc.PassRpc(&rpc.A{
87 | Name: w.Mach.Id(),
88 | Payload: payload,
89 | }))
90 | }
91 |
--------------------------------------------------------------------------------
/examples/repl/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Steps:
4 | // 1. go run .
5 | // 2. go run github.com/pancsta/asyncmachine-go/tools/cmd/arpc@latest -d tmp
6 |
7 | import (
8 | "context"
9 | "fmt"
10 | "log"
11 | "os"
12 | "os/signal"
13 | "syscall"
14 | "time"
15 |
16 | "github.com/pancsta/asyncmachine-go/examples/arpc/states"
17 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
18 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
19 | arpc "github.com/pancsta/asyncmachine-go/pkg/rpc"
20 | )
21 |
22 | var ss = states.ExampleStates
23 |
24 | func init() {
25 | // am-dbg is required for debugging, go run it
26 | // go run github.com/pancsta/asyncmachine-go/tools/cmd/am-dbg@latest
27 | // amhelp.EnableDebugging(true)
28 | // amhelp.SetEnvLogLevel(am.LogOps)
29 | }
30 |
31 | func main() {
32 | ctx, cancel := context.WithCancel(context.Background())
33 | defer cancel()
34 |
35 | // handle exit TODO not working
36 | sigChan := make(chan os.Signal, 1)
37 | signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
38 |
39 | // workers
40 | for i := range 3 {
41 | worker, err := newWorker(ctx, i)
42 | if err != nil {
43 | log.Print(err)
44 | }
45 |
46 | time.Sleep(100 * time.Millisecond)
47 | fmt.Printf("start %d\n", i)
48 | worker.Log("hello")
49 | }
50 |
51 | // wait
52 | <-sigChan
53 |
54 | fmt.Println("bye")
55 | }
56 |
57 | func newWorker(ctx context.Context, num int) (*am.Machine, error) {
58 | // init
59 |
60 | handlers := &workerHandlers{}
61 | id := fmt.Sprintf("worker%d", num)
62 | worker, err := am.NewCommon(ctx, id, states.ExampleSchema,
63 | ss.Names(), handlers, nil, nil)
64 | if err != nil {
65 | return nil, err
66 | }
67 | handlers.Mach = worker
68 |
69 | // telemetry
70 |
71 | amhelp.MachDebugEnv(worker)
72 | // worker.SemLogger().SetArgsMapper(am.NewArgsMapper([]string{"log"}, 0))
73 | worker.SemLogger().SetLevel(am.LogChanges)
74 | // start a REPL aRPC server, create an addr file
75 | arpc.MachRepl(worker, "", "tmp", nil, nil)
76 |
77 | return worker, nil
78 | }
79 |
80 | type workerHandlers struct {
81 | *am.ExceptionHandler
82 | Mach *am.Machine
83 | }
84 |
85 | func (h *workerHandlers) FooState(e *am.Event) {
86 | fmt.Println("FooState")
87 | h.Mach.Log("FooState")
88 | }
89 |
90 | func (h *workerHandlers) BarState(e *am.Event) {
91 | fmt.Println("BarState")
92 | h.Mach.Log("BarState")
93 | }
94 |
95 | func (h *workerHandlers) BazState(e *am.Event) {
96 | fmt.Println("BazState")
97 | h.Mach.Log("BarState")
98 | }
99 |
--------------------------------------------------------------------------------
/tools/debugger/utils_test.go:
--------------------------------------------------------------------------------
1 | package debugger
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 |
9 | "github.com/pancsta/asyncmachine-go/tools/debugger/server"
10 | )
11 |
12 | func TestHumanSort(t *testing.T) {
13 | data := []string{
14 | "_nco-dev-test-210946-00e4ff1",
15 | "_rs-nco-dev-test-210946-00e4ff1",
16 | "_rc-con-dev-test-2870ef",
17 | "_rs-nw-loc-dev-test-6791e5",
18 | "_rc-con-dev-test-6791e5",
19 | "_ns-dev-test-210946-0",
20 | "_rs-ns-pub-dev-test-210946-0",
21 | "_rs-ns-loc-dev-test-210946-0",
22 | "_rs-nco-dev-test-210946-0e7608f",
23 | "_rc-ns-dev-test-p-36317",
24 | "_rs-nw-loc-dev-test-b55a1b",
25 | "_nco-dev-test-210946-0c2bbae",
26 | "_rc-ns-dev-test-p-44331",
27 | "_rs-nw-loc-dev-test-2870ef",
28 | "_nw-dev-test-2870ef",
29 | "_nw-dev-test-6791e5",
30 | "_nco-dev-test-210946-0e7608f",
31 | "_nw-dev-test-b55a1b",
32 | "_rc-con-dev-test-b55a1b",
33 | "_rs-nco-dev-test-210946-0c2bbae",
34 | }
35 |
36 | expected := []string{
37 | "_nco-dev-test-210946-00e4ff1",
38 | "_nco-dev-test-210946-0c2bbae",
39 | "_nco-dev-test-210946-0e7608f",
40 |
41 | "_ns-dev-test-210946-0",
42 |
43 | "_nw-dev-test-2870ef",
44 | "_nw-dev-test-6791e5",
45 | "_nw-dev-test-b55a1b",
46 |
47 | "_rc-con-dev-test-2870ef",
48 | "_rc-con-dev-test-6791e5",
49 | "_rc-con-dev-test-b55a1b",
50 | "_rc-ns-dev-test-p-36317",
51 | "_rc-ns-dev-test-p-44331",
52 | "_rs-nco-dev-test-210946-00e4ff1",
53 | "_rs-nco-dev-test-210946-0c2bbae",
54 | "_rs-nco-dev-test-210946-0e7608f",
55 | "_rs-ns-loc-dev-test-210946-0",
56 | "_rs-ns-pub-dev-test-210946-0",
57 | "_rs-nw-loc-dev-test-2870ef",
58 | "_rs-nw-loc-dev-test-6791e5",
59 | "_rs-nw-loc-dev-test-b55a1b",
60 | }
61 |
62 | humanSort(data)
63 | join := strings.Join(data, "\n")
64 |
65 | // debug
66 | // t.Log("\n" + join)
67 |
68 | assert.Equal(t, strings.Join(expected, "\n"), join)
69 | }
70 |
71 | func TestHadErrSince(t *testing.T) {
72 | c := &Client{
73 | Client: &server.Client{
74 | Errors: []int{100, 50, 5, 1},
75 | },
76 | }
77 |
78 | assert.False(t, c.HadErrSinceTx(300, 5), "tx: %d, dist: %d", 300, 5)
79 | assert.False(t, c.HadErrSinceTx(105, 3), "tx: %d, dist: %d", 105, 3)
80 |
81 | assert.True(t, c.HadErrSinceTx(55, 10), "tx: %d, dist: %d", 55, 10)
82 | assert.True(t, c.HadErrSinceTx(6, 2), "tx: %d, dist: %d", 6, 2)
83 | assert.True(t, c.HadErrSinceTx(100, 2), "tx: %d, dist: %d", 100, 2)
84 | assert.True(t, c.HadErrSinceTx(1, 1), "tx: %d, dist: %d", 1, 1)
85 | }
86 |
--------------------------------------------------------------------------------
/internal/testing/cmd/am-dbg-worker/main_dbg_worker.go:
--------------------------------------------------------------------------------
1 | // AM_DBG_WORKER_ADDR
2 | // AM_DBG_ADDR
3 | package main
4 |
5 | import (
6 | "context"
7 | "flag"
8 | "os"
9 | "time"
10 |
11 | amtest "github.com/pancsta/asyncmachine-go/internal/testing"
12 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
13 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
14 | "github.com/pancsta/asyncmachine-go/pkg/rpc"
15 | ssrpc "github.com/pancsta/asyncmachine-go/pkg/rpc/states"
16 | "github.com/pancsta/asyncmachine-go/pkg/telemetry"
17 | "github.com/pancsta/asyncmachine-go/tools/debugger"
18 | "github.com/pancsta/asyncmachine-go/tools/debugger/server"
19 | ssdbg "github.com/pancsta/asyncmachine-go/tools/debugger/states"
20 | )
21 |
22 | func main() {
23 | ctx, cancel := context.WithCancel(context.Background())
24 | defer cancel()
25 |
26 | // read env
27 | amDbgAddr := os.Getenv(telemetry.EnvAmDbgAddr)
28 | logLvl := am.EnvLogLevel("")
29 |
30 | // test worker has its own flags
31 | serverAddr := flag.String("server-addr", "",
32 | "Addr of the debugger server (opt)")
33 | workerAddr := flag.String("worker-addr", amtest.WorkerRpcAddr,
34 | "Addr of the rpc worker")
35 | flag.Parse()
36 |
37 | // worker init
38 | os.Setenv(amhelp.EnvAmLogFile, "1")
39 | dbg, err := amtest.NewDbgWorker(true, debugger.Opts{
40 | AddrRpc: *serverAddr,
41 | })
42 | if err != nil {
43 | panic(err)
44 | }
45 |
46 | // server init
47 | s, err := rpc.NewServer(ctx, *workerAddr, "worker", dbg.Mach, nil)
48 | if err != nil {
49 | panic(err)
50 | }
51 | amhelp.MachDebug(s.Mach, amDbgAddr, logLvl, false,
52 | amhelp.SemConfig(true))
53 |
54 | // tear down
55 | defer func() {
56 | if amDbgAddr != "" {
57 | // cool off am-dbg and free the ports
58 | time.Sleep(100 * time.Millisecond)
59 | }
60 | }()
61 |
62 | // am-dbg server (used for testing live connections)
63 | if *serverAddr != "" {
64 | go server.StartRpc(dbg.Mach, *serverAddr, nil, nil, false)
65 | }
66 |
67 | // start with a timeout
68 | readyCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
69 | defer cancel()
70 |
71 | // server start
72 | s.Start()
73 | select {
74 | case <-s.Mach.WhenErr(readyCtx):
75 | err := s.Mach.Err()
76 | if readyCtx.Err() != nil {
77 | err = readyCtx.Err()
78 | }
79 | panic(err)
80 | case <-s.Mach.When1(ssrpc.ServerStates.RpcReady, readyCtx):
81 | }
82 |
83 | // wait till the end
84 | select {
85 | case <-dbg.Mach.WhenDisposed():
86 | // user exit
87 | case <-dbg.Mach.WhenNot1(ssdbg.Start, nil):
88 | dbg.Dispose()
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/pkg/rpc/states/ss_rpc_server.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | . "github.com/pancsta/asyncmachine-go/pkg/states/global"
6 | )
7 |
8 | // ServerStatesDef contains all the states of the Client state machine.
9 | type ServerStatesDef struct {
10 |
11 | // errors
12 |
13 | // ErrOnClient indicates an error added on the RPC worker, not the source
14 | // worker.
15 | ErrOnClient string
16 |
17 | // basics
18 |
19 | // Ready - Client is fully connected to the server.
20 | Ready string
21 |
22 | // rpc
23 |
24 | RpcStarting string
25 | RpcReady string
26 |
27 | // TODO failsafe
28 | // RetryingCall string
29 | // CallRetryFailed string
30 |
31 | ClientConnected string
32 |
33 | // inherit from SharedStatesDef
34 | *SharedStatesDef
35 | }
36 |
37 | // ServerGroupsDef contains all the state groups of the Client state machine.
38 | type ServerGroupsDef struct {
39 | *SharedGroupsDef
40 |
41 | // Rpc is a group for RPC ready states.
42 | Rpc S
43 | }
44 |
45 | // ServerSchema represents all relations and properties of ClientStates.
46 | var ServerSchema = SchemaMerge(
47 | // inherit from SharedStruct
48 | SharedSchema,
49 | am.Schema{
50 |
51 | ssS.ErrOnClient: {Require: S{Exception}},
52 | ssS.ErrNetwork: {
53 | Require: S{am.StateException},
54 | Remove: S{ssS.ClientConnected},
55 | },
56 |
57 | // inject Server states into HandshakeDone
58 | ssS.HandshakeDone: StateAdd(
59 | SharedSchema[ssS.HandshakeDone],
60 | am.State{
61 | Remove: S{Exception},
62 | }),
63 |
64 | // Server
65 |
66 | ssS.Start: {Add: S{ssS.RpcStarting}},
67 | ssS.Ready: {
68 | Auto: true,
69 | Require: S{ssS.HandshakeDone, ssS.RpcReady},
70 | },
71 |
72 | ssS.RpcStarting: {
73 | Require: S{ssS.Start},
74 | Remove: sgS.Rpc,
75 | },
76 | ssS.RpcReady: {
77 | Require: S{ssS.Start},
78 | Remove: sgS.Rpc,
79 | },
80 |
81 | ssS.ClientConnected: {
82 | Require: S{ssS.RpcReady},
83 | },
84 | // TODO ClientBye for graceful shutdowns
85 | })
86 |
87 | // EXPORTS AND GROUPS
88 |
89 | var (
90 | ssS = am.NewStates(ServerStatesDef{})
91 | sgS = am.NewStateGroups(ServerGroupsDef{
92 | Rpc: S{ssS.RpcStarting, ssS.RpcReady},
93 | }, SharedGroups)
94 |
95 | // ServerStates contains all the states for the Client machine.
96 | ServerStates = ssS
97 | // ServerGroups contains all the state groups for the Client machine.
98 | ServerGroups = sgS
99 | )
100 |
--------------------------------------------------------------------------------
/examples/subscriptions/example_subscriptions.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "sync"
8 | "time"
9 |
10 | "github.com/joho/godotenv"
11 |
12 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
13 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
14 | )
15 |
16 | func init() {
17 | // load .env
18 | _ = godotenv.Load()
19 |
20 | // am-dbg is required for debugging, go run it
21 | // go run github.com/pancsta/asyncmachine-go/tools/cmd/am-dbg@latest
22 | // amhelp.EnableDebugging(false)
23 | // amhelp.SetEnvLogLevel(am.LogOps)
24 | }
25 |
26 | func main() {
27 | ctx := context.Background()
28 |
29 | // init state machines
30 | mach := am.New(ctx, am.Schema{
31 | "Foo": {},
32 | "Bar": {},
33 | "Healthcheck": {Multi: true},
34 | }, &am.Opts{LogLevel: am.LogOps, Id: "source"})
35 | amhelp.MachDebugEnv(mach)
36 |
37 | wg := sync.WaitGroup{}
38 |
39 | wg.Add(1)
40 | go func() {
41 | go wg.Done()
42 | // wait until FileDownloaded becomes active
43 | <-mach.When1("Foo", nil)
44 | fmt.Println("1 - Foo activated")
45 | }()
46 |
47 | wg.Add(1)
48 | go func() {
49 | go wg.Done()
50 | // wait until FileDownloaded becomes inactive
51 | <-mach.WhenNot1("Bar", nil)
52 | fmt.Println("2 - Bar deactivated")
53 | }()
54 |
55 | wg.Add(1)
56 | go func() {
57 | go wg.Done()
58 | // wait for EventConnected to be activated with an arg ID=123
59 | <-mach.WhenArgs("Bar", am.A{"ID": 123}, nil)
60 | fmt.Println("3 - Bar activated with ID=123")
61 | }()
62 |
63 | wg.Add(1)
64 | go func() {
65 | go wg.Done()
66 | // wait for Foo to have a tick >= 1 and Bar tick >= 3
67 | <-mach.WhenTime(am.S{"Foo", "Bar"}, am.Time{1, 3}, nil)
68 | fmt.Println("4 - Foo tick >= 1 and Bar tick >= 3")
69 | }()
70 |
71 | wg.Add(1)
72 | go func() {
73 | go wg.Done()
74 | // wait for DownloadingFile to have a tick increased by 2 since now
75 | <-mach.WhenTicks("Foo", 2, nil)
76 | fmt.Println("5 - Foo tick increased by 2 since now")
77 | }()
78 |
79 | wg.Add(1)
80 | go func() {
81 | go wg.Done()
82 | // wait for an error
83 | <-mach.WhenErr(ctx)
84 | fmt.Println("6 - Error")
85 | }()
86 |
87 | wg.Wait()
88 |
89 | mach.Add1("Foo", nil)
90 | mach.Add1("Bar", nil)
91 | mach.Remove1("Bar", nil)
92 | mach.Remove1("Foo", nil)
93 | mach.Add1("Foo", nil)
94 | mach.Add1("Bar", am.A{"ID": 123})
95 | mach.AddErr(errors.New("err"), nil)
96 |
97 | // wait
98 | time.Sleep(time.Second)
99 | }
100 |
--------------------------------------------------------------------------------
/pkg/history/test/test_hist.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "context"
5 | "testing"
6 | "time"
7 |
8 | "github.com/stretchr/testify/require"
9 |
10 | amhist "github.com/pancsta/asyncmachine-go/pkg/history"
11 | amss "github.com/pancsta/asyncmachine-go/pkg/states"
12 | )
13 |
14 | var ss = amss.BasicStates
15 |
16 | func AssertBasics(t *testing.T, mem amhist.MemoryApi, rounds int) {
17 |
18 | ctx := context.Background()
19 | mach := mem.Machine()
20 | start := time.Now()
21 |
22 | // validate
23 | require.True(t, mach.Has(ss.Names()), "Machine has to implement BasicStates")
24 | require.True(t, mem.IsTracked1(ss.Start), "Start should be tracked")
25 | require.False(t, mem.IsTracked1(ss.Ready), "Ready should not be tracked")
26 |
27 | t.Logf("rounds: %d", rounds)
28 |
29 | // mutate
30 | for range rounds {
31 | mach.Toggle1(ss.Start, nil)
32 | }
33 |
34 | t.Logf("mach: %s", time.Since(start))
35 |
36 | require.NoError(t, mem.Sync())
37 |
38 | t.Logf("db: %s", time.Since(start))
39 |
40 | // check conditions
41 | // now := time.Now().UTC()
42 | // require.True(t, mem.ActivatedBetween(ctx, ss.Start, start.UTC(), now),
43 | // "Start was activated")
44 | // require.False(t, mem.ActivatedBetween(ctx, ss.Ready, start.UTC(), now),
45 | // "Ready isn't tracked")
46 |
47 | // machine record
48 | machRec := mem.MachineRecord()
49 | require.NotNil(t, machRec, "Machine record is not nil")
50 |
51 | // many rows, no condition
52 | latest, err := mem.FindLatest(ctx, false, 25, amhist.Query{})
53 | // sum := []int{}
54 | // for _, r := range latest {
55 | // sum = append(sum, int(r.Time.MTimeSum))
56 | // }
57 | // print(sum)
58 | require.NoError(t, err)
59 | require.Len(t, latest, 25, "25 rows returned")
60 | require.Equal(t, int(machRec.NextId)-24, int(latest[23].Time.MTimeSum),
61 | "time sum matches")
62 | require.Equal(t, int(machRec.NextId)-25, int(latest[24].Time.MTimeSum),
63 | "time sum matches")
64 |
65 | t.Logf("query: %s", time.Since(start))
66 | }
67 |
68 | func AssertGc(t *testing.T, mem amhist.MemoryApi, rounds int) {
69 | ctx := context.Background()
70 |
71 | // GC
72 | all, err := mem.FindLatest(ctx, false, mem.Config().MaxRecords*2,
73 | amhist.Query{})
74 | require.NoError(t, err)
75 | require.LessOrEqual(t, len(all), mem.Config().MaxRecords,
76 | "max records respected")
77 | }
78 |
79 | // TODO test resume
80 | // TODO test schema
81 | // TODO test parallel tracking
82 | // TODO test rpc tracking in /pkg/rpc
83 | // TODO test double tracking
84 | // TODO test config
85 | // TODO test restore
86 | // TODO test transition
87 | // TODO test transition queries
88 |
--------------------------------------------------------------------------------
/tools/cmd/am-relay/README.md:
--------------------------------------------------------------------------------
1 | #
/tools/cmd/arpc
2 |
3 | [`cd /`](/README.md)
4 |
5 | > [!NOTE]
6 | > **asyncmachine-go** is a batteries-included graph control flow library (AOP, actor model, state-machine).
7 |
8 | ## am-relay
9 |
10 | `am-relay` converts formats and relays connections. It's an early version that for now can only rotate [dbg telemetry](/pkg/telemetry/README.md#dbg)
11 | into chunked file dumps.
12 |
13 | ```bash
14 | .rw-r--r--@ 713k foo 17 Nov 12:19 am-dbg-dump-2025-11-17_12-19-35.gob.br
15 | .rw-r--r--@ 737k foo 17 Nov 12:20 am-dbg-dump-2025-11-17_12-20-02.gob.br
16 | .rw-r--r--@ 749k foo 17 Nov 12:20 am-dbg-dump-2025-11-17_12-20-28.gob.br
17 | ```
18 |
19 | ## Installation
20 |
21 | - [Download a release binary](https://github.com/pancsta/asyncmachine-go/releases/latest)
22 | - Install `go install github.com/pancsta/asyncmachine-go/tools/cmd/am-relay@latest`
23 | - Run directly `go run github.com/pancsta/asyncmachine-go/tools/cmd/am-relay@latest`
24 |
25 | ## Features
26 |
27 | - rotate dbg telemetry
28 | - TODO websocket-to-tcp
29 | - TODO convert gob to JSON
30 |
31 | `$ am-relay --help`
32 |
33 | ```bash
34 | Usage: am-relay [--debug] []
35 |
36 | Options:
37 | --debug Enable debugging for asyncmachine
38 | --help, -h display this help and exit
39 |
40 | Commands:
41 | rotate-dbg Rotate dbg protocol with fragmented dump files
42 | ```
43 |
44 | `$ am-relay rotate-dbg --help`
45 |
46 | ```text
47 | Usage: am-relay rotate-dbg [--listen-addr LISTEN-ADDR] [--fwd-addr FWD-ADDR] [--interval-tx INTERVAL-TX]
48 | [--interval-duration INTERVAL-DURATION] [--output OUTPUT] [--dir DIR]
49 |
50 | Options:
51 | --listen-addr LISTEN-ADDR, -l LISTEN-ADDR
52 | Listen address for RPC server [default: localhost:2732]
53 | --fwd-addr FWD-ADDR, -f FWD-ADDR
54 | Address of an RPC server to forward data to (repeatable)
55 | --interval-tx INTERVAL-TX
56 | Amount of transitions to create a dump file [default: 10000]
57 | --interval-duration INTERVAL-DURATION
58 | Amount of human time to create a dump file [default: 24h]
59 | --output OUTPUT, -o OUTPUT
60 | Output file base name [default: am-dbg-dump]
61 | --dir DIR, -d DIR Output directory [default: .]
62 |
63 | Global options:
64 | --debug Enable debugging for asyncmachine
65 | --help, -h display this help and exit
66 | ```
67 |
68 | ## monorepo
69 |
70 | [Go back to the monorepo root](/README.md) to continue reading.
71 |
--------------------------------------------------------------------------------
/examples/raw_strings/raw_strings.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/joho/godotenv"
8 |
9 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
10 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
11 | )
12 |
13 | func init() {
14 | // load .env
15 | _ = godotenv.Load()
16 |
17 | // am-dbg is required for debugging, go run it
18 | // go run github.com/pancsta/asyncmachine-go/tools/cmd/am-dbg@latest
19 | // amhelp.EnableDebugging(false)
20 | // amhelp.SetEnvLogLevel(am.LogOps)
21 | }
22 |
23 | func main() {
24 | // init the state machine
25 | mach := am.New(nil, am.Schema{
26 | "ProcessingFile": { // async
27 | Remove: am.S{"FileProcessed"},
28 | },
29 | "FileProcessed": { // async
30 | Remove: am.S{"ProcessingFile"},
31 | },
32 | "InProgress": { // sync
33 | Auto: true,
34 | Require: am.S{"ProcessingFile"},
35 | },
36 | }, &am.Opts{LogLevel: am.LogOps, Id: "raw-strings"})
37 | amhelp.MachDebugEnv(mach)
38 | mach.BindHandlers(&Handlers{
39 | Filename: "README.md",
40 | })
41 | // change the state
42 | mach.Add1("ProcessingFile", nil)
43 |
44 | // wait for completed
45 | select {
46 | case <-time.After(5 * time.Second):
47 | println("timeout")
48 | case <-mach.WhenErr(nil):
49 | println("err:", mach.Err())
50 | case <-mach.When1("FileProcessed", nil):
51 | println("done")
52 | }
53 | }
54 |
55 | type Handlers struct {
56 | Filename string
57 | }
58 |
59 | // negotiation handler
60 | func (h *Handlers) ProcessingFileEnter(e *am.Event) bool {
61 | // read-only ops
62 | // decide if moving fwd is ok
63 | // no blocking
64 | // lock-free critical section
65 | return true
66 | }
67 |
68 | // final handler
69 | func (h *Handlers) ProcessingFileState(e *am.Event) {
70 | // read & write ops
71 | // no blocking
72 | // lock-free critical section
73 | mach := e.Machine()
74 | // tick-based context
75 | stateCtx := mach.NewStateCtx("ProcessingFile")
76 | go func() {
77 | // block in the background, locks needed
78 | if stateCtx.Err() != nil {
79 | return // expired
80 | }
81 | // blocking call
82 | err := processFile(h.Filename, stateCtx)
83 | if err != nil {
84 | mach.AddErr(err, nil)
85 | return
86 | }
87 | // re-check the tick ctx after a blocking call
88 | if stateCtx.Err() != nil {
89 | return // expired
90 | }
91 | // move to the next state in the flow
92 | mach.Add1("FileProcessed", am.A{"beaver": "1"})
93 | }()
94 | }
95 |
96 | func processFile(name string, ctx context.Context) error {
97 | time.Sleep(1 * time.Second)
98 | return nil
99 | }
100 |
--------------------------------------------------------------------------------
/examples/tree_state_source/gen_states/gen_states.go:
--------------------------------------------------------------------------------
1 | /*
2 | # Schema
3 |
4 | Flight1GoToGate
5 |
6 | Status:
7 | Flight1OnTime
8 | Flight1Delayed
9 | Flight1Departed
10 | Flight1Arrived
11 | Flight1Scheduled
12 |
13 | Direction:
14 | Flight1Inbound
15 | Flight1Outbound
16 |
17 | Gates:
18 | Flight1GateUnknown
19 | Flight1Gate1
20 | Flight1Gate2
21 | Flight1Gate3
22 | */
23 |
24 | package main
25 |
26 | import (
27 | "context"
28 | "os"
29 | "strconv"
30 | "strings"
31 |
32 | "github.com/pancsta/asyncmachine-go/tools/generator"
33 | "github.com/pancsta/asyncmachine-go/tools/generator/cli"
34 | )
35 |
36 | const (
37 | flights = 10
38 | gates = 5
39 | )
40 |
41 | func main() {
42 | ctx := context.Background()
43 |
44 | // TODO am.Schema to cli.SFParams converter
45 | params := cli.SFParams{
46 | Name: "Flights",
47 | Inherit: "rpc/worker,basic",
48 | }
49 |
50 | for i := 1; i <= flights; i++ {
51 | numF := strconv.Itoa(i)
52 | flight := "Flight" + numF
53 | params.States += ","
54 | params.Groups += ","
55 |
56 | // Status
57 | params.States += flight + "OnTime:remove(_" + flight + "Status)," +
58 | flight + "Delayed:remove(_" + flight + "Status)," +
59 | flight + "Departed:remove(_" + flight + "Status;" + flight + "GoToGate)," +
60 | flight + "Arrived:remove(_" + flight + "Status)," +
61 | flight + "Scheduled:auto:remove(_" + flight + "Status)," +
62 | // Direction
63 | flight + "Inbound:remove(_" + flight + "Direction)," +
64 | flight + "Outbound:remove(_" + flight + "Direction)," +
65 | // Gates
66 | flight + "GoToGate:require(" + flight + "Outbound)," +
67 | flight + "GateUnknown:auto,"
68 |
69 | // Direction
70 | params.Groups += flight + "Direction(" +
71 | flight + "Inbound;" + flight + "Outbound),"
72 |
73 | // Status
74 | params.Groups += flight + "Status(" +
75 | flight + "OnTime;" + flight + "Delayed;" + flight + "Departed;" + flight + "Arrived;" + flight + "Scheduled),"
76 |
77 | // Gates
78 | params.Groups += flight + "Gates("
79 | for ii := 1; ii <= gates; ii++ {
80 | numG := strconv.Itoa(ii)
81 | gate := flight + "Gate" + numG
82 |
83 | params.States += gate + ":remove(_" + flight + "Gates),"
84 | params.Groups += gate + ";"
85 | }
86 | params.Groups = strings.TrimRight(params.Groups, ";") + "),"
87 | }
88 |
89 | gen, err := generator.NewSchemaGenerator(ctx, params)
90 | if err != nil {
91 | panic(err)
92 | }
93 |
94 | // save to ../states/ss_random_data.go
95 | out := gen.Output()
96 | err = os.WriteFile("../states/ss_flights.go", []byte(out), 0644)
97 | if err != nil {
98 | panic(err)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/examples/benchmark_grpc/server_arpc.go:
--------------------------------------------------------------------------------
1 | package benchmark_grpc
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/pancsta/asyncmachine-go/examples/benchmark_grpc/states"
8 | "github.com/pancsta/asyncmachine-go/pkg/helpers"
9 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
10 | arpc "github.com/pancsta/asyncmachine-go/pkg/rpc"
11 | ssrpc "github.com/pancsta/asyncmachine-go/pkg/rpc/states"
12 | )
13 |
14 | var ss = states.WorkerStates
15 |
16 | type WorkerArpcServer struct {
17 | Worker *Worker
18 | Mach *am.Machine
19 |
20 | RPC *arpc.Server
21 | }
22 |
23 | func NewWorkerArpcServer(
24 | ctx context.Context, addr string, worker *Worker,
25 | ) (*WorkerArpcServer, error) {
26 | // validate
27 | if worker == nil {
28 | return nil, errors.New("worker is nil")
29 | }
30 |
31 | // init
32 | w := &WorkerArpcServer{
33 | Worker: worker,
34 | Mach: am.New(ctx, states.WorkerSchema, &am.Opts{Id: "worker"}),
35 | }
36 |
37 | // verify states and bind to methods
38 | err := w.Mach.VerifyStates(ss.Names())
39 | if err != nil {
40 | return nil, err
41 | }
42 | err = w.Mach.BindHandlers(w)
43 | if err != nil {
44 | return nil, err
45 | }
46 |
47 | // bind to worker
48 | worker.Subscribe(func() {
49 | w.Mach.Add1(ss.Event, nil)
50 | })
51 |
52 | // server init
53 | s, err := arpc.NewServer(ctx, addr, "worker", w.Mach, nil)
54 | if err != nil {
55 | return nil, err
56 | }
57 | w.RPC = s
58 |
59 | // logging
60 | logLvl := am.EnvLogLevel("")
61 | w.RPC.Mach.SemLogger().SetSimple(w.log, logLvl)
62 | w.Mach.SemLogger().SetSimple(w.log, logLvl)
63 |
64 | // telemetry debug
65 | helpers.MachDebugEnv(w.RPC.Mach)
66 | helpers.MachDebugEnv(w.Mach)
67 |
68 | // server start
69 | w.RPC.Start()
70 | <-w.RPC.Mach.When1(ssrpc.ServerStates.RpcReady, nil)
71 |
72 | return w, nil
73 | }
74 |
75 | // methods
76 |
77 | func (w *WorkerArpcServer) log(msg string, args ...any) {
78 | l("arpc-server", msg, args...)
79 | }
80 |
81 | // handlers
82 |
83 | func (w *WorkerArpcServer) CallOpEnter(e *am.Event) bool {
84 | _, ok := e.Args["Op"].(Op)
85 | return ok
86 | }
87 |
88 | func (w *WorkerArpcServer) CallOpState(e *am.Event) {
89 | w.Mach.Remove1(ss.CallOp, nil)
90 |
91 | op := e.Args["Op"].(Op)
92 | w.Worker.CallOp(op)
93 | }
94 |
95 | func (w *WorkerArpcServer) EventState(_ *am.Event) {
96 | w.Mach.Remove1(ss.Event, nil)
97 |
98 | switch w.Worker.GetValue() {
99 | case Value1:
100 | w.Mach.Add1(ss.Value1, nil)
101 | case Value2:
102 | w.Mach.Add1(ss.Value2, nil)
103 | case Value3:
104 | w.Mach.Add1(ss.Value3, nil)
105 | }
106 | }
107 |
108 | func (w *WorkerArpcServer) StartState(_ *am.Event) {
109 | w.Worker.Start()
110 | }
111 |
--------------------------------------------------------------------------------
/pkg/integrations/README.md:
--------------------------------------------------------------------------------
1 | # 🦾 /pkg/integrations
2 |
3 | [`cd /`](/README.md)
4 |
5 | > [!NOTE]
6 | > **asyncmachine-go** is a batteries-included graph control flow library (AOP, actor model, state-machine).
7 |
8 | **/pkg/integrations** is responsible for exposing state machines over various
9 | JSON transports, with currently only NATS being implemented. In the future,
10 | this may include email, Kafka, or HTTP.
11 |
12 | ## JSON
13 |
14 | JSON types cover **mutations**, **subscriptions**, and **data getters**. Each of these is divided in request and
15 | response objects which have a (/docs/jsonschema). Their usage depends on the specific implementation, eg in NATS each
16 | machine has a dedicated subtopic for mutation requests.
17 |
18 | - [`MutationReq`](/pkg/integrations/integrations.go) ([jsonschema](/docs/jsonschema/mutation_req.json))
19 | - [`MutationResp`](/pkg/integrations/integrations.go) ([jsonschema](/docs/jsonschema/mutation_resp.json))
20 | - [`WaitingReq`](/pkg/integrations/integrations.go) ([jsonschema](/docs/jsonschema/waiting_req.json))
21 | - [`WaitingResp`](/pkg/integrations/integrations.go) ([jsonschema](/docs/jsonschema/waiting_resp.json))
22 | - [`GetterReq`](/pkg/integrations/integrations.go) ([jsonschema](/docs/jsonschema/getter_req.json))
23 | - [`GetterResp`](/pkg/integrations/integrations.go) ([jsonschema](/docs/jsonschema/getter_resp.json))
24 |
25 | ```go
26 | import amjson "github.com/pancsta/asyncmachine-go/pkg/integration"
27 |
28 | // create a subscription to Foo
29 | reqSub := integrations.NewWaitingReq()
30 | reqSub.States = am.S{"Foo"}
31 | j, err := json.Marshal(reqSub)
32 | ```
33 |
34 | ## NATS
35 |
36 | [NATS](https://github.com/nats-io/nats-server/) is a popular and high-performance messaging system made in Go.
37 | State machines are exposed under a **topic**, with each state machine also being subscribed to a dedicated subtopic
38 | "\[topic\].\[machineID\]" for mutation requests. Optional [queue] allows to load-balance requests across multiple
39 | subscribers.
40 |
41 | ```go
42 | import am "github.com/pancsta/asyncmachine-go"
43 | import nats "github.com/pancsta/asyncmachine-go/pkg/integration/nats"
44 |
45 | // ...
46 |
47 | // var mach *am.Machine
48 | // var ctx context.Context
49 | // var nc *nats.Conn
50 |
51 | // expose mach under mytopic
52 | _ = nats.ExposeMachine(ctx, mach, nc, "mytopic", "")
53 | // mutate - add Foo
54 | res, _ := nats.Add(ctx, nc, topic, mach.Id(), am.S{"Foo"}, nil)
55 | if res == am.Executed {
56 | print("Foo added to mach")
57 | }
58 | ```
59 |
60 | ## TODO
61 |
62 | - recipient matching (filters similar to the REPL ones)
63 | - better error handling (avoid overreporting)
64 |
65 | ## Status
66 |
67 | Alpha, work in progress, not semantically versioned.
68 |
69 | ## monorepo
70 |
71 | [Go back to the monorepo root](/README.md) to continue reading.
72 |
--------------------------------------------------------------------------------
/examples/arpc/client/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "math/rand"
7 | "os"
8 | "os/signal"
9 | "syscall"
10 | "time"
11 |
12 | "github.com/pancsta/asyncmachine-go/examples/arpc/states"
13 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
14 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
15 | arpc "github.com/pancsta/asyncmachine-go/pkg/rpc"
16 | ssrpc "github.com/pancsta/asyncmachine-go/pkg/rpc/states"
17 | )
18 |
19 | const addr = "localhost:8090"
20 |
21 | var ss = states.ExampleStates
22 |
23 | func init() {
24 | // am-dbg is required for debugging, go run it
25 | // go run github.com/pancsta/asyncmachine-go/tools/cmd/am-dbg@latest
26 | // amhelp.EnableDebugging(true)
27 | // amhelp.SetEnvLogLevel(am.LogOps)
28 | }
29 |
30 | func main() {
31 | ctx, cancel := context.WithCancel(context.Background())
32 | defer cancel()
33 |
34 | // handle exit
35 | sigChan := make(chan os.Signal, 1)
36 | signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
37 | go func() {
38 | <-sigChan
39 | cancel()
40 | }()
41 |
42 | // worker
43 | client, err := newClient(ctx, addr, states.ExampleSchema, ss.Names())
44 | if err != nil {
45 | panic(err)
46 | }
47 |
48 | // connect
49 | client.Start()
50 | err = amhelp.WaitForAll(ctx, 3*time.Second,
51 | client.Mach.When1(ssrpc.ClientStates.Ready, ctx))
52 | fmt.Printf("Connected to aRPC %s\n", client.Addr)
53 |
54 | // randomly mutate the remote worker
55 | t := time.NewTicker(1 * time.Second)
56 | for {
57 | exit := false
58 | select {
59 | case <-t.C:
60 | switch rand.Intn(2) {
61 | case 0:
62 | client.Worker.Add1(ss.Foo, nil)
63 | case 1:
64 | client.Worker.Add1(ss.Bar, nil)
65 | case 2:
66 | client.Worker.Add1(ss.Baz, nil)
67 | }
68 | case <-ctx.Done():
69 | exit = true
70 | }
71 | if exit {
72 | break
73 | }
74 | }
75 |
76 | fmt.Println("bye")
77 | }
78 |
79 | func newClient(
80 | ctx context.Context, addr string, ssSchema am.Schema, ssNames am.S,
81 | ) (*arpc.Client, error) {
82 |
83 | // consumer
84 | consumer := am.New(ctx, ssrpc.ConsumerSchema, nil)
85 | err := consumer.BindHandlers(&clientHandlers{})
86 | if err != nil {
87 | return nil, err
88 | }
89 |
90 | // init
91 | c, err := arpc.NewClient(ctx, addr, "clientid", ssSchema, ssNames, &arpc.ClientOpts{
92 | Consumer: consumer,
93 | })
94 | if err != nil {
95 | panic(err)
96 | }
97 | amhelp.MachDebugEnv(c.Mach)
98 |
99 | return c, nil
100 | }
101 |
102 | type clientHandlers struct {
103 | *am.ExceptionHandler
104 | }
105 |
106 | func (h *clientHandlers) WorkerPayloadState(e *am.Event) {
107 | e.Machine().Remove1(ssrpc.ConsumerStates.WorkerPayload, nil)
108 |
109 | args := arpc.ParseArgs(e.Args)
110 | println("Payload: " + args.Payload.Data.(string))
111 | }
112 |
--------------------------------------------------------------------------------
/pkg/telemetry/telemetry.go:
--------------------------------------------------------------------------------
1 | package telemetry
2 |
3 | import (
4 | "context"
5 | "os"
6 | "regexp"
7 | "strings"
8 |
9 | "github.com/ic2hrmk/promtail"
10 |
11 | ssam "github.com/pancsta/asyncmachine-go/pkg/states"
12 |
13 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
14 | )
15 |
16 | const (
17 | EnvService = "AM_SERVICE"
18 | EnvLokiAddr = "AM_LOKI_ADDR"
19 | EnvOtelTrace = "AM_OTEL_TRACE"
20 | EnvOtelTraceTxs = "AM_OTEL_TRACE_TXS"
21 | EnvOtelTraceArgs = "AM_OTEL_TRACE_ARGS"
22 | EnvOtelTraceNoauto = "AM_OTEL_TRACE_NOAUTO"
23 | )
24 |
25 | func BindLokiLogger(mach am.Api, client promtail.Client) {
26 | labels := map[string]string{
27 | "asyncmachine_id": mach.Id(),
28 | }
29 | mach.SemLogger().EnableId(false)
30 |
31 | amlog := func(level am.LogLevel, msg string, args ...any) {
32 | if strings.HasPrefix(msg, "[error") {
33 | client.LogfWithLabels(promtail.Error, labels, msg, args...)
34 | } else {
35 | switch level {
36 |
37 | case am.LogChanges:
38 | client.LogfWithLabels(promtail.Info, labels, msg, args...)
39 | case am.LogOps:
40 | client.LogfWithLabels(promtail.Info, labels, msg, args...)
41 | case am.LogDecisions:
42 | client.LogfWithLabels(promtail.Debug, labels, msg, args...)
43 | case am.LogEverything:
44 | client.LogfWithLabels(promtail.Debug, labels, msg, args...)
45 | default:
46 | }
47 | }
48 | }
49 |
50 | mach.SemLogger().SetLogger(amlog)
51 | mach.Log("[bind] loki logger")
52 | }
53 |
54 | // everything else than a-z and _
55 | var normalizeRegexp = regexp.MustCompile("[^a-z_0-9]+")
56 |
57 | func NormalizeId(id string) string {
58 | return normalizeRegexp.ReplaceAllString(strings.ToLower(id), "_")
59 | }
60 |
61 | // BindLokiEnv bind Loki logger to [mach], based on environment vars:
62 | // - AM_SERVICE (required)
63 | // - AM_LOKI_ADDR (required)
64 | // This tracer is NOT inherited by submachines.
65 | func BindLokiEnv(mach am.Api) error {
66 | service := os.Getenv(EnvService)
67 | addr := os.Getenv(EnvLokiAddr)
68 | if service == "" || addr == "" {
69 | return nil
70 | }
71 |
72 | // init promtail and bind AM logger
73 | identifiers := map[string]string{
74 | "service_name": NormalizeId(service),
75 | }
76 | pt, err := promtail.NewJSONv1Client(addr, identifiers)
77 | if err != nil {
78 | return err
79 | }
80 |
81 | // flush and close
82 | var dispose am.HandlerDispose = func(id string, _ context.Context) {
83 | pt.Close()
84 | }
85 |
86 | // dispose somehow
87 | register := ssam.DisposedStates.RegisterDisposal
88 | if mach.Has1(register) {
89 | mach.Add1(register, am.A{
90 | ssam.DisposedArgHandler: dispose,
91 | })
92 | } else {
93 | func() {
94 | <-mach.WhenDisposed()
95 | pt.Close()
96 | }()
97 | }
98 |
99 | BindLokiLogger(mach, pt)
100 |
101 | return nil
102 | }
103 |
--------------------------------------------------------------------------------
/pkg/node/states/ss_node_client.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | ssrpc "github.com/pancsta/asyncmachine-go/pkg/rpc/states"
6 | "github.com/pancsta/asyncmachine-go/pkg/states"
7 | . "github.com/pancsta/asyncmachine-go/pkg/states/global"
8 | )
9 |
10 | // ClientStatesDef contains all the states of the Client state machine.
11 | type ClientStatesDef struct {
12 | *am.StatesBase
13 |
14 | Exception string
15 | ErrWorker string
16 | ErrSupervisor string
17 |
18 | // worker
19 |
20 | WorkerDisconnected string
21 | WorkerConnecting string
22 | WorkerConnected string
23 | WorkerDisconnecting string
24 | WorkerReady string
25 | // Ready - Client is connected to a worker and ready to delegate work and
26 | // receive payloads.
27 | Ready string
28 |
29 | // supervisor
30 |
31 | SuperDisconnected string
32 | SuperConnecting string
33 | SuperConnected string
34 | SuperDisconnecting string
35 | // SuperReady - Client is fully connected to the Supervisor.
36 | SuperReady string
37 | // WorkerRequested - Client has requested a Worker from the Supervisor.
38 | WorkerRequested string
39 |
40 | // inherit from BasicStatesDef
41 | *states.BasicStatesDef
42 | // inherit from ConsumerStatesDef
43 | *ssrpc.ConsumerStatesDef
44 | }
45 |
46 | // ClientGroupsDef contains all the state groups of the Client state machine.
47 | type ClientGroupsDef struct {
48 | *states.ConnectedGroupsDef
49 | // TODO?
50 | }
51 |
52 | // ClientSchema represents all relations and properties of ClientStates.
53 | var ClientSchema = SchemaMerge(
54 | // inherit from BasicStruct
55 | states.BasicSchema,
56 | // inherit from ConsumerStruct
57 | ssrpc.ConsumerSchema,
58 | am.Schema{
59 |
60 | // errors
61 |
62 | ssC.ErrWorker: {Require: S{Exception}},
63 | ssC.ErrSupervisor: {Require: S{Exception}},
64 |
65 | // piped
66 |
67 | ssC.SuperDisconnected: {},
68 | ssC.SuperConnecting: {},
69 | ssC.SuperConnected: {},
70 | ssC.SuperDisconnecting: {},
71 | ssC.SuperReady: {},
72 |
73 | ssC.WorkerDisconnected: {},
74 | ssC.WorkerConnecting: {},
75 | ssC.WorkerConnected: {},
76 | ssC.WorkerDisconnecting: {},
77 | ssC.WorkerReady: {Remove: S{ssC.WorkerRequested}},
78 |
79 | // client
80 |
81 | ssC.WorkerRequested: {Require: S{ssC.SuperReady}},
82 | ssC.Ready: {
83 | Auto: true,
84 | Require: S{ssC.WorkerReady},
85 | },
86 | })
87 |
88 | // TODO handlers iface
89 |
90 | // EXPORTS AND GROUPS
91 |
92 | var (
93 | ssC = am.NewStates(ClientStatesDef{})
94 | sgC = am.NewStateGroups(ClientGroupsDef{}, states.ConnectedGroups)
95 |
96 | // ClientStates contains all the states for the Client machine.
97 | ClientStates = ssC
98 | // ClientGroups contains all the state groups for the Client machine.
99 | ClientGroups = sgC
100 | )
101 |
--------------------------------------------------------------------------------
/examples/tree_state_source/Taskfile.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | dotenv: [ '.env' ]
4 |
5 | tasks:
6 | root:
7 | env:
8 | TST_ADDR: :19700
9 | TST_HTTP_ADDR: :18700
10 | cmd: go run ./
11 |
12 | # :19700
13 | rep-1:
14 | env:
15 | TST_PARENT_ADDR: :19700
16 | TST_NAME: rep-1
17 | TST_ADDR: :19701
18 | TST_HTTP_ADDR: :18701
19 | cmd: go run ./
20 |
21 | rep-2:
22 | env:
23 | TST_PARENT_ADDR: :19700
24 | TST_NAME: rep-2
25 | TST_ADDR: :19702
26 | TST_HTTP_ADDR: :18702
27 | cmd: go run ./
28 |
29 | # :19701
30 | rep-1-1:
31 | env:
32 | TST_PARENT_ADDR: :19701
33 | TST_NAME: rep-1-1
34 | TST_ADDR: :19703
35 | TST_HTTP_ADDR: :18703
36 | cmd: go run ./
37 |
38 | rep-1-2:
39 | env:
40 | TST_PARENT_ADDR: :19701
41 | TST_NAME: rep-1-2
42 | TST_ADDR: :19704
43 | TST_HTTP_ADDR: :18704
44 | cmd: go run ./
45 |
46 | # :19702
47 | rep-2-1:
48 | env:
49 | TST_PARENT_ADDR: :19702
50 | TST_NAME: rep-2-1
51 | TST_ADDR: :19705
52 | TST_HTTP_ADDR: :18705
53 | cmd: go run ./
54 |
55 | rep-2-2:
56 | env:
57 | TST_PARENT_ADDR: :19702
58 | TST_NAME: rep-2-2
59 | TST_ADDR: :19706
60 | TST_HTTP_ADDR: :18706
61 | cmd: go run ./
62 |
63 | start:
64 | desc: Start the example
65 | cmd: goreman start
66 |
67 | web-metrics:
68 | dir: ../..
69 | cmd: task web-metrics
70 |
71 | gen-grafanas:
72 | cmds:
73 | - task: gen-grafana-root
74 | - task: gen-grafana-rep
75 | vars:
76 | NAME: rep_1
77 | PARENT: root
78 | - task: gen-grafana-rep
79 | vars:
80 | NAME: rep_2
81 | PARENT: root
82 | - task: gen-grafana-rep
83 | vars:
84 | NAME: rep_1_1
85 | PARENT: rep_1_root
86 | - task: gen-grafana-rep
87 | vars:
88 | NAME: rep_1_2
89 | PARENT: rep_1_root
90 | - task: gen-grafana-rep
91 | vars:
92 | NAME: rep_2_1
93 | PARENT: rep_2_root
94 | - task: gen-grafana-rep
95 | vars:
96 | NAME: rep_2_2
97 | PARENT: rep_2_root
98 |
99 | gen-grafana-root:
100 | internal: true
101 | cmd: go run ../../tools/cmd/am-gen grafana
102 | --name root
103 | --folder tree_state_source
104 | --ids root,rm-root,rs-root-0,rs-root-1,rs-root-2
105 | --grafana-url {{.GRAFANA_URL}}
106 | --source tree_state_source_root
107 |
108 | gen-grafana-rep:
109 | internal: true
110 | cmd: go run ../../tools/cmd/am-gen grafana
111 | --name {{.NAME}}
112 | --folder tree_state_source
113 | --ids {{.NAME}}_{{.PARENT}},rc_{{.NAME}},rm_{{.NAME}},rs_{{.NAME}}_0,rs_{{.NAME}}_1,rs_{{.NAME}}_2
114 | --grafana-url {{.GRAFANA_URL}}
115 | --source tree_state_source_{{.NAME}}
--------------------------------------------------------------------------------
/internal/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/hex"
6 | "fmt"
7 | "os"
8 | "runtime/debug"
9 | "slices"
10 | "strings"
11 |
12 | "github.com/lithammer/dedent"
13 | )
14 |
15 | // EnvAmHostname will override the hostname in all machine names.
16 | const EnvAmHostname = "AM_HOSTNAME"
17 |
18 | // J joins state names into a single string
19 | func J(states []string) string {
20 | return strings.Join(states, " ")
21 | }
22 |
23 | // Jw joins state names into a single string with a separator.
24 | func Jw(states []string, sep string) string {
25 | return strings.Join(states, sep)
26 | }
27 |
28 | func GetVersion() string {
29 | build, ok := debug.ReadBuildInfo()
30 | if !ok {
31 | return "(devel)"
32 | }
33 |
34 | ver := build.Main.Version
35 | if ver == "" {
36 | return "(devel)"
37 | }
38 |
39 | return ver
40 | }
41 |
42 | // TODO remove if that speeds things up
43 | func CloseSafe[T any](ch chan T) {
44 | select {
45 | case <-ch:
46 | default:
47 | close(ch)
48 | }
49 | }
50 |
51 | func SlicesWithout[S ~[]E, E comparable](coll S, el E) S {
52 | idx := slices.Index(coll, el)
53 | ret := slices.Clone(coll)
54 | if idx == -1 {
55 | return ret
56 | }
57 | return slices.Delete(ret, idx, idx+1)
58 | }
59 |
60 | // SlicesNone returns true if none of the elements of coll2 are in coll1.
61 | func SlicesNone[S1 ~[]E, S2 ~[]E, E comparable](col1 S1, col2 S2) bool {
62 | for _, el := range col2 {
63 | if slices.Contains(col1, el) {
64 | return false
65 | }
66 | }
67 | return true
68 | }
69 |
70 | // SlicesEvery returns true if all elements of coll2 are in coll1.
71 | func SlicesEvery[S1 ~[]E, S2 ~[]E, E comparable](col1 S1, col2 S2) bool {
72 | for _, el := range col2 {
73 | if !slices.Contains(col1, el) {
74 | return false
75 | }
76 | }
77 | return true
78 | }
79 |
80 | func SlicesUniq[S ~[]E, E comparable](coll S) S {
81 | var ret S
82 | for _, el := range coll {
83 | if !slices.Contains(ret, el) {
84 | ret = append(ret, el)
85 | }
86 | }
87 | return ret
88 | }
89 |
90 | // RandId generates a random ID of the given length (defaults to 8).
91 | func RandId(strLen int) string {
92 | if strLen == 0 {
93 | strLen = 16
94 | }
95 | strLen = strLen / 2
96 |
97 | id := make([]byte, strLen)
98 | _, err := rand.Read(id)
99 | if err != nil {
100 | return "error"
101 | }
102 |
103 | return hex.EncodeToString(id)
104 | }
105 |
106 | func Hostname() string {
107 | if h := os.Getenv(EnvAmHostname); h != "" {
108 | return h
109 | }
110 | host, _ := os.Hostname()
111 | if host == "" {
112 | host = "localhost"
113 | }
114 |
115 | return host
116 | }
117 |
118 | func Sp(txt string, args ...any) string {
119 | return fmt.Sprintf(dedent.Dedent(strings.Trim(txt, "\n")), args...)
120 | }
121 |
122 | func P(txt string, args ...any) {
123 | fmt.Printf(dedent.Dedent(strings.Trim(txt, "\n")), args...)
124 | }
125 |
--------------------------------------------------------------------------------
/deploy/web-metrics/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | prometheus:
4 | image: prom/prometheus:v2.37.9
5 | container_name: prometheus
6 | user: root
7 | ports:
8 | - "9090:9090"
9 | command:
10 | - '--config.file=/etc/prometheus/prometheus.yaml'
11 | volumes:
12 | - ./prometheus.yaml:/etc/prometheus/prometheus.yaml:ro
13 | restart: unless-stopped
14 |
15 | grafana:
16 | image: grafana/grafana-oss:latest
17 | container_name: grafana
18 | ports:
19 | - "3000:3000"
20 | volumes:
21 | - grafana_data:/var/lib/grafana
22 | environment:
23 | - GF_PATHS_PROVISIONING=/etc/grafana/provisioning
24 | - GF_AUTH_ANONYMOUS_ENABLED=true
25 | - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
26 | restart: unless-stopped
27 | #password: root123
28 | entrypoint:
29 | - sh
30 | - -euc
31 | - |
32 | mkdir -p /etc/grafana/provisioning/datasources
33 | cat < /etc/grafana/provisioning/datasources/ds.yaml
34 | apiVersion: 1
35 | datasources:
36 | - name: Prometheus
37 | type: prometheus
38 | access: proxy
39 | orgId: 1
40 | url: http://prometheus:9090
41 | basicAuth: false
42 | isDefault: true
43 | version: 1
44 | editable: false
45 | - name: Loki
46 | type: loki
47 | access: proxy
48 | orgId: 1
49 | url: http://loki:3100
50 | basicAuth: false
51 | isDefault: false
52 | version: 1
53 | editable: false
54 | EOF
55 | /run.sh
56 |
57 | pushgateway:
58 | image: prom/pushgateway
59 | container_name: pushgateway
60 | ports:
61 | - "9091:9091"
62 | restart: unless-stopped
63 |
64 | # TODO update https://docs.google.com/document/d/18B1yTMewRft2N0nW9K-ecVRTt5VaNgnrPTW1eL236t4/edit?tab=t.0#heading=h.5j21da1bep6t
65 |
66 | jaeger:
67 | image: jaegertracing/all-in-one:1.71.0
68 | container_name: jaeger
69 | environment:
70 | - COLLECTOR_OTLP_ENABLED=true
71 | ports:
72 | - "16686:16686"
73 | - "4317:4317"
74 | restart: unless-stopped
75 |
76 | loki:
77 | image: grafana/loki:3.1.1
78 | container_name: loki
79 | ports:
80 | - "3100:3100"
81 | volumes:
82 | - ./loki.yaml:/etc/loki/local-config.yaml:ro
83 | command: -config.file=/etc/loki/local-config.yaml
84 | restart: unless-stopped
85 |
86 | otel-collector:
87 | image: otel/opentelemetry-collector:0.111.0
88 | container_name: otel-collector
89 | ports:
90 | # - "4317:4317"
91 | - "4318:4318"
92 | - "55681:55681"
93 | volumes:
94 | - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml:ro
95 | command: [ "--config", "/etc/otel-collector-config.yaml" ]
96 | restart: unless-stopped
97 |
98 | volumes:
99 | grafana_data: { }
100 |
--------------------------------------------------------------------------------
/docs/jsonschema/getter_resp.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json-schema.org/draft/2020-12/schema",
3 | "$id": "https://github.com/pancsta/asyncmachine-go/pkg/integrations/getter-resp",
4 | "$ref": "#/$defs/GetterResp",
5 | "$defs": {
6 | "Clock": {
7 | "additionalProperties": {
8 | "type": "integer"
9 | },
10 | "type": "object"
11 | },
12 | "GetterResp": {
13 | "properties": {
14 | "kind": {
15 | "$ref": "#/$defs/Kind",
16 | "description": "The kind of the response."
17 | },
18 | "mach_id": {
19 | "type": "string",
20 | "description": "The ID of the state machine."
21 | },
22 | "time": {
23 | "$ref": "#/$defs/Time",
24 | "description": "The ticks of the passed states"
25 | },
26 | "time_sum": {
27 | "type": "integer",
28 | "description": "The sum of ticks of the passed states"
29 | },
30 | "clocks": {
31 | "$ref": "#/$defs/Clock",
32 | "description": "The named clocks of the passed states"
33 | },
34 | "tags": {
35 | "items": {
36 | "type": "string"
37 | },
38 | "type": "array",
39 | "description": "The tags of the state machine"
40 | },
41 | "export": {
42 | "$ref": "#/$defs/Serialized",
43 | "description": "The importable version of the state machine"
44 | },
45 | "id": {
46 | "type": "string",
47 | "description": "The ID of the state machine"
48 | },
49 | "parent_id": {
50 | "type": "string",
51 | "description": "The ID of the parent state machine"
52 | }
53 | },
54 | "additionalProperties": false,
55 | "type": "object",
56 | "required": [
57 | "kind"
58 | ],
59 | "description": "GetterResp is a response to GetterReq."
60 | },
61 | "Kind": {
62 | "properties": {
63 | "Value": {
64 | "type": "string"
65 | }
66 | },
67 | "additionalProperties": false,
68 | "type": "object",
69 | "required": [
70 | "Value"
71 | ]
72 | },
73 | "S": {
74 | "items": {
75 | "type": "string"
76 | },
77 | "type": "array"
78 | },
79 | "Serialized": {
80 | "properties": {
81 | "id": {
82 | "type": "string"
83 | },
84 | "time": {
85 | "$ref": "#/$defs/Time"
86 | },
87 | "state_names": {
88 | "$ref": "#/$defs/S"
89 | }
90 | },
91 | "additionalProperties": false,
92 | "type": "object",
93 | "required": [
94 | "id",
95 | "time",
96 | "state_names"
97 | ]
98 | },
99 | "Time": {
100 | "items": {
101 | "type": "integer"
102 | },
103 | "type": "array"
104 | }
105 | }
106 | }
--------------------------------------------------------------------------------
/config/terminalizer.yml:
--------------------------------------------------------------------------------
1 | # Specify a command to be executed
2 | # like `/bin/bash -l`, `ls`, or any other commands
3 | # the default is bash for Linux
4 | # or powershell.exe for Windows
5 | command: go run ./internal/cmd/am-dbg-video
6 |
7 | # Specify the current working directory path
8 | # the default is the current working directory path
9 | cwd: null
10 |
11 | # Export additional ENV variables
12 | env:
13 | RECORDING: true
14 |
15 | # Explicitly set the number of columns
16 | # or use `auto` to take the current
17 | # number of columns of your shell
18 | cols: 135
19 |
20 | # Explicitly set the number of rows
21 | # or use `auto` to take the current
22 | # number of rows of your shell
23 | rows: 40
24 |
25 | # Amount of times to repeat GIF
26 | # If value is -1, play once
27 | # If value is 0, loop indefinitely
28 | # If value is a positive number, loop n times
29 | repeat: 0
30 |
31 | # Quality
32 | # 1 - 100
33 | quality: 100
34 |
35 | # Delay between frames in ms
36 | # If the value is `auto` use the actual recording delays
37 | frameDelay: auto
38 |
39 | # Maximum delay between frames in ms
40 | # Ignored if the `frameDelay` isn't set to `auto`
41 | # Set to `auto` to prevent limiting the max idle time
42 | maxIdleTime: 2000
43 |
44 | # The surrounding frame box
45 | # The `type` can be null, window, floating, or solid`
46 | # To hide the title use the value null
47 | # Don't forget to add a backgroundColor style with a null as type
48 | frameBox:
49 | type: null
50 | title: null
51 | style:
52 | border: 0px black solid
53 | background: #231f20
54 | # boxShadow: none
55 | # margin: 0px
56 |
57 | # Add a watermark image to the rendered gif
58 | # You need to specify an absolute path for
59 | # the image on your machine or a URL, and you can also
60 | # add your own CSS styles
61 | watermark:
62 | imagePath: null
63 | style:
64 | position: absolute
65 | right: 15px
66 | bottom: 15px
67 | width: 100px
68 | opacity: 0.9
69 |
70 | # Cursor style can be one of
71 | # `block`, `underline`, or `bar`
72 | cursorStyle: block
73 |
74 | # Font family
75 | # You can use any font that is installed on your machine
76 | # in CSS-like syntax
77 | fontFamily: "Liberation Mono, Monaco, Lucida Console, Ubuntu Mono, Monospace"
78 |
79 | # The size of the font
80 | fontSize: 10
81 |
82 | # The height of lines
83 | lineHeight: 1
84 |
85 | # The spacing between letters
86 | letterSpacing: 0
87 |
88 | # Theme
89 | theme:
90 | background: "#231f20"
91 | foreground: "#afafaf"
92 | cursor: "#c7c7c7"
93 | black: "#232628"
94 | red: "#fc4384"
95 | green: "#b3e33b"
96 | yellow: "#ffa727"
97 | blue: "#75dff2"
98 | magenta: "#ae89fe"
99 | cyan: "#708387"
100 | white: "#d5d5d0"
101 | brightBlack: "#626566"
102 | brightRed: "#ff7fac"
103 | brightGreen: "#c8ed71"
104 | brightYellow: "#ebdf86"
105 | brightBlue: "#75dff2"
106 | brightMagenta: "#ae89fe"
107 | brightCyan: "#b1c6ca"
108 | brightWhite: "#f9f9f4"
109 |
--------------------------------------------------------------------------------
/pkg/states/ss_disposed.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | "fmt"
5 |
6 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
7 | )
8 |
9 | // DisposedStatesDef contains all the states of the Disposed state machine.
10 | // Required states:
11 | // - Start
12 | type DisposedStatesDef struct {
13 | *am.StatesBase
14 |
15 | // RegisterDisposal registers a disposal handler passed under the
16 | // DisposedArgHandler key.
17 | RegisterDisposal string
18 | // Disposing indicates that the machine is during the disposal process.
19 | Disposing string
20 | // Disposed indicates that the machine has disposed allocated resoruces
21 | // and is ready to be garbage collected by calling [am.Machine.Dispose].
22 | Disposed string
23 | }
24 |
25 | // DisposedGroupsDef contains all the state groups Disposed state machine.
26 | type DisposedGroupsDef struct {
27 | Disposed S
28 | }
29 |
30 | // DisposedSchema represents all relations and properties of DisposedStates.
31 | var DisposedSchema = am.Schema{
32 | ssD.RegisterDisposal: {Multi: true},
33 | ssD.Disposing: {Remove: sgD.Disposed},
34 | ssD.Disposed: {Remove: SAdd(sgD.Disposed, S{ssB.Start})},
35 | }
36 |
37 | // EXPORTS AND GROUPS
38 |
39 | var (
40 | ssD = am.NewStates(DisposedStatesDef{})
41 | sgD = am.NewStateGroups(DisposedGroupsDef{
42 | Disposed: S{ssD.RegisterDisposal, ssD.Disposing, ssD.Disposed},
43 | })
44 |
45 | // DisposedStates contains all the states for the Disposed machine.
46 | DisposedStates = ssD
47 | // DisposedGroups contains all the state groups for the Disposed machine.
48 | DisposedGroups = sgD
49 | )
50 |
51 | // handlers
52 |
53 | // DisposedArgHandler is the key for the disposal handler passed to the
54 | // RegisterDisposal state. It needs to contain the EXPLICIT type of
55 | // am.HandlerDispose, eg
56 | //
57 | // var dispose am.HandlerDispose = func(id string, ctx *am.StateCtx) {
58 | // // ...
59 | // }
60 | var DisposedArgHandler = "DisposedArgHandler"
61 |
62 | type DisposedHandlers struct {
63 | // DisposedHandlers is a list of handler for pkg/states.DisposedStates
64 | DisposedHandlers []am.HandlerDispose
65 | }
66 |
67 | func (h *DisposedHandlers) RegisterDisposalEnter(e *am.Event) bool {
68 | fn, ok := e.Args[DisposedArgHandler].(am.HandlerDispose)
69 | ret := ok && fn != nil
70 | // avoid errs on check mutations
71 | if !ret && !e.IsCheck {
72 | err := fmt.Errorf("%w: DisposedArgHandler invalid", am.ErrInvalidArgs)
73 | e.Machine().AddErr(err, nil)
74 | }
75 |
76 | return ret
77 | }
78 |
79 | func (h *DisposedHandlers) RegisterDisposalState(e *am.Event) {
80 | // TODO ability to deregister a disposal handler (by ref)
81 | fn := e.Args[DisposedArgHandler].(am.HandlerDispose)
82 | h.DisposedHandlers = append(h.DisposedHandlers, fn)
83 | }
84 |
85 | // DisposingState triggers a disposal procedure, but does NOT dispose the
86 | // machine.
87 | func (h *DisposedHandlers) DisposingState(e *am.Event) {
88 | mach := e.Machine()
89 | ctx := mach.NewStateCtx(ssD.Disposing)
90 |
91 | // unblock
92 | go func() {
93 | for _, fn := range h.DisposedHandlers {
94 | if ctx.Err() != nil {
95 | return // expired
96 | }
97 | fn(mach.Id(), ctx)
98 | }
99 |
100 | mach.Add1(ssD.Disposed, nil)
101 | }()
102 | }
103 |
--------------------------------------------------------------------------------
/examples/fsm/fsm_test.go:
--------------------------------------------------------------------------------
1 | // Based on https://en.wikipedia.org/wiki/Finite-state_machine
2 | //
3 | // === RUN TestTurnstile
4 | // [state] +Locked
5 | // fsm_test.go:74: Push into (Locked:1)
6 | // fsm_test.go:79: Coin into (Locked:1)
7 | // [state] +InputCoin
8 | // [state] -InputCoin
9 | // [state] +Unlocked -Locked
10 | // fsm_test.go:85: Coin into (Unlocked:1)
11 | // [state] +InputCoin
12 | // [state] -InputCoin
13 | // fsm_test.go:91: Push into (Unlocked:1)
14 | // [state] +InputPush
15 | // [state] -InputPush
16 | // [state] +Locked -Unlocked
17 | // fsm_test.go:96: End with (Locked:3)
18 | // --- PASS: TestTurnstile (0.00s)
19 | // PASS
20 |
21 | package fsm
22 |
23 | import (
24 | "context"
25 | "testing"
26 | "time"
27 |
28 | "github.com/stretchr/testify/assert"
29 |
30 | "github.com/joho/godotenv"
31 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
32 | )
33 |
34 | func init() {
35 | // load .env
36 | _ = godotenv.Load()
37 |
38 | // am-dbg is required for debugging, go run it
39 | // go run github.com/pancsta/asyncmachine-go/tools/cmd/am-dbg@latest
40 | // amhelp.EnableDebugging(false)
41 | // amhelp.SetEnvLogLevel(am.LogOps)
42 | }
43 |
44 | // state enum pkg
45 |
46 | var (
47 | states = am.Schema{
48 | // input states
49 | InputPush: {},
50 | InputCoin: {},
51 |
52 | // "state" states
53 | Locked: {
54 | Auto: true,
55 | Remove: groupUnlocked,
56 | },
57 | Unlocked: {Remove: groupUnlocked},
58 | }
59 |
60 | groupUnlocked = am.S{Locked, Unlocked}
61 |
62 | InputPush = "InputPush"
63 | InputCoin = "InputCoin"
64 | Locked = "Locked"
65 | Unlocked = "Unlocked"
66 |
67 | Names = am.S{InputPush, InputCoin, Locked, Unlocked}
68 | )
69 |
70 | // handlers
71 |
72 | type Turnstile struct{}
73 |
74 | func (t *Turnstile) InputPushEnter(e *am.Event) bool {
75 | return e.Machine().Is1(Unlocked)
76 | }
77 |
78 | func (t *Turnstile) InputPushState(e *am.Event) {
79 | e.Machine().Remove1(InputPush, nil)
80 | e.Machine().Add1(Locked, nil)
81 | }
82 |
83 | func (t *Turnstile) InputCoinState(e *am.Event) {
84 | e.Machine().Remove1(InputCoin, nil)
85 | e.Machine().Add1(Unlocked, nil)
86 | }
87 |
88 | // example
89 |
90 | func TestTurnstile(t *testing.T) {
91 | mach := am.New(context.Background(), states, &am.Opts{
92 | Id: "turnstile",
93 | DontPanicToException: true,
94 | DontLogId: true,
95 | LogLevel: am.LogChanges,
96 | HandlerTimeout: time.Minute,
97 | })
98 |
99 | _ = mach.BindHandlers(&Turnstile{})
100 | _ = mach.VerifyStates(Names)
101 |
102 | // start
103 | mach.Add1(Locked, nil)
104 | assert.True(t, mach.Is1(Locked))
105 |
106 | t.Logf("Push into %s", mach)
107 | mach.Add1(InputPush, nil)
108 | assert.True(t, mach.Is1(Locked))
109 |
110 | // coin
111 | t.Logf("Coin into %s", mach)
112 | mach.Add1(InputCoin, nil)
113 | assert.True(t, mach.Not1(Locked))
114 | assert.True(t, mach.Is1(Unlocked))
115 |
116 | // coin
117 | t.Logf("Coin into %s", mach)
118 | mach.Add1(InputCoin, nil)
119 | assert.True(t, mach.Not1(Locked))
120 | assert.True(t, mach.Is1(Unlocked))
121 |
122 | // push
123 | t.Logf("Push into %s", mach)
124 | mach.Add1(InputPush, nil)
125 | assert.True(t, mach.Not1(Unlocked))
126 | assert.True(t, mach.Is1(Locked))
127 |
128 | t.Logf("End with %s", mach)
129 | }
130 |
--------------------------------------------------------------------------------
/pkg/history/bbolt/bbolt_test.go:
--------------------------------------------------------------------------------
1 | package bbolt
2 |
3 | import (
4 | "context"
5 | "os"
6 | "testing"
7 |
8 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
9 | "github.com/stretchr/testify/require"
10 | "go.etcd.io/bbolt"
11 |
12 | amhist "github.com/pancsta/asyncmachine-go/pkg/history"
13 | testhist "github.com/pancsta/asyncmachine-go/pkg/history/test"
14 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
15 | amss "github.com/pancsta/asyncmachine-go/pkg/states"
16 | )
17 |
18 | var ss = amss.BasicStates
19 |
20 | func TestBboltRead(t *testing.T) {
21 | if amhelp.IsTestRunner() {
22 | t.Skip("skipping debug test")
23 | }
24 |
25 | // init memory
26 | db, err := NewDb("")
27 | require.NoError(t, err)
28 |
29 | // list
30 | // require.NoError(t, db.View(func(tx *bbolt.Tx) error {
31 | // b := tx.Bucket([]byte("MyMach1"))
32 | // bTime := b.Bucket([]byte(BuckTimes))
33 | // c := bTime.Cursor()
34 | // for k, v := c.First(); k != nil; k, v = c.Next() {
35 | // t.Logf("key: %s, value: %s", k, v)
36 | // }
37 | // return nil
38 | // }))
39 |
40 | // single
41 | require.NoError(t, db.View(func(tx *bbolt.Tx) error {
42 | b := tx.Bucket([]byte("MyMach1"))
43 | bTime := b.Bucket([]byte(BuckTimes))
44 | t.Logf("key: %d, value: %s", 5, bTime.Get(itob(5)))
45 | return nil
46 | }))
47 | }
48 |
49 | func TestBboltTrack(t *testing.T) {
50 | os.Remove("amhist.db")
51 | debug := false
52 | // debug := true
53 |
54 | // configs
55 | rounds := 50
56 | onErr := func(err error) {
57 | t.Error(err)
58 | }
59 | cfg := Config{
60 | QueueBatch: 10,
61 | BaseConfig: amhist.BaseConfig{
62 | Log: debug,
63 | TrackedStates: am.S{ss.Start},
64 | MaxRecords: 30,
65 | },
66 | EncJson: debug,
67 | }
68 |
69 | // init basic machine
70 | ctx := context.Background()
71 | mach := am.New(ctx, amss.BasicSchema, &am.Opts{Id: "MyMach1"})
72 |
73 | // init memory
74 | db, err := NewDb("")
75 | require.NoError(t, err)
76 | defer func() {
77 | t.Logf("write time: %v", db.Stats().TxStats.WriteTime)
78 | }()
79 |
80 | mem, err := NewMemory(ctx, db, mach, cfg, onErr)
81 | require.NoError(t, err)
82 | defer func() {
83 | require.NoError(t, mem.Dispose())
84 | }()
85 |
86 | // common test
87 | testhist.AssertBasics(t, mem, rounds)
88 |
89 | // GC test
90 | mem.checkGc()
91 | testhist.AssertGc(t, mem, rounds)
92 | }
93 |
94 | func TestBboltTrackMany(t *testing.T) {
95 | os.Remove("amhist.db")
96 | debug := false
97 | // debug := true
98 |
99 | // configs
100 | rounds := 50000
101 | onErr := func(err error) {
102 | t.Error(err)
103 | }
104 | cfg := Config{
105 | BaseConfig: amhist.BaseConfig{
106 | Log: debug,
107 | TrackedStates: am.S{ss.Start},
108 | MaxRecords: 10e6,
109 | },
110 | EncJson: debug,
111 | }
112 |
113 | // init basic machine
114 | ctx := context.Background()
115 | mach := am.New(ctx, amss.BasicSchema, &am.Opts{Id: "MyMach1"})
116 |
117 | // init memory
118 | db, err := NewDb("")
119 | require.NoError(t, err)
120 | defer func() {
121 | t.Logf("write time: %v", db.Stats().TxStats.WriteTime)
122 | }()
123 |
124 | mem, err := NewMemory(ctx, db, mach, cfg, onErr)
125 | require.NoError(t, err)
126 | defer func() {
127 | require.NoError(t, mem.Dispose())
128 | }()
129 |
130 | // common test
131 | testhist.AssertBasics(t, mem, rounds)
132 | }
133 |
--------------------------------------------------------------------------------
/examples/arpc/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "os/signal"
8 | "syscall"
9 | "time"
10 |
11 | "github.com/pancsta/asyncmachine-go/examples/arpc/states"
12 | amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
13 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
14 | arpc "github.com/pancsta/asyncmachine-go/pkg/rpc"
15 | ssrpc "github.com/pancsta/asyncmachine-go/pkg/rpc/states"
16 | )
17 |
18 | const addr = "localhost:8090"
19 |
20 | var ss = states.ExampleStates
21 |
22 | func init() {
23 | // am-dbg is required for debugging, go run it
24 | // go run github.com/pancsta/asyncmachine-go/tools/cmd/am-dbg@latest
25 | // amhelp.EnableDebugging(true)
26 | // amhelp.SetEnvLogLevel(am.LogOps)
27 | }
28 |
29 | func main() {
30 | ctx, cancel := context.WithCancel(context.Background())
31 | defer cancel()
32 |
33 | // handle exit
34 | sigChan := make(chan os.Signal, 1)
35 | signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
36 | go func() {
37 | <-sigChan
38 | cancel()
39 | }()
40 |
41 | // worker
42 | worker, err := newWorker(ctx)
43 | if err != nil {
44 | panic(err)
45 | }
46 |
47 | // arpc
48 | server, err := newServer(ctx, addr, worker)
49 | if err != nil {
50 | panic(err)
51 | }
52 |
53 | // server start
54 | server.Start()
55 | err = amhelp.WaitForAll(ctx, 3*time.Second,
56 | server.Mach.When1(ssrpc.ServerStates.RpcReady, ctx))
57 | if err != nil {
58 | panic(err)
59 | }
60 | fmt.Printf("Started aRPC server on %s\n", server.Addr)
61 |
62 | // wait for a client
63 | err = amhelp.WaitForAll(ctx, 3*time.Second,
64 | server.Mach.When1(ssrpc.ServerStates.Ready, ctx))
65 |
66 | // periodically send data to the client
67 | t := time.NewTicker(3 * time.Second)
68 | for {
69 | exit := false
70 | select {
71 | case <-t.C:
72 | worker.Add1(ssrpc.WorkerStates.SendPayload, arpc.Pass(&arpc.A{
73 | Name: "mypayload",
74 | Payload: &arpc.ArgsPayload{
75 | Name: "mypayload",
76 | Source: "worker1",
77 | Data: "Hello aRPC",
78 | },
79 | }))
80 | case <-ctx.Done():
81 | exit = true
82 | }
83 | if exit {
84 | break
85 | }
86 | }
87 |
88 | fmt.Println("bye")
89 | }
90 |
91 | func newWorker(ctx context.Context) (*am.Machine, error) {
92 |
93 | // init
94 | worker, err := am.NewCommon(ctx, "worker", states.ExampleSchema, ss.Names(), &workerHandlers{}, nil, nil)
95 | if err != nil {
96 | return nil, err
97 | }
98 | amhelp.MachDebugEnv(worker)
99 |
100 | return worker, nil
101 | }
102 |
103 | func newServer(ctx context.Context, addr string, worker *am.Machine) (*arpc.Server, error) {
104 |
105 | // init
106 | s, err := arpc.NewServer(ctx, addr, worker.Id(), worker, nil)
107 | if err != nil {
108 | panic(err)
109 | }
110 | amhelp.MachDebugEnv(s.Mach)
111 |
112 | // start
113 | s.Start()
114 | err = amhelp.WaitForAll(ctx, 2*time.Second,
115 | s.Mach.When1(ssrpc.ServerStates.RpcReady, ctx))
116 | if err != nil {
117 | return nil, err
118 | }
119 |
120 | return s, nil
121 | }
122 |
123 | type workerHandlers struct {
124 | *am.ExceptionHandler
125 | }
126 |
127 | func (h *workerHandlers) FooState(e *am.Event) {
128 | fmt.Print("FooState")
129 | }
130 |
131 | func (h *workerHandlers) BarState(e *am.Event) {
132 | fmt.Print("BarState")
133 | }
134 |
135 | func (h *workerHandlers) BazState(e *am.Event) {
136 | fmt.Print("BazState")
137 | }
138 |
--------------------------------------------------------------------------------
/examples/tree_state_source/README.md:
--------------------------------------------------------------------------------
1 | #
/example/tree_state_source
2 |
3 | [`cd /`](/README.md)
4 |
5 | > [!NOTE]
6 | > **asyncmachine-go** is a batteries-included graph control flow library (AOP, actor model, state-machine).
7 |
8 | State source of flight statuses - in the real world this data-oriented problem should be modelled using composition and
9 | handler delegation, but it's flat in this example for simplicity and research purposes.
10 |
11 | 
12 |
13 | ## States
14 |
15 | The repo version has 5 flights and 3 gates (149 states). The amount of flights and gates can be adjusted and
16 | re-generated using `go run ./gen_states`. Full states file is available at [`states/ss_flights.go`](/examples/tree_state_source/states/ss_flights.go).
17 | Sample definition without relations can be found below.
18 |
19 | ```go
20 | // FlightsStatesDef contains all the states of the Flights state machine.
21 | type FlightsStatesDef struct {
22 | *am.StatesBase
23 |
24 | Flight1OnTime string
25 | Flight1Delayed string
26 | Flight1Departed string
27 | Flight1Arrived string
28 | Flight1Scheduled string
29 | Flight1Inbound string
30 | Flight1Outbound string
31 | Flight1GoToGate string
32 | Flight1GateUnknown string
33 | Flight1Gate1 string
34 | Flight1Gate2 string
35 | Flight1Gate3 string
36 | Flight1Gate4 string
37 | Flight1Gate5 string
38 |
39 | // ...
40 |
41 | // inherit from BasicStatesDef
42 | *ssam.BasicStatesDef
43 | // inherit from rpc/WorkerStatesDef
44 | *ssrpc.WorkerStatesDef
45 | }
46 | ```
47 |
48 | ## Actors
49 |
50 | - root
51 | - replicant L1
52 | - replicant L2
53 |
54 | Scenario:
55 |
56 | - root process runs the flight-status state machine
57 | - replicants of the 1st level connect to the root
58 | - replicants of the 2st level connect to replicants of the 1st level
59 | - state clocks flow from the root to replicants
60 | - http clients GET active states from the 2nd level replicants
61 | - all state machines are connected to am-dbg, prometheus, and loki
62 |
63 | ## Running
64 |
65 | From the monorepo root:
66 |
67 | 1. `task web-metrics`
68 | 2. `task gen-grafana-tree-state-source`
69 | 3. `task start`
70 | 4. [http://localhost:3000](http://localhost:3000) -> Dashboards -> tree-state-source
71 |
72 | ## Possibilities
73 |
74 | - L1 and L2 replicants can execute handlers locally (TODO)
75 | - root-L1 can have shorter push interval than L1-L2
76 |
77 | ## Benchmark
78 |
79 | HTTP endpoints have been benchmarked using [go-wrk](https://github.com/tsliwowicz/go-wrk), see [/examples/benchmark_state_source](/examples/benchmark_state_source/README.md)
80 | for more info.
81 |
82 | ## Config
83 |
84 | Config available in the `.env` file.
85 |
86 | ```bash
87 | # grafana sync
88 |
89 | GRAFANA_TOKEN=
90 | GRAFANA_URL=http://localhost:3000
91 |
92 | # metrics
93 |
94 | PUSH_GATEWAY_URL=http://localhost:9091
95 | LOKI_ADDR=localhost:3100
96 | AM_LOG=3
97 |
98 | # AM debugging
99 |
100 | #AM_DEBUG=1
101 | #AM_DBG_ADDR=:6831
102 | #AM_HEALTHCHECK=1
103 |
104 | #AM_RPC_LOG_SERVER=1
105 | #AM_RPC_LOG_CLIENT=1
106 | #AM_RPC_LOG_MUX=1
107 |
108 | #AM_NODE_LOG_SUPERVISOR=1
109 | #AM_NODE_LOG_CLIENT=1
110 | #AM_NODE_LOG_WORKER=1
111 | ```
112 |
113 | ## monorepo
114 |
115 | [Go back to the monorepo root](/README.md) to continue reading.
116 |
--------------------------------------------------------------------------------
/tools/debugger/types/dbg_types.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | "strconv"
7 | "strings"
8 | "time"
9 |
10 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
11 | )
12 |
13 | type MachAddress struct {
14 | MachId string
15 | TxId string
16 | MachTime uint64
17 | HumanTime time.Time
18 | // TODO support step
19 | Step int
20 | // TODO support queue ticks
21 | QueueTick uint64
22 | }
23 |
24 | type MachTime struct {
25 | Id string
26 | Time uint64
27 | }
28 |
29 | func (a *MachAddress) Clone() *MachAddress {
30 | return &MachAddress{
31 | MachId: a.MachId,
32 | TxId: a.TxId,
33 | Step: a.Step,
34 | MachTime: a.MachTime,
35 | }
36 | }
37 |
38 | func (a *MachAddress) String() string {
39 | if a.TxId != "" {
40 | u := fmt.Sprintf("mach://%s/%s", a.MachId, a.TxId)
41 | if a.Step != 0 {
42 | u += fmt.Sprintf("/%d", a.Step)
43 | }
44 | if a.MachTime != 0 {
45 | u += fmt.Sprintf("/t%d", a.MachTime)
46 | }
47 |
48 | return u
49 | }
50 | if a.MachTime != 0 {
51 | return fmt.Sprintf("mach://%s/t%d", a.MachId, a.MachTime)
52 | }
53 | if !a.HumanTime.IsZero() {
54 | return fmt.Sprintf("mach://%s/%s", a.MachId, a.HumanTime)
55 | }
56 |
57 | return fmt.Sprintf("mach://%s", a.MachId)
58 | }
59 |
60 | func ParseMachUrl(u string) (*MachAddress, error) {
61 | // TODO merge parsing with addr bar
62 |
63 | up, err := url.Parse(u)
64 | if err != nil {
65 | return nil, err
66 | } else if up.Host == "" {
67 | return nil, fmt.Errorf("host missing in: %s", u)
68 | }
69 |
70 | addr := &MachAddress{
71 | MachId: up.Host,
72 | }
73 | p := strings.Split(up.Path, "/")
74 | if len(p) > 1 {
75 | addr.TxId = p[1]
76 | }
77 | if len(p) > 2 {
78 | if s, err := strconv.Atoi(p[2]); err == nil {
79 | addr.Step = s
80 | }
81 | }
82 |
83 | return addr, nil
84 | }
85 |
86 | type MsgTxParsed struct {
87 | StatesAdded []int
88 | StatesRemoved []int
89 | StatesTouched []int
90 | // TimeSum is machine time after this transition.
91 | TimeSum uint64
92 | TimeDiff uint64
93 | ReaderEntries []*LogReaderEntryPtr
94 | // Transitions which reported this one as their source
95 | Forks []MachAddress
96 | ForksLabels []string
97 | // QueueTick when this tx should be executed
98 | ResultTick uint64
99 | }
100 |
101 | type MsgSchemaParsed struct {
102 | Groups map[string]am.S
103 | GroupsOrder []string
104 | }
105 |
106 | type LogReaderEntry struct {
107 | Kind LogReaderKind
108 | // states are empty for logReaderWhenQueue
109 | States []int
110 | // CreatedAt is machine time when this entry was created
111 | CreatedAt uint64
112 | // ClosedAt is human time when this entry was closed, so it can be disposed.
113 | ClosedAt time.Time
114 |
115 | // per-type fields
116 |
117 | // Pipe is for logReaderPipeIn, logReaderPipeOut
118 | Pipe am.MutationType
119 | // Mach is for logReaderPipeIn, logReaderPipeOut, logReaderMention
120 | Mach string
121 | // Ticks is for logReaderWhenTime only
122 | Ticks am.Time
123 | // Args is for logReaderWhenArgs only
124 | Args string
125 | // QueueTick is for logReaderWhenQueue only
126 | QueueTick int
127 | }
128 |
129 | type LogReaderEntryPtr struct {
130 | TxId string
131 | EntryIdx int
132 | }
133 |
134 | type LogReaderKind int
135 |
136 | const (
137 | LogReaderCtx LogReaderKind = iota + 1
138 | LogReaderWhen
139 | LogReaderWhenNot
140 | LogReaderWhenTime
141 | LogReaderWhenArgs
142 | LogReaderWhenQueue
143 | LogReaderPipeIn
144 | LogReaderPipeOut
145 | // TODO mentions of machine IDs
146 | // logReaderMention
147 | )
148 |
--------------------------------------------------------------------------------
/pkg/rpc/states/ss_rpc_client.go:
--------------------------------------------------------------------------------
1 | package states
2 |
3 | import (
4 | am "github.com/pancsta/asyncmachine-go/pkg/machine"
5 | "github.com/pancsta/asyncmachine-go/pkg/states"
6 | . "github.com/pancsta/asyncmachine-go/pkg/states/global"
7 | )
8 |
9 | // ClientStatesDef contains all the states of the Client state machine.
10 | type ClientStatesDef struct {
11 | // shadow duplicated StatesBase
12 | *am.StatesBase
13 |
14 | // failsafe
15 |
16 | RetryingCall string
17 | // TODO should be ErrCallRetry, req:Exception
18 | CallRetryFailed string
19 | RetryingConn string
20 | // TODO should be ErrConnRetry, req:Exception
21 | ConnRetryFailed string
22 |
23 | // local docs
24 |
25 | // Ready indicates the remote worker is ready to be used.
26 | Ready string
27 |
28 | // worker delivers
29 |
30 | // WorkerDelivering is an optional indication that the server has started a
31 | // data transmission to the Client.
32 | WorkerDelivering string
33 | // WorkPayload allows the Consumer to bind his handlers and receive data
34 | // from the Client.
35 | WorkerPayload string
36 |
37 | // inherit from SharedStatesDef
38 | *SharedStatesDef
39 | // inherit from ConnectedStatesDef
40 | *states.ConnectedStatesDef
41 | }
42 |
43 | // ClientGroupsDef contains all the state groups of the Client state machine.
44 | type ClientGroupsDef struct {
45 | *SharedGroupsDef
46 | *states.ConnectedGroupsDef
47 | // TODO
48 | }
49 |
50 | // ClientSchema represents all relations and properties of ClientStates.
51 | var ClientSchema = SchemaMerge(
52 | // inherit from SharedStruct
53 | SharedSchema,
54 | // inherit from ConnectedStruct
55 | states.ConnectedSchema,
56 | am.Schema{
57 |
58 | // Try to RetryingConn on ErrNetwork.
59 | ssC.ErrNetwork: {
60 | Require: S{am.StateException},
61 | Remove: S{ssC.Connecting},
62 | },
63 |
64 | ssC.Start: {
65 | Add: S{ssC.Connecting},
66 | Remove: S{ssC.ConnRetryFailed},
67 | },
68 | ssC.Ready: {
69 | Auto: true,
70 | Require: S{ssC.HandshakeDone},
71 | },
72 |
73 | // inject Client states into Connected
74 | ssC.Connected: StateAdd(
75 | states.ConnectedSchema[states.ConnectedStates.Connected],
76 | am.State{
77 | Remove: S{ssC.RetryingConn},
78 | Add: S{ssC.Handshaking},
79 | }),
80 |
81 | // inject Client states into Handshaking
82 | ssC.Handshaking: StateAdd(
83 | SharedSchema[s.Handshaking],
84 | am.State{
85 | Require: S{ssC.Connected},
86 | }),
87 |
88 | // inject Client states into HandshakeDone
89 | ssC.HandshakeDone: am.StateAdd(
90 | SharedSchema[ssC.HandshakeDone], am.State{
91 | // HandshakeDone will depend on Connected.2
92 | Require: S{ssC.Connected},
93 | }),
94 |
95 | // Retrying
96 |
97 | ssC.RetryingCall: {Require: S{ssC.Start}},
98 | ssC.CallRetryFailed: {
99 | Remove: S{ssC.RetryingCall},
100 | Add: S{ssC.ErrNetwork, am.StateException},
101 | },
102 | ssC.RetryingConn: {Require: S{ssC.Start}},
103 | ssC.ConnRetryFailed: {Remove: S{ssC.Start}},
104 |
105 | // worker delivers
106 |
107 | ssC.WorkerDelivering: {
108 | Multi: true,
109 | Require: S{ssC.Connected},
110 | },
111 | ssC.WorkerPayload: {
112 | Multi: true,
113 | Require: S{ssC.Connected},
114 | },
115 | })
116 |
117 | // EXPORTS AND GROUPS
118 |
119 | var (
120 | ssC = am.NewStates(ClientStatesDef{})
121 | sgC = am.NewStateGroups(ClientGroupsDef{}, states.ConnectedGroups,
122 | SharedGroups)
123 |
124 | // ClientStates contains all the states for the Client machine.
125 | ClientStates = ssC
126 | // ClientGroups contains all the state groups for the Client machine.
127 | ClientGroups = sgC
128 | )
129 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | # .goreleaser.yml
2 | # TODO
3 | # • only version: 2 configuration files are supported, yours is version: 0, please update your configuration
4 | # • DEPRECATED: snapshot.name_template should not be used anymore, check https://goreleaser.com/deprecations#snapshotname_template for more info
5 | # • DEPRECATED: archives.format should not be used anymore, check https://goreleaser.com/deprecations#archivesformat for more info
6 | # • DEPRECATED: archives.builds should not be used anymore, check https://goreleaser.com/deprecations#archivesbuilds for more info
7 |
8 | builds:
9 | -
10 | id: "am-dbg"
11 | env:
12 | - CGO_ENABLED=0
13 | - GO111MODULE=on
14 | goos:
15 | - linux
16 | - darwin
17 | - windows
18 | goarch:
19 | - amd64
20 | - arm64
21 | main: ./tools/cmd/am-dbg
22 | binary: am-dbg
23 |
24 | -
25 | id: "am-dbg-ssh"
26 | env:
27 | - CGO_ENABLED=0
28 | - GO111MODULE=on
29 | goos:
30 | - linux
31 | - darwin
32 | - windows
33 | goarch:
34 | - amd64
35 | - arm64
36 | main: ./tools/cmd/am-dbg-ssh
37 | binary: am-dbg-ssh
38 |
39 | -
40 | id: "arpc"
41 | env:
42 | - CGO_ENABLED=0
43 | - GO111MODULE=on
44 | goos:
45 | - linux
46 | - darwin
47 | - windows
48 | goarch:
49 | - amd64
50 | - arm64
51 | main: ./tools/cmd/arpc
52 | binary: arpc
53 |
54 | -
55 | id: "am-relay"
56 | env:
57 | - CGO_ENABLED=0
58 | - GO111MODULE=on
59 | goos:
60 | - linux
61 | - darwin
62 | - windows
63 | goarch:
64 | - amd64
65 | - arm64
66 | main: ./tools/cmd/am-relay
67 | binary: am-relay
68 |
69 | -
70 | id: "am-vis"
71 | env:
72 | - CGO_ENABLED=0
73 | - GO111MODULE=on
74 | goos:
75 | - linux
76 | - darwin
77 | - windows
78 | goarch:
79 | - amd64
80 | - arm64
81 | main: ./tools/cmd/am-vis
82 | binary: am-vis
83 |
84 | archives:
85 | -
86 | id: "am-dbg"
87 | format: tar.gz
88 | name_template: "am-dbg_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
89 | # dont add readmes etc
90 | files: [""]
91 | builds:
92 | - am-dbg
93 |
94 | -
95 | id: "am-dbg-ssh"
96 | format: tar.gz
97 | name_template: "am-dbg-ssh_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
98 | # dont add readmes etc
99 | files: [""]
100 | builds:
101 | - am-dbg
102 | - am-dbg-ssh
103 |
104 | -
105 | id: "arpc"
106 | format: tar.gz
107 | name_template: "arpc_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
108 | # dont add readmes etc
109 | files: [""]
110 | builds:
111 | - arpc
112 |
113 | -
114 | id: "am-relay"
115 | format: tar.gz
116 | name_template: "am-relay_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
117 | # dont add readmes etc
118 | files: [""]
119 | builds:
120 | - am-relay
121 |
122 | -
123 | id: "am-vis"
124 | format: tar.gz
125 | name_template: "am-vis_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
126 | # dont add readmes etc
127 | files: [""]
128 | builds:
129 | - am-vis
130 |
131 | checksum:
132 | name_template: 'checksums.txt'
133 |
134 | release:
135 | github:
136 | owner: pancsta
137 | name: asyncmachine-go
138 | draft: true
139 | replace_existing_draft: true
140 |
141 | changelog:
142 | sort: asc
143 | filters:
144 | exclude:
145 | - '^docs:'
146 | - '^test:'
147 | - '^chore:'
148 | - '^refactor:'
149 | - '^style:'
150 | - '^ci:'
151 | - '^perf:'
152 | - '^revert:'
153 | snapshot:
154 | name_template: "{{ .Tag }}-next"
--------------------------------------------------------------------------------