async-graph 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +31 -2
- data/.github/workflows/docs.yml +36 -0
- data/.github/workflows/release.yml +20 -3
- data/.gitignore +15 -0
- data/README.erb +26 -1
- data/README.md +26 -1
- data/examples/all_in_one_runner.rb +48 -0
- data/examples/app_graph.rb +2 -0
- data/examples/execute_jobs.rb +12 -12
- data/examples/graph_run.rb +29 -165
- data/examples/reset.rb +10 -18
- data/examples/run.sh +4 -1
- data/lib/async-graph/graph.rb +160 -25
- data/lib/async-graph/graph_validation.rb +83 -0
- data/lib/async-graph/runner.rb +255 -0
- data/lib/async-graph/version.rb +1 -1
- data/lib/async-graph.rb +1 -0
- data/spec/async_graph_spec.rb +152 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9e191c8f8d75367ded4fb71dc739d1c337bc41b158e40577aaf06a054a0321f4
|
|
4
|
+
data.tar.gz: b82e743e0b5119862364200eb8ff558312cb7d0963dd7257e87b38f8895e5924
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a351f8bdbb37c7b7da3d09ce4c63d965d9981d6ebeb97bd7473837909085d186c31e2d1efe2a28e46c9d3bd417b20f1cd851a7a4d44ef817360b026d249f7729
|
|
7
|
+
data.tar.gz: 1ad91c064b0dcd6bafd8bc8f62e987d6b909fb77f713eedadbe2581b6025e47a03d2ee1b36f2f04752718df9efb2fbbd743743ee837012053957634214ed2d7e
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -4,15 +4,44 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
pull_request:
|
|
6
6
|
|
|
7
|
+
env:
|
|
8
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
9
|
+
|
|
7
10
|
jobs:
|
|
8
11
|
test:
|
|
9
12
|
runs-on: ubuntu-latest
|
|
10
13
|
steps:
|
|
11
|
-
- uses: actions/checkout@
|
|
14
|
+
- uses: actions/checkout@v5
|
|
12
15
|
- uses: ruby/setup-ruby@v1
|
|
13
16
|
with:
|
|
14
17
|
ruby-version: "3.4.4"
|
|
15
18
|
bundler-cache: true
|
|
16
19
|
- run: bundle exec rake
|
|
17
|
-
-
|
|
20
|
+
- name: Run RuboCop
|
|
21
|
+
run: |
|
|
22
|
+
mkdir -p results
|
|
23
|
+
set -o pipefail
|
|
24
|
+
git ls-files -z '*.rb' '*.gemspec' Gemfile Rakefile 'bin/*' \
|
|
25
|
+
| xargs -0 bundle exec rubocop --display-cop-names --extra-details --cache false --force-exclusion 2>&1 \
|
|
26
|
+
| tee results/rubocop.log
|
|
27
|
+
- name: Upload RuboCop log
|
|
28
|
+
if: failure()
|
|
29
|
+
uses: actions/upload-artifact@v6
|
|
30
|
+
with:
|
|
31
|
+
name: rubocop-log
|
|
32
|
+
path: results/rubocop.log
|
|
18
33
|
- run: bash examples/run.sh
|
|
34
|
+
docs:
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
defaults:
|
|
37
|
+
run:
|
|
38
|
+
working-directory: docs-site
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v5
|
|
41
|
+
- uses: actions/setup-node@v6
|
|
42
|
+
with:
|
|
43
|
+
node-version: 24
|
|
44
|
+
cache: npm
|
|
45
|
+
cache-dependency-path: docs-site/package-lock.json
|
|
46
|
+
- run: npm ci
|
|
47
|
+
- run: npm run build
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
env:
|
|
14
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v5
|
|
21
|
+
- uses: withastro/action@v5
|
|
22
|
+
with:
|
|
23
|
+
path: docs-site
|
|
24
|
+
node-version: 24
|
|
25
|
+
package-manager: npm@11.6.2
|
|
26
|
+
|
|
27
|
+
deploy:
|
|
28
|
+
needs: build
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
environment:
|
|
31
|
+
name: github-pages
|
|
32
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
33
|
+
steps:
|
|
34
|
+
- name: Deploy to GitHub Pages
|
|
35
|
+
id: deployment
|
|
36
|
+
uses: actions/deploy-pages@v4
|
|
@@ -4,18 +4,35 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches: [main]
|
|
6
6
|
|
|
7
|
+
env:
|
|
8
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
9
|
+
|
|
7
10
|
jobs:
|
|
8
11
|
build_and_publish:
|
|
9
12
|
runs-on: ubuntu-latest
|
|
10
13
|
steps:
|
|
11
|
-
- uses: actions/checkout@
|
|
14
|
+
- uses: actions/checkout@v5
|
|
12
15
|
- uses: ruby/setup-ruby@v1
|
|
13
16
|
with:
|
|
14
17
|
ruby-version: "3.4.4"
|
|
15
|
-
- run:
|
|
18
|
+
- run: bundle install
|
|
19
|
+
- id: gem_version
|
|
20
|
+
name: Check published gem version
|
|
21
|
+
run: |
|
|
22
|
+
current="$(ruby -e 'require_relative "lib/async-graph/version"; print AsyncGraph::VERSION')"
|
|
23
|
+
latest="$(ruby -rjson -ropen-uri -e 'begin; print JSON.parse(URI.open("https://rubygems.org/api/v1/versions/async-graph/latest.json", &:read))["version"]; rescue StandardError; end')"
|
|
24
|
+
echo "current=$current" >> "$GITHUB_OUTPUT"
|
|
25
|
+
echo "latest=$latest" >> "$GITHUB_OUTPUT"
|
|
26
|
+
- name: Publish gem
|
|
27
|
+
if: steps.gem_version.outputs.current != steps.gem_version.outputs.latest
|
|
28
|
+
run: |
|
|
16
29
|
mkdir -p ~/.gem && touch ~/.gem/credentials && chmod 0600 ~/.gem/credentials
|
|
17
30
|
printf -- "---\n:rubygems_api_key: ${API_KEY}\n" > ~/.gem/credentials
|
|
18
|
-
bundle install
|
|
19
31
|
bundle exec rake push
|
|
20
32
|
env:
|
|
21
33
|
API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
|
34
|
+
- name: Skip published version
|
|
35
|
+
if: steps.gem_version.outputs.current == steps.gem_version.outputs.latest
|
|
36
|
+
run: echo "async-graph ${CURRENT_VERSION} is already published; skipping release."
|
|
37
|
+
env:
|
|
38
|
+
CURRENT_VERSION: ${{ steps.gem_version.outputs.current }}
|
data/.gitignore
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
/.bundle/
|
|
2
|
+
.idea/
|
|
2
3
|
/pkg/
|
|
3
4
|
/results/*
|
|
4
5
|
!/results/.gitkeep
|
|
5
6
|
/.idea/workspace.xml
|
|
7
|
+
/docs-site/.astro
|
|
8
|
+
/docs-site/.vscode
|
|
6
9
|
/examples/*.json
|
|
7
10
|
/*.gem
|
|
8
11
|
/*.iml
|
|
12
|
+
/docs-site/dist/
|
|
13
|
+
/docs-site/node_modules/
|
|
14
|
+
/docs-site/npm-debug.log*
|
|
15
|
+
/docs-site/yarn-debug.log*
|
|
16
|
+
/docs-site/yarn-error.log*
|
|
17
|
+
/docs-site/pnpm-debug.log*
|
|
18
|
+
/docs-site/.env
|
|
19
|
+
/docs-site/.env.production
|
|
20
|
+
/docs-site/.DS_Store
|
|
21
|
+
/docs-site/src/assets/houston.webp
|
|
22
|
+
AGENTS.md
|
|
23
|
+
**/.knowledge/
|
data/README.erb
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# AsyncGraph
|
|
2
|
+
Published docs: <https://artyomb.github.io/async-graph/>
|
|
2
3
|
|
|
3
4
|
AsyncGraph is a Ruby runtime for graph-style workflows that suspend on external work,
|
|
4
5
|
store jobs outside the graph, and resume on later passes. It supports:
|
|
5
6
|
|
|
6
7
|
- single-step graph execution
|
|
8
|
+
- runner helpers for opaque persisted run state and fan-out/join bookkeeping
|
|
7
9
|
- barrier joins such as `edge %i[left right], :merge`
|
|
10
|
+
- library-owned join processing for persisted branch tokens
|
|
8
11
|
- `await.call(...)` for one external job
|
|
9
12
|
- `await.all(...)` for multiple parallel jobs in one node
|
|
13
|
+
- graph validation before execution
|
|
10
14
|
|
|
11
15
|
## Installation
|
|
12
16
|
|
|
@@ -42,10 +46,31 @@ resumed.state
|
|
|
42
46
|
# => { user_id: 7, user: { id: 7, name: "Ada" } }
|
|
43
47
|
```
|
|
44
48
|
|
|
49
|
+
For persisted multi-pass execution, `AsyncGraph::Runner` can create and advance a run
|
|
50
|
+
snapshot while your application still owns persistence and external jobs.
|
|
51
|
+
|
|
45
52
|
## Demo
|
|
46
53
|
|
|
47
|
-
The repository includes
|
|
54
|
+
The repository includes two runnable examples in `examples/`:
|
|
55
|
+
|
|
56
|
+
- persisted multi-pass flow with external job persistence:
|
|
48
57
|
|
|
49
58
|
```bash
|
|
50
59
|
bash examples/run.sh
|
|
51
60
|
```
|
|
61
|
+
|
|
62
|
+
- self-contained runner loop with inline `:add` / `:subtract` request handling:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
ruby examples/all_in_one_runner.rb
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Documentation
|
|
69
|
+
|
|
70
|
+
The repository also includes the Starlight source site in `docs-site/`.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cd docs-site
|
|
74
|
+
npm install
|
|
75
|
+
npm run dev
|
|
76
|
+
```
|
data/README.md
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# AsyncGraph
|
|
2
|
+
Published docs: <https://artyomb.github.io/async-graph/>
|
|
2
3
|
|
|
3
4
|
AsyncGraph is a Ruby runtime for graph-style workflows that suspend on external work,
|
|
4
5
|
store jobs outside the graph, and resume on later passes. It supports:
|
|
5
6
|
|
|
6
7
|
- single-step graph execution
|
|
8
|
+
- runner helpers for opaque persisted run state and fan-out/join bookkeeping
|
|
7
9
|
- barrier joins such as `edge %i[left right], :merge`
|
|
10
|
+
- library-owned join processing for persisted branch tokens
|
|
8
11
|
- `await.call(...)` for one external job
|
|
9
12
|
- `await.all(...)` for multiple parallel jobs in one node
|
|
13
|
+
- graph validation before execution
|
|
10
14
|
|
|
11
15
|
## Installation
|
|
12
16
|
|
|
@@ -42,10 +46,31 @@ resumed.state
|
|
|
42
46
|
# => { user_id: 7, user: { id: 7, name: "Ada" } }
|
|
43
47
|
```
|
|
44
48
|
|
|
49
|
+
For persisted multi-pass execution, `AsyncGraph::Runner` can create and advance a run
|
|
50
|
+
snapshot while your application still owns persistence and external jobs.
|
|
51
|
+
|
|
45
52
|
## Demo
|
|
46
53
|
|
|
47
|
-
The repository includes
|
|
54
|
+
The repository includes two runnable examples in `examples/`:
|
|
55
|
+
|
|
56
|
+
- persisted multi-pass flow with external job persistence:
|
|
48
57
|
|
|
49
58
|
```bash
|
|
50
59
|
bash examples/run.sh
|
|
51
60
|
```
|
|
61
|
+
|
|
62
|
+
- self-contained runner loop with inline `:add` / `:subtract` request handling:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
ruby examples/all_in_one_runner.rb
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Documentation
|
|
69
|
+
|
|
70
|
+
The repository also includes the Starlight source site in `docs-site/`.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cd docs-site
|
|
74
|
+
npm install
|
|
75
|
+
npm run dev
|
|
76
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "../lib/async-graph"
|
|
5
|
+
|
|
6
|
+
runner = AsyncGraph::Runner.new(
|
|
7
|
+
AsyncGraph::Graph.new do
|
|
8
|
+
node :calculate do |state, await|
|
|
9
|
+
results = await.all(
|
|
10
|
+
added: [:add, {left: state[:left], right: state[:right]}],
|
|
11
|
+
subtracted: [:subtract, {left: state[:total], right: state[:discount]}]
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
added: results[:added],
|
|
16
|
+
subtracted: results[:subtracted],
|
|
17
|
+
answer: results[:added] - results[:subtracted]
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
set_entry_point :calculate
|
|
22
|
+
set_finish_point :calculate
|
|
23
|
+
end
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
results = {}
|
|
27
|
+
run = runner.start_run state: {left: 7, right: 5, total: 20, discount: 3}
|
|
28
|
+
|
|
29
|
+
until run.finished?
|
|
30
|
+
run = runner.advance_run(
|
|
31
|
+
run: run,
|
|
32
|
+
resolved_for: lambda do |token|
|
|
33
|
+
token[:awaits].each_with_object({}) do |(key, request_id), memo|
|
|
34
|
+
memo[key.to_s] = results[request_id] if results.key?(request_id)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
) do |request|
|
|
38
|
+
results[request.key] =
|
|
39
|
+
case request.kind
|
|
40
|
+
when :add then request.payload[:left] + request.payload[:right]
|
|
41
|
+
when :subtract then request.payload[:left] - request.payload[:right]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
request.key
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
puts JSON.pretty_generate(run.result)
|
data/examples/app_graph.rb
CHANGED
data/examples/execute_jobs.rb
CHANGED
|
@@ -4,25 +4,25 @@ require "json"
|
|
|
4
4
|
|
|
5
5
|
Dir.chdir(__dir__)
|
|
6
6
|
|
|
7
|
-
jobs = JSON.parse(File.read("jobs.json"))
|
|
7
|
+
jobs = JSON.parse(File.read("jobs.json"), symbolize_names: true)
|
|
8
8
|
|
|
9
|
-
jobs.fetch(
|
|
10
|
-
next unless job[
|
|
9
|
+
jobs.fetch(:jobs, []).each do |job|
|
|
10
|
+
next unless job[:status] == "pending"
|
|
11
11
|
|
|
12
|
-
job[
|
|
13
|
-
case job[
|
|
12
|
+
job[:result] =
|
|
13
|
+
case job[:kind]
|
|
14
14
|
when "fetch_profile"
|
|
15
|
-
user_id = job.dig(
|
|
16
|
-
{
|
|
15
|
+
user_id = job.dig(:payload, :user_id)
|
|
16
|
+
{id: user_id, name: "Ada-#{user_id}"}
|
|
17
17
|
when "fetch_score"
|
|
18
|
-
user_id = job.dig(
|
|
19
|
-
{
|
|
18
|
+
user_id = job.dig(:payload, :user_id)
|
|
19
|
+
{score: user_id * 10}
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
next unless job[
|
|
22
|
+
next unless job[:result]
|
|
23
23
|
|
|
24
|
-
job[
|
|
25
|
-
puts "done #{job[
|
|
24
|
+
job[:status] = "done"
|
|
25
|
+
puts "done #{job[:job_uid]} #{job[:kind]}"
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
File.write("jobs.json", JSON.pretty_generate(jobs) + "\n")
|
data/examples/graph_run.rb
CHANGED
|
@@ -3,178 +3,42 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
require_relative "app_graph"
|
|
5
5
|
|
|
6
|
-
def
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def next_job_uid(jobs)
|
|
18
|
-
"job-#{jobs.fetch("jobs", []).size + 1}"
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def queue_job(jobs, request)
|
|
22
|
-
job_uid = next_job_uid(jobs)
|
|
23
|
-
jobs["jobs"] << AsyncGraph.stringify(
|
|
24
|
-
job_uid: job_uid,
|
|
25
|
-
kind: request.kind,
|
|
26
|
-
payload: request.payload,
|
|
27
|
-
status: :pending
|
|
28
|
-
)
|
|
29
|
-
job_uid
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def spawn_tokens(graph_uid, token, state, destinations, next_tokens)
|
|
33
|
-
if destinations.size > 1
|
|
34
|
-
fork_uid = "fork-#{graph_uid}-#{token["token_uid"]}"
|
|
35
|
-
|
|
36
|
-
destinations.each do |edge|
|
|
37
|
-
next_tokens << AsyncGraph.stringify(
|
|
38
|
-
token_uid: "#{token["token_uid"]}.#{edge.branch}",
|
|
39
|
-
node: edge.to,
|
|
40
|
-
state: state,
|
|
41
|
-
fork_uid: fork_uid,
|
|
42
|
-
branch: edge.branch,
|
|
43
|
-
from_node: token["node"],
|
|
44
|
-
awaits: {}
|
|
45
|
-
)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
return nil
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
edge = destinations.first
|
|
52
|
-
return state if edge.to == AsyncGraph::FINISH
|
|
53
|
-
|
|
54
|
-
next_tokens << AsyncGraph.stringify(
|
|
55
|
-
token_uid: token["token_uid"],
|
|
56
|
-
node: edge.to,
|
|
57
|
-
state: state,
|
|
58
|
-
fork_uid: token["fork_uid"],
|
|
59
|
-
branch: token["branch"],
|
|
60
|
-
from_node: token["node"],
|
|
61
|
-
awaits: {}
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
nil
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def process_join(graph_state, token, joins, next_tokens)
|
|
68
|
-
expects = GRAPH.join_for(token["node"])
|
|
69
|
-
bucket_key = "#{token["fork_uid"]}:#{token["node"]}"
|
|
70
|
-
bucket = joins[bucket_key] || {"join_node" => token["node"], "states" => {}}
|
|
71
|
-
bucket["states"][token["from_node"]] = token["state"]
|
|
72
|
-
|
|
73
|
-
missing = expects.map(&:to_s) - bucket["states"].keys
|
|
74
|
-
if missing.empty?
|
|
75
|
-
joins.delete(bucket_key)
|
|
76
|
-
merged_state = bucket["states"].values.reduce({}) do |memo, state|
|
|
77
|
-
memo.merge(AsyncGraph.symbolize(state))
|
|
6
|
+
def advance_graph_state!(graph_state, job_list, jobs_by_uid)
|
|
7
|
+
graph_uid = graph_state.fetch(:graph_uid)
|
|
8
|
+
next_run = RUNNER.advance_run(
|
|
9
|
+
run: graph_state.fetch(:run),
|
|
10
|
+
resolved_for: lambda do |token|
|
|
11
|
+
token.fetch(:awaits, {}).each_with_object({}) do |(key, job_uid), memo|
|
|
12
|
+
job = jobs_by_uid[job_uid]
|
|
13
|
+
memo[key.to_s] = job[:result] if job&.[](:status) == "done"
|
|
14
|
+
end
|
|
78
15
|
end
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
90
|
-
return nil
|
|
16
|
+
) do |request|
|
|
17
|
+
job_uid = "job-#{job_list.size + 1}"
|
|
18
|
+
job = jobs_by_uid[job_uid] = {
|
|
19
|
+
job_uid: job_uid,
|
|
20
|
+
kind: request.kind.to_s,
|
|
21
|
+
payload: request.payload,
|
|
22
|
+
status: "pending"
|
|
23
|
+
}
|
|
24
|
+
job_list << job
|
|
25
|
+
job_uid
|
|
91
26
|
end
|
|
92
27
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
nil
|
|
28
|
+
puts "#{graph_uid} finished" if next_run.finished?
|
|
29
|
+
graph_state[:run] = next_run.to_h
|
|
96
30
|
end
|
|
97
31
|
|
|
98
32
|
Dir.chdir(__dir__)
|
|
99
33
|
|
|
100
|
-
graph_states = JSON.parse(File.read("graph_states.json"))
|
|
101
|
-
jobs = JSON.parse(File.read("jobs.json"))
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if graph_state["status"] == "finished"
|
|
105
|
-
graph_state
|
|
106
|
-
else
|
|
107
|
-
next_tokens = []
|
|
108
|
-
joins = graph_state["joins"] || {}
|
|
109
|
-
final_state = graph_state["result"]
|
|
110
|
-
|
|
111
|
-
graph_state.fetch("tokens", []).each do |token|
|
|
112
|
-
if GRAPH.join?(token["node"]) && token["from_node"]
|
|
113
|
-
process_join(graph_state, token, joins, next_tokens)
|
|
114
|
-
next
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
waiting_jobs = token.fetch("awaits", {}).values
|
|
118
|
-
.filter_map { |job_uid| job_for(jobs, job_uid) }
|
|
119
|
-
.select { |job| job["status"] == "pending" }
|
|
120
|
-
unless waiting_jobs.empty?
|
|
121
|
-
puts "#{graph_state["graph_uid"]}/#{token["token_uid"]} waiting #{waiting_jobs.map { |job| job["job_uid"] }.join(",")}"
|
|
122
|
-
next_tokens << token
|
|
123
|
-
next
|
|
124
|
-
end
|
|
34
|
+
graph_states = JSON.parse(File.read("graph_states.json"), symbolize_names: true)
|
|
35
|
+
jobs = JSON.parse(File.read("jobs.json"), symbolize_names: true)
|
|
36
|
+
job_list = jobs.fetch(:jobs)
|
|
37
|
+
jobs_by_uid = job_list.to_h { |job| [job[:job_uid], job] }
|
|
125
38
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
resolved: resolved_for(token, jobs)
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
case step
|
|
133
|
-
when AsyncGraph::Suspended
|
|
134
|
-
awaits = token.fetch("awaits", {}).dup
|
|
135
|
-
job_uids = step.requests.map do |request|
|
|
136
|
-
awaits[request.key] ||= queue_job(jobs, request)
|
|
137
|
-
end
|
|
138
|
-
puts "#{graph_state["graph_uid"]}/#{token["token_uid"]} suspended #{job_uids.join(",")}"
|
|
139
|
-
next_tokens << AsyncGraph.stringify(
|
|
140
|
-
token_uid: token["token_uid"],
|
|
141
|
-
node: step.node,
|
|
142
|
-
state: step.state,
|
|
143
|
-
fork_uid: token["fork_uid"],
|
|
144
|
-
branch: token["branch"],
|
|
145
|
-
from_node: token["from_node"],
|
|
146
|
-
awaits: awaits
|
|
147
|
-
)
|
|
148
|
-
when AsyncGraph::Advanced
|
|
149
|
-
puts "#{graph_state["graph_uid"]}/#{token["token_uid"]} advanced"
|
|
150
|
-
advanced_state = spawn_tokens(
|
|
151
|
-
graph_state["graph_uid"],
|
|
152
|
-
token,
|
|
153
|
-
step.state,
|
|
154
|
-
step.destinations,
|
|
155
|
-
next_tokens
|
|
156
|
-
)
|
|
157
|
-
final_state = advanced_state if advanced_state
|
|
158
|
-
when AsyncGraph::Finished
|
|
159
|
-
final_state = step.state
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
status = final_state && next_tokens.empty? && joins.empty? ? :finished : :running
|
|
164
|
-
puts "#{graph_state["graph_uid"]} finished" if status == :finished
|
|
165
|
-
|
|
166
|
-
{
|
|
167
|
-
graph_uid: graph_state["graph_uid"],
|
|
168
|
-
status: status,
|
|
169
|
-
tokens: next_tokens,
|
|
170
|
-
joins: joins,
|
|
171
|
-
result: final_state
|
|
172
|
-
}
|
|
173
|
-
end
|
|
174
|
-
end
|
|
39
|
+
graph_states.fetch(:graphs, [])
|
|
40
|
+
.reject { it.dig(:run, :status) == "finished" }
|
|
41
|
+
.each { advance_graph_state!(it, job_list, jobs_by_uid) }
|
|
175
42
|
|
|
176
|
-
File.write(
|
|
177
|
-
"graph_states.json",
|
|
178
|
-
JSON.pretty_generate(AsyncGraph.stringify(graphs: next_graph_states)) + "\n"
|
|
179
|
-
)
|
|
43
|
+
File.write("graph_states.json", JSON.pretty_generate(graph_states) + "\n")
|
|
180
44
|
File.write("jobs.json", JSON.pretty_generate(jobs) + "\n")
|
data/examples/reset.rb
CHANGED
|
@@ -8,24 +8,16 @@ Dir.chdir(__dir__)
|
|
|
8
8
|
File.write(
|
|
9
9
|
"graph_states.json",
|
|
10
10
|
JSON.pretty_generate(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
graph_uid: "graph-2",
|
|
22
|
-
status: :running,
|
|
23
|
-
tokens: [{token_uid: "t1", node: GRAPH.entry, state: {user_id: 8}, fork_uid: nil, branch: nil, from_node: nil, awaits: {}}],
|
|
24
|
-
joins: {},
|
|
25
|
-
result: nil
|
|
26
|
-
}
|
|
27
|
-
]
|
|
28
|
-
)
|
|
11
|
+
graphs: [
|
|
12
|
+
{
|
|
13
|
+
graph_uid: "graph-1",
|
|
14
|
+
run: RUNNER.start_run(state: {user_id: 7}).to_h
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
graph_uid: "graph-2",
|
|
18
|
+
run: RUNNER.start_run(state: {user_id: 8}).to_h
|
|
19
|
+
}
|
|
20
|
+
]
|
|
29
21
|
) + "\n"
|
|
30
22
|
)
|
|
31
23
|
File.write("jobs.json", JSON.pretty_generate(jobs: []) + "\n")
|
data/examples/run.sh
CHANGED
|
@@ -3,7 +3,7 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
cd "$(dirname "$0")"
|
|
5
5
|
|
|
6
|
-
for file in app_graph.rb reset.rb graph_run.rb execute_jobs.rb; do
|
|
6
|
+
for file in app_graph.rb reset.rb graph_run.rb execute_jobs.rb all_in_one_runner.rb; do
|
|
7
7
|
ruby -c "$file"
|
|
8
8
|
done
|
|
9
9
|
|
|
@@ -40,3 +40,6 @@ printf '\n== graph run 5 ==\n'
|
|
|
40
40
|
ruby graph_run.rb
|
|
41
41
|
cat graph_states.json
|
|
42
42
|
cat jobs.json
|
|
43
|
+
|
|
44
|
+
printf '\n== all-in-one runner ==\n'
|
|
45
|
+
ruby all_in_one_runner.rb
|