ask-graph 0.6.1 → 0.7.0
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/CHANGELOG.md +43 -0
- data/lib/ask/graph/runner.rb +38 -22
- data/lib/ask/graph/version.rb +1 -1
- data/lib/ask/graph.rb +46 -13
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8035b4d2c2a93881c2733cb41e33696fe6e7403b2233a709c89ac75e1035323a
|
|
4
|
+
data.tar.gz: 04c96e95ec9b0afa00133f100627250ae26dc87d9208be62b22246f80548eaa7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a365f235bb392a4be69981dd816bc6f961ea68a1fe740e094fe07a4b1a5f7f39839261ba75784b5028a6a82b7add2351e9f10c97695a7a9edeb00b512cf4d67
|
|
7
|
+
data.tar.gz: fabd27780966a9877e4a17b014388fcad7c3c57fdfbe9df8bc433ba15d05960fe3542f09c6f9501e388d147197a5d84c85964b59646f3b245a44cdb03ba13a6d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
+
## [0.7.0] — 2026-07-31
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **`workflow_timeout`** — total runtime cap for the entire workflow. The
|
|
6
|
+
whole run aborts with `Ask::Graph::WorkflowTimeout` if it exceeds the
|
|
7
|
+
limit, regardless of individual step timeouts.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
class MyWorkflow < Ask::Graph
|
|
11
|
+
workflow_timeout 60 # whole workflow must finish within 60s
|
|
12
|
+
step FetchData, timeout: 30
|
|
13
|
+
step ProcessData, timeout: 30
|
|
14
|
+
end
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- **`Ask::Graph.default_workflow_timeout`** — global default total cap for
|
|
18
|
+
all graphs that don't set their own.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **`timeout` renamed to `step_timeout`** — the class-level default timeout
|
|
23
|
+
per step is now `step_timeout`. The inline step option `step X, timeout: 10`
|
|
24
|
+
is unchanged. No alias is kept — the gem is pre-1.0.
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
# Before
|
|
28
|
+
class MyWorkflow < Ask::Graph
|
|
29
|
+
timeout 30
|
|
30
|
+
end
|
|
31
|
+
Ask::Graph.timeout 30
|
|
32
|
+
|
|
33
|
+
# After
|
|
34
|
+
class MyWorkflow < Ask::Graph
|
|
35
|
+
step_timeout 30
|
|
36
|
+
end
|
|
37
|
+
Ask::Graph.default_step_timeout 30
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Tested
|
|
41
|
+
|
|
42
|
+
- 95 tests, 129 assertions, 0 failures
|
|
43
|
+
|
|
1
44
|
## [0.6.1] — 2026-07-27
|
|
2
45
|
|
|
3
46
|
### Fixed
|
data/lib/ask/graph/runner.rb
CHANGED
|
@@ -11,6 +11,9 @@ module Ask
|
|
|
11
11
|
# Error raised when a step times out.
|
|
12
12
|
class StepTimeout < StandardError; end
|
|
13
13
|
|
|
14
|
+
# Error raised when the entire workflow exceeds its total runtime cap.
|
|
15
|
+
class WorkflowTimeout < StandardError; end
|
|
16
|
+
|
|
14
17
|
# Error raised when a step pauses for human approval.
|
|
15
18
|
class Paused < StandardError; end
|
|
16
19
|
|
|
@@ -24,39 +27,30 @@ module Ask
|
|
|
24
27
|
# @param storage [#set, #get, #delete] key-value store for checkpoint data
|
|
25
28
|
# @param hooks [Hash] lifecycle hook method names
|
|
26
29
|
# @param graph_instance [Object] the graph instance (for hooks)
|
|
27
|
-
# @param
|
|
30
|
+
# @param step_timeout [Integer, nil] fallback timeout for steps with no explicit timeout
|
|
31
|
+
# @param workflow_timeout [Integer, nil] total runtime cap for the whole run
|
|
28
32
|
def initialize(declarations, storage: nil,
|
|
29
33
|
hooks: { before_step: [], after_step: [], on_failure: [] },
|
|
30
34
|
graph_instance: nil,
|
|
31
|
-
|
|
35
|
+
step_timeout: nil,
|
|
36
|
+
workflow_timeout: nil)
|
|
32
37
|
@declarations = declarations
|
|
33
38
|
@store = storage
|
|
34
39
|
@hooks = hooks
|
|
35
40
|
@graph = graph_instance
|
|
36
|
-
@
|
|
41
|
+
@step_timeout = step_timeout
|
|
42
|
+
@workflow_timeout = workflow_timeout
|
|
37
43
|
@completed_steps = []
|
|
38
44
|
end
|
|
39
45
|
|
|
40
46
|
def run(context)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
unless condition_met?(decl, context)
|
|
47
|
-
record_completion(idx, :skipped)
|
|
48
|
-
next
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
run_hooks(:before_step, decl, context)
|
|
52
|
-
execute_with_retry(decl, context)
|
|
53
|
-
run_hooks(:after_step, decl, context)
|
|
54
|
-
|
|
55
|
-
record_completion(idx, :completed)
|
|
56
|
-
save_checkpoint(context, idx)
|
|
47
|
+
if @workflow_timeout
|
|
48
|
+
::Timeout.timeout(@workflow_timeout) { run_steps(context) }
|
|
49
|
+
else
|
|
50
|
+
run_steps(context)
|
|
57
51
|
end
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
rescue ::Timeout::Error
|
|
53
|
+
raise WorkflowTimeout, "workflow timed out after #{@workflow_timeout}s"
|
|
60
54
|
end
|
|
61
55
|
|
|
62
56
|
def resume(context, input:)
|
|
@@ -77,6 +71,28 @@ module Ask
|
|
|
77
71
|
|
|
78
72
|
private
|
|
79
73
|
|
|
74
|
+
def run_steps(context)
|
|
75
|
+
resume_index = load_checkpoint(context)
|
|
76
|
+
|
|
77
|
+
@declarations.each_with_index do |decl, idx|
|
|
78
|
+
next if resume_index && idx <= resume_index
|
|
79
|
+
|
|
80
|
+
unless condition_met?(decl, context)
|
|
81
|
+
record_completion(idx, :skipped)
|
|
82
|
+
next
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
run_hooks(:before_step, decl, context)
|
|
86
|
+
execute_with_retry(decl, context)
|
|
87
|
+
run_hooks(:after_step, decl, context)
|
|
88
|
+
|
|
89
|
+
record_completion(idx, :completed)
|
|
90
|
+
save_checkpoint(context, idx)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
context
|
|
94
|
+
end
|
|
95
|
+
|
|
80
96
|
def execute_with_retry(decl, context)
|
|
81
97
|
retries = decl[:retry] || 0
|
|
82
98
|
|
|
@@ -93,7 +109,7 @@ module Ask
|
|
|
93
109
|
end
|
|
94
110
|
|
|
95
111
|
def run_with_timeout(decl, context, timeout_value)
|
|
96
|
-
timeout_value ||= @
|
|
112
|
+
timeout_value ||= @step_timeout
|
|
97
113
|
if timeout_value
|
|
98
114
|
::Timeout.timeout(timeout_value) { execute_step(decl, context) }
|
|
99
115
|
else
|
data/lib/ask/graph/version.rb
CHANGED
data/lib/ask/graph.rb
CHANGED
|
@@ -73,36 +73,68 @@ module Ask
|
|
|
73
73
|
lifecycle_hooks.transform_values(&:dup))
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
# ---
|
|
76
|
+
# --- Timeouts ---
|
|
77
77
|
|
|
78
|
-
# Set or get the default timeout (in seconds) for
|
|
79
|
-
# Steps without an explicit +timeout:+ option
|
|
78
|
+
# Set or get the default timeout (in seconds) for each step in this graph.
|
|
79
|
+
# Steps without an explicit +timeout:+ option inherit this value.
|
|
80
80
|
# Setting on {Ask::Graph} itself applies to all graphs that don't set
|
|
81
81
|
# their own default — a global default.
|
|
82
82
|
#
|
|
83
83
|
# @example Global default for all graphs
|
|
84
|
-
# Ask::Graph.
|
|
84
|
+
# Ask::Graph.default_step_timeout 30
|
|
85
85
|
#
|
|
86
86
|
# @example Per-graph override
|
|
87
87
|
# class MyGraph < Ask::Graph
|
|
88
|
-
#
|
|
88
|
+
# step_timeout 15
|
|
89
89
|
# step FastOp # uses 15s
|
|
90
90
|
# step SlowOp, timeout: 60 # overrides
|
|
91
91
|
# end
|
|
92
92
|
#
|
|
93
93
|
# @param seconds [Integer, nil] timeout in seconds, or nil to clear
|
|
94
|
-
# @return [Integer, nil] the current default timeout for this graph
|
|
95
|
-
def
|
|
94
|
+
# @return [Integer, nil] the current default step timeout for this graph
|
|
95
|
+
def step_timeout(seconds = :not_set)
|
|
96
96
|
if seconds == :not_set
|
|
97
|
-
if instance_variable_defined?(:@
|
|
98
|
-
@
|
|
99
|
-
elsif superclass.respond_to?(:
|
|
100
|
-
superclass.
|
|
97
|
+
if instance_variable_defined?(:@step_timeout)
|
|
98
|
+
@step_timeout
|
|
99
|
+
elsif superclass.respond_to?(:step_timeout)
|
|
100
|
+
superclass.step_timeout
|
|
101
101
|
end
|
|
102
102
|
else
|
|
103
|
-
@
|
|
103
|
+
@step_timeout = seconds
|
|
104
104
|
end
|
|
105
105
|
end
|
|
106
|
+
alias default_step_timeout step_timeout
|
|
107
|
+
|
|
108
|
+
# Set or get the total runtime cap (in seconds) for the entire graph.
|
|
109
|
+
# The whole workflow aborts with {Ask::Graph::WorkflowTimeout} if it
|
|
110
|
+
# exceeds this limit, regardless of individual step timeouts.
|
|
111
|
+
# Setting on {Ask::Graph} itself applies to all graphs that don't set
|
|
112
|
+
# their own default — a global default.
|
|
113
|
+
#
|
|
114
|
+
# @example Global default for all graphs
|
|
115
|
+
# Ask::Graph.default_workflow_timeout 60
|
|
116
|
+
#
|
|
117
|
+
# @example Per-graph override
|
|
118
|
+
# class MyGraph < Ask::Graph
|
|
119
|
+
# workflow_timeout 30
|
|
120
|
+
# step FetchData, timeout: 10
|
|
121
|
+
# step ProcessData, timeout: 10
|
|
122
|
+
# end
|
|
123
|
+
#
|
|
124
|
+
# @param seconds [Integer, nil] timeout in seconds, or nil to clear
|
|
125
|
+
# @return [Integer, nil] the current workflow timeout for this graph
|
|
126
|
+
def workflow_timeout(seconds = :not_set)
|
|
127
|
+
if seconds == :not_set
|
|
128
|
+
if instance_variable_defined?(:@workflow_timeout)
|
|
129
|
+
@workflow_timeout
|
|
130
|
+
elsif superclass.respond_to?(:workflow_timeout)
|
|
131
|
+
superclass.workflow_timeout
|
|
132
|
+
end
|
|
133
|
+
else
|
|
134
|
+
@workflow_timeout = seconds
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
alias default_workflow_timeout workflow_timeout
|
|
106
138
|
|
|
107
139
|
# --- Storage ---
|
|
108
140
|
|
|
@@ -251,7 +283,8 @@ module Ask
|
|
|
251
283
|
storage: store,
|
|
252
284
|
hooks: hooks,
|
|
253
285
|
graph_instance: self,
|
|
254
|
-
|
|
286
|
+
step_timeout: self.class.step_timeout,
|
|
287
|
+
workflow_timeout: self.class.workflow_timeout)
|
|
255
288
|
@context = nil
|
|
256
289
|
end
|
|
257
290
|
|