taski 0.8.3 → 0.9.1
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 +52 -0
- data/README.md +108 -50
- data/docs/GUIDE.md +79 -55
- data/examples/README.md +10 -29
- data/examples/clean_demo.rb +25 -65
- data/examples/large_tree_demo.rb +356 -0
- data/examples/message_demo.rb +0 -1
- data/examples/progress_demo.rb +13 -24
- data/examples/reexecution_demo.rb +8 -44
- data/lib/taski/execution/execution_facade.rb +150 -0
- data/lib/taski/execution/executor.rb +167 -359
- data/lib/taski/execution/fiber_protocol.rb +27 -0
- data/lib/taski/execution/registry.rb +15 -19
- data/lib/taski/execution/scheduler.rb +161 -140
- data/lib/taski/execution/task_observer.rb +41 -0
- data/lib/taski/execution/task_output_router.rb +41 -58
- data/lib/taski/execution/task_wrapper.rb +123 -219
- data/lib/taski/execution/worker_pool.rb +279 -64
- data/lib/taski/logging.rb +105 -0
- data/lib/taski/progress/layout/base.rb +600 -0
- data/lib/taski/progress/layout/filters.rb +126 -0
- data/lib/taski/progress/layout/log.rb +27 -0
- data/lib/taski/progress/layout/simple.rb +166 -0
- data/lib/taski/progress/layout/tags.rb +76 -0
- data/lib/taski/progress/layout/theme_drop.rb +84 -0
- data/lib/taski/progress/layout/tree.rb +300 -0
- data/lib/taski/progress/theme/base.rb +224 -0
- data/lib/taski/progress/theme/compact.rb +58 -0
- data/lib/taski/progress/theme/default.rb +25 -0
- data/lib/taski/progress/theme/detail.rb +48 -0
- data/lib/taski/progress/theme/plain.rb +40 -0
- data/lib/taski/static_analysis/analyzer.rb +5 -17
- data/lib/taski/static_analysis/dependency_graph.rb +19 -1
- data/lib/taski/static_analysis/start_dep_analyzer.rb +400 -0
- data/lib/taski/static_analysis/visitor.rb +1 -39
- data/lib/taski/task.rb +49 -58
- data/lib/taski/task_proxy.rb +59 -0
- data/lib/taski/test_helper/errors.rb +1 -1
- data/lib/taski/test_helper.rb +22 -36
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +62 -61
- data/sig/taski.rbs +194 -203
- metadata +34 -8
- data/examples/section_demo.rb +0 -195
- data/lib/taski/execution/base_progress_display.rb +0 -393
- data/lib/taski/execution/execution_context.rb +0 -390
- data/lib/taski/execution/plain_progress_display.rb +0 -76
- data/lib/taski/execution/simple_progress_display.rb +0 -247
- data/lib/taski/execution/tree_progress_display.rb +0 -643
- data/lib/taski/section.rb +0 -74
|
@@ -4,45 +4,25 @@ require "monitor"
|
|
|
4
4
|
|
|
5
5
|
module Taski
|
|
6
6
|
module Execution
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# @return [TaskTiming] New timing with current time as start
|
|
15
|
-
def self.start_now
|
|
16
|
-
new(start_time: Time.now, end_time: nil)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# @return [TaskTiming] New timing with current time as end
|
|
20
|
-
def with_end_now
|
|
21
|
-
with(end_time: Time.now)
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# TaskWrapper manages the state and synchronization for a single task.
|
|
26
|
-
# In the Producer-Consumer pattern, TaskWrapper does NOT start threads.
|
|
27
|
-
# The Executor controls all scheduling and execution.
|
|
7
|
+
# Manages state and synchronization for a single task instance.
|
|
8
|
+
# Does NOT start threads or fibers — Executor and WorkerPool control scheduling.
|
|
9
|
+
#
|
|
10
|
+
# State transitions (both run and clean phases):
|
|
11
|
+
# pending -> running -> completed | failed
|
|
12
|
+
# pending -> skipped (run-phase only)
|
|
28
13
|
class TaskWrapper
|
|
29
|
-
attr_reader :task, :result, :error, :
|
|
14
|
+
attr_reader :task, :result, :error, :clean_error
|
|
30
15
|
|
|
31
16
|
STATE_PENDING = :pending
|
|
32
17
|
STATE_RUNNING = :running
|
|
33
18
|
STATE_COMPLETED = :completed
|
|
19
|
+
STATE_FAILED = :failed
|
|
20
|
+
STATE_SKIPPED = :skipped
|
|
34
21
|
|
|
35
|
-
|
|
36
|
-
# Create a new TaskWrapper for the given task and registry.
|
|
37
|
-
# Initializes synchronization primitives, state tracking for execution and cleanup, and timing/result/error holders.
|
|
38
|
-
# @param [Object] task - The task instance being wrapped.
|
|
39
|
-
# @param [Object] registry - The registry used to query abort status and coordinate execution.
|
|
40
|
-
# @param [Object, nil] execution_context - Optional execution context used to trigger and report execution and cleanup.
|
|
41
|
-
# @param [Hash, nil] args - User-defined arguments for Task.new usage.
|
|
42
|
-
def initialize(task, registry:, execution_context: nil, args: nil)
|
|
22
|
+
def initialize(task, registry:, execution_facade: nil, args: nil)
|
|
43
23
|
@task = task
|
|
44
24
|
@registry = registry
|
|
45
|
-
@
|
|
25
|
+
@execution_facade = execution_facade
|
|
46
26
|
@args = args
|
|
47
27
|
@result = nil
|
|
48
28
|
@clean_result = nil
|
|
@@ -53,27 +33,18 @@ module Taski
|
|
|
53
33
|
@clean_condition = @monitor.new_cond
|
|
54
34
|
@state = STATE_PENDING
|
|
55
35
|
@clean_state = STATE_PENDING
|
|
56
|
-
@
|
|
57
|
-
@clean_timing = nil
|
|
36
|
+
@waiters = []
|
|
58
37
|
end
|
|
59
38
|
|
|
60
|
-
# @return [Symbol] Current state
|
|
61
39
|
def state
|
|
62
40
|
@monitor.synchronize { @state }
|
|
63
41
|
end
|
|
64
42
|
|
|
65
|
-
|
|
66
|
-
def
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# @return [Boolean] true if task is completed
|
|
71
|
-
def completed?
|
|
72
|
-
state == STATE_COMPLETED
|
|
73
|
-
end
|
|
43
|
+
def pending? = state == STATE_PENDING
|
|
44
|
+
def completed? = state == STATE_COMPLETED
|
|
45
|
+
def failed? = state == STATE_FAILED
|
|
46
|
+
def skipped? = state == STATE_SKIPPED
|
|
74
47
|
|
|
75
|
-
# Resets the wrapper state to allow re-execution.
|
|
76
|
-
# Clears all cached results and returns state to pending.
|
|
77
48
|
def reset!
|
|
78
49
|
@monitor.synchronize do
|
|
79
50
|
@state = STATE_PENDING
|
|
@@ -82,16 +53,11 @@ module Taski
|
|
|
82
53
|
@clean_result = nil
|
|
83
54
|
@error = nil
|
|
84
55
|
@clean_error = nil
|
|
85
|
-
@timing = nil
|
|
86
|
-
@clean_timing = nil
|
|
87
56
|
end
|
|
88
57
|
@task.reset! if @task.respond_to?(:reset!)
|
|
89
58
|
@registry.reset!
|
|
90
59
|
end
|
|
91
60
|
|
|
92
|
-
# Called by user code to get result. Triggers execution if needed.
|
|
93
|
-
# Sets up args if not already set (for Task.new.run usage).
|
|
94
|
-
# @return [Object] The result of task execution
|
|
95
61
|
def run
|
|
96
62
|
with_args_lifecycle do
|
|
97
63
|
trigger_execution_and_wait
|
|
@@ -100,9 +66,6 @@ module Taski
|
|
|
100
66
|
end
|
|
101
67
|
end
|
|
102
68
|
|
|
103
|
-
# Called by user code to clean. Triggers clean execution if needed.
|
|
104
|
-
# Sets up args if not already set (for Task.new.clean usage).
|
|
105
|
-
# @return [Object] The result of cleanup
|
|
106
69
|
def clean
|
|
107
70
|
with_args_lifecycle do
|
|
108
71
|
trigger_clean_and_wait
|
|
@@ -110,23 +73,22 @@ module Taski
|
|
|
110
73
|
end
|
|
111
74
|
end
|
|
112
75
|
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
run
|
|
76
|
+
# Runs execution followed by cleanup. Block is called between phases.
|
|
77
|
+
# @param clean_on_failure [Boolean] When true, clean runs even if run raises.
|
|
78
|
+
# Default is false (clean is skipped on run failure).
|
|
79
|
+
def run_and_clean(clean_on_failure: false, &block)
|
|
80
|
+
facade = ensure_facade
|
|
81
|
+
facade.notify_start # Pre-increment nest_level to prevent double rendering
|
|
82
|
+
run_succeeded = false
|
|
83
|
+
result = run
|
|
84
|
+
run_succeeded = true
|
|
85
|
+
block&.call
|
|
86
|
+
result
|
|
121
87
|
ensure
|
|
122
|
-
clean
|
|
123
|
-
|
|
88
|
+
clean if run_succeeded || clean_on_failure
|
|
89
|
+
facade&.notify_stop # Final decrement and render
|
|
124
90
|
end
|
|
125
91
|
|
|
126
|
-
# Called by user code to get exported value. Triggers execution if needed.
|
|
127
|
-
# Sets up args if not already set (for Task.new usage).
|
|
128
|
-
# @param method_name [Symbol] The name of the exported method
|
|
129
|
-
# @return [Object] The exported value
|
|
130
92
|
def get_exported_value(method_name)
|
|
131
93
|
with_args_lifecycle do
|
|
132
94
|
trigger_execution_and_wait
|
|
@@ -135,138 +97,128 @@ module Taski
|
|
|
135
97
|
end
|
|
136
98
|
end
|
|
137
99
|
|
|
138
|
-
#
|
|
100
|
+
# Atomically resolve the dependency value for a waiting Fiber.
|
|
101
|
+
# Returns a status tuple indicating how the caller should proceed:
|
|
102
|
+
# - [:completed, value] → dependency already done, resume immediately
|
|
103
|
+
# - [:failed, error] → dependency failed, propagate error
|
|
104
|
+
# - [:wait] → dependency running, Fiber parked (will be resumed via thread_queue)
|
|
105
|
+
# - [:start] → dependency was PENDING, now RUNNING (caller must drive it)
|
|
106
|
+
def request_value(method, thread_queue, fiber)
|
|
107
|
+
@monitor.synchronize do
|
|
108
|
+
case @state
|
|
109
|
+
when STATE_COMPLETED
|
|
110
|
+
value = @task.public_send(method)
|
|
111
|
+
[:completed, value]
|
|
112
|
+
when STATE_FAILED
|
|
113
|
+
[:failed, @error]
|
|
114
|
+
when STATE_RUNNING
|
|
115
|
+
@waiters << [thread_queue, fiber, method]
|
|
116
|
+
[:wait]
|
|
117
|
+
else
|
|
118
|
+
# PENDING → atomically transition to RUNNING
|
|
119
|
+
@state = STATE_RUNNING
|
|
120
|
+
@waiters << [thread_queue, fiber, method]
|
|
121
|
+
[:start]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
139
126
|
def mark_running
|
|
140
127
|
@monitor.synchronize do
|
|
141
128
|
return false unless @state == STATE_PENDING
|
|
142
129
|
@state = STATE_RUNNING
|
|
143
|
-
@timing = TaskTiming.start_now
|
|
144
130
|
true
|
|
145
131
|
end
|
|
146
132
|
end
|
|
147
133
|
|
|
148
|
-
# Called by Executor after task.run completes successfully
|
|
149
|
-
# @param result [Object] The result of task execution
|
|
150
134
|
def mark_completed(result)
|
|
151
|
-
|
|
135
|
+
waiters_to_notify = nil
|
|
152
136
|
@monitor.synchronize do
|
|
153
137
|
@result = result
|
|
154
138
|
@state = STATE_COMPLETED
|
|
155
139
|
@condition.broadcast
|
|
140
|
+
waiters_to_notify = @waiters.dup
|
|
141
|
+
@waiters.clear
|
|
156
142
|
end
|
|
157
|
-
|
|
143
|
+
notify_fiber_waiters_completed(waiters_to_notify)
|
|
144
|
+
update_progress(:completed)
|
|
158
145
|
end
|
|
159
146
|
|
|
160
|
-
# Called by Executor when task.run raises an error
|
|
161
|
-
##
|
|
162
|
-
# Marks the task as failed and records the error.
|
|
163
|
-
# Records the provided error, sets the task state to completed, updates the timing end time, notifies threads waiting for completion, and reports the failure to the execution context.
|
|
164
|
-
# @param [Exception] error - The exception raised during task execution.
|
|
165
147
|
def mark_failed(error)
|
|
166
|
-
|
|
148
|
+
waiters_to_notify = nil
|
|
167
149
|
@monitor.synchronize do
|
|
168
150
|
@error = error
|
|
169
|
-
@state =
|
|
151
|
+
@state = STATE_FAILED
|
|
152
|
+
@condition.broadcast
|
|
153
|
+
waiters_to_notify = @waiters.dup
|
|
154
|
+
@waiters.clear
|
|
155
|
+
end
|
|
156
|
+
notify_fiber_waiters_failed(waiters_to_notify, error)
|
|
157
|
+
update_progress(:failed)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def mark_skipped
|
|
161
|
+
@monitor.synchronize do
|
|
162
|
+
return false unless @state == STATE_PENDING
|
|
163
|
+
@state = STATE_SKIPPED
|
|
170
164
|
@condition.broadcast
|
|
171
165
|
end
|
|
172
|
-
|
|
166
|
+
notify_skipped
|
|
167
|
+
true
|
|
173
168
|
end
|
|
174
169
|
|
|
175
|
-
# Called by Executor to mark clean as running
|
|
176
|
-
##
|
|
177
|
-
# Mark the task's cleanup state as running and start timing.
|
|
178
|
-
# @return [Boolean] `true` if the clean state was changed from pending to running, `false` otherwise.
|
|
179
170
|
def mark_clean_running
|
|
180
171
|
@monitor.synchronize do
|
|
181
172
|
return false unless @clean_state == STATE_PENDING
|
|
182
173
|
@clean_state = STATE_RUNNING
|
|
183
|
-
@clean_timing = TaskTiming.start_now
|
|
184
174
|
true
|
|
185
175
|
end
|
|
186
176
|
end
|
|
187
177
|
|
|
188
|
-
# Called by Executor after clean completes
|
|
189
|
-
##
|
|
190
|
-
# Marks the cleanup run as completed, stores the cleanup result, sets the clean state to COMPLETED,
|
|
191
|
-
# notifies any waiters, and reports completion to observers.
|
|
192
|
-
# @param [Object] result - The result of the cleanup operation.
|
|
193
178
|
def mark_clean_completed(result)
|
|
194
|
-
@clean_timing = @clean_timing&.with_end_now
|
|
195
179
|
@monitor.synchronize do
|
|
196
180
|
@clean_result = result
|
|
197
181
|
@clean_state = STATE_COMPLETED
|
|
198
182
|
@clean_condition.broadcast
|
|
199
183
|
end
|
|
200
|
-
update_clean_progress(:
|
|
184
|
+
update_clean_progress(:completed)
|
|
201
185
|
end
|
|
202
186
|
|
|
203
|
-
# Called by Executor when clean raises an error
|
|
204
|
-
##
|
|
205
|
-
# Marks the cleanup as failed by storing the cleanup error, transitioning the cleanup state to completed,
|
|
206
|
-
# notifying any waiters, and reports failure to observers.
|
|
207
|
-
# @param [Exception] error - The exception raised during the cleanup run.
|
|
208
187
|
def mark_clean_failed(error)
|
|
209
|
-
@clean_timing = @clean_timing&.with_end_now
|
|
210
188
|
@monitor.synchronize do
|
|
211
189
|
@clean_error = error
|
|
212
|
-
@clean_state =
|
|
190
|
+
@clean_state = STATE_FAILED
|
|
213
191
|
@clean_condition.broadcast
|
|
214
192
|
end
|
|
215
|
-
update_clean_progress(:
|
|
193
|
+
update_clean_progress(:failed)
|
|
216
194
|
end
|
|
217
195
|
|
|
218
|
-
##
|
|
219
|
-
# Blocks the current thread until the task reaches the completed state.
|
|
220
|
-
#
|
|
221
|
-
# The caller will be suspended until the wrapper's state becomes STATE_COMPLETED.
|
|
222
|
-
# This method does not raise on its own; any errors from task execution are surfaced elsewhere.
|
|
223
196
|
def wait_for_completion
|
|
224
197
|
@monitor.synchronize do
|
|
225
|
-
@condition.wait_until { @state == STATE_COMPLETED }
|
|
198
|
+
@condition.wait_until { @state == STATE_COMPLETED || @state == STATE_FAILED || @state == STATE_SKIPPED }
|
|
226
199
|
end
|
|
227
200
|
end
|
|
228
201
|
|
|
229
|
-
##
|
|
230
|
-
# Blocks the current thread until the task's clean phase reaches the completed state.
|
|
231
|
-
# The caller will be suspended until the wrapper's clean_state becomes STATE_COMPLETED.
|
|
232
202
|
def wait_for_clean_completion
|
|
233
203
|
@monitor.synchronize do
|
|
234
|
-
@clean_condition.wait_until { @clean_state == STATE_COMPLETED }
|
|
204
|
+
@clean_condition.wait_until { @clean_state == STATE_COMPLETED || @clean_state == STATE_FAILED }
|
|
235
205
|
end
|
|
236
206
|
end
|
|
237
207
|
|
|
238
|
-
##
|
|
239
|
-
# Delegates method calls to get_exported_value for exported task methods.
|
|
240
|
-
# @param method_name [Symbol] The method name being called.
|
|
241
|
-
# @param args [Array] Arguments passed to the method.
|
|
242
|
-
# @param block [Proc] Block passed to the method.
|
|
243
|
-
# @return [Object] The exported value for the method.
|
|
244
208
|
def method_missing(method_name, *args, &block)
|
|
245
|
-
if @task.class.
|
|
209
|
+
if @task.class.exported_methods.include?(method_name)
|
|
246
210
|
get_exported_value(method_name)
|
|
247
211
|
else
|
|
248
212
|
super
|
|
249
213
|
end
|
|
250
214
|
end
|
|
251
215
|
|
|
252
|
-
##
|
|
253
|
-
# Returns true if the task class defines the given method.
|
|
254
|
-
# @param method_name [Symbol] The method name to check.
|
|
255
|
-
# @param include_private [Boolean] Whether to include private methods.
|
|
256
|
-
# @return [Boolean] true if the task responds to the method.
|
|
257
216
|
def respond_to_missing?(method_name, include_private = false)
|
|
258
|
-
@task.class.
|
|
217
|
+
@task.class.exported_methods.include?(method_name) || super
|
|
259
218
|
end
|
|
260
219
|
|
|
261
220
|
private
|
|
262
221
|
|
|
263
|
-
##
|
|
264
|
-
# Ensures args are set during block execution, then resets if they weren't set before.
|
|
265
|
-
# This allows Task.new.run usage without requiring explicit args setup.
|
|
266
|
-
# If args are already set (e.g., from Task.run class method), just yields the block.
|
|
267
|
-
# Uses stored @args if set (from Task.new), otherwise uses empty hash.
|
|
268
|
-
# @yield The block to execute with args lifecycle management
|
|
269
|
-
# @return [Object] The result of the block
|
|
270
222
|
def with_args_lifecycle(&block)
|
|
271
223
|
# If args are already set, just execute the block
|
|
272
224
|
return yield if Taski.args
|
|
@@ -277,37 +229,19 @@ module Taski
|
|
|
277
229
|
end
|
|
278
230
|
end
|
|
279
231
|
|
|
280
|
-
##
|
|
281
|
-
# Ensures the task is executed if still pending and waits for completion.
|
|
282
|
-
# If the task is pending, triggers execution (via the configured ExecutionContext when present, otherwise via Executor) outside the monitor; if the task is running, waits until it becomes completed; if already completed, returns immediately.
|
|
283
|
-
# @raise [Taski::TaskAbortException] If the registry requested an abort before execution begins.
|
|
284
232
|
def trigger_execution_and_wait
|
|
285
|
-
trigger_and_wait(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
trigger: ->(ctx) { ctx.trigger_execution(@task.class, registry: @registry) }
|
|
289
|
-
)
|
|
233
|
+
trigger_and_wait(state_accessor: -> { @state }, condition: @condition) do |facade|
|
|
234
|
+
facade.trigger_execution(@task.class, registry: @registry)
|
|
235
|
+
end
|
|
290
236
|
end
|
|
291
237
|
|
|
292
|
-
##
|
|
293
|
-
# Triggers task cleanup through the configured execution mechanism and waits until the cleanup completes.
|
|
294
|
-
#
|
|
295
|
-
# If an ExecutionContext is configured the cleanup is invoked through it; otherwise a fallback executor is used.
|
|
296
|
-
# @raise [Taski::TaskAbortException] if the registry has requested an abort.
|
|
297
238
|
def trigger_clean_and_wait
|
|
298
|
-
trigger_and_wait(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
trigger: ->(ctx) { ctx.trigger_clean(@task.class, registry: @registry) }
|
|
302
|
-
)
|
|
239
|
+
trigger_and_wait(state_accessor: -> { @clean_state }, condition: @clean_condition) do |facade|
|
|
240
|
+
facade.trigger_clean(@task.class, registry: @registry)
|
|
241
|
+
end
|
|
303
242
|
end
|
|
304
243
|
|
|
305
|
-
|
|
306
|
-
# @param state_accessor [Proc] Lambda returning the current state
|
|
307
|
-
# @param condition [MonitorMixin::ConditionVariable] Condition to wait on
|
|
308
|
-
# @param trigger [Proc] Lambda receiving context to trigger execution
|
|
309
|
-
# @raise [Taski::TaskAbortException] If the registry requested an abort
|
|
310
|
-
def trigger_and_wait(state_accessor:, condition:, trigger:)
|
|
244
|
+
def trigger_and_wait(state_accessor:, condition:)
|
|
311
245
|
should_execute = false
|
|
312
246
|
@monitor.synchronize do
|
|
313
247
|
case state_accessor.call
|
|
@@ -315,92 +249,62 @@ module Taski
|
|
|
315
249
|
check_abort!
|
|
316
250
|
should_execute = true
|
|
317
251
|
when STATE_RUNNING
|
|
318
|
-
condition.wait_until { state_accessor.call
|
|
319
|
-
when STATE_COMPLETED
|
|
252
|
+
condition.wait_until { [STATE_COMPLETED, STATE_FAILED, STATE_SKIPPED].include?(state_accessor.call) }
|
|
253
|
+
when STATE_COMPLETED, STATE_FAILED, STATE_SKIPPED
|
|
320
254
|
# Already done
|
|
321
255
|
end
|
|
322
256
|
end
|
|
323
257
|
|
|
324
|
-
if should_execute
|
|
325
|
-
# Execute outside the lock to avoid deadlock
|
|
326
|
-
context = ensure_execution_context
|
|
327
|
-
trigger.call(context)
|
|
328
|
-
# After execution returns, the task is completed
|
|
329
|
-
end
|
|
258
|
+
yield ensure_facade if should_execute
|
|
330
259
|
end
|
|
331
260
|
|
|
332
|
-
##
|
|
333
|
-
# Checks whether the registry has requested an abort and raises an exception to stop starting new tasks.
|
|
334
|
-
# @raise [Taski::TaskAbortException] if `@registry.abort_requested?` is true — raised with the message "Execution aborted - no new tasks will start".
|
|
335
261
|
def check_abort!
|
|
336
262
|
if @registry.abort_requested?
|
|
337
263
|
raise Taski::TaskAbortException, "Execution aborted - no new tasks will start"
|
|
338
264
|
end
|
|
339
265
|
end
|
|
340
266
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
# Returns the existing context if set, otherwise creates a shared context.
|
|
344
|
-
# This enables run and clean phases to share state like runtime dependencies.
|
|
345
|
-
# @return [ExecutionContext] The execution context for this wrapper
|
|
346
|
-
def ensure_execution_context
|
|
347
|
-
@execution_context ||= create_shared_context
|
|
267
|
+
def ensure_facade
|
|
268
|
+
@execution_facade ||= create_shared_facade
|
|
348
269
|
end
|
|
349
270
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
# @return [ExecutionContext] A new execution context
|
|
354
|
-
def create_shared_context
|
|
355
|
-
context = ExecutionContext.new
|
|
356
|
-
progress = Taski.progress_display
|
|
357
|
-
context.add_observer(progress) if progress
|
|
358
|
-
|
|
359
|
-
# Set triggers to reuse this context for nested executions
|
|
360
|
-
context.execution_trigger = ->(task_class, registry) do
|
|
361
|
-
Executor.execute(task_class, registry: registry, execution_context: context)
|
|
362
|
-
end
|
|
363
|
-
context.clean_trigger = ->(task_class, registry) do
|
|
364
|
-
Executor.execute_clean(task_class, registry: registry, execution_context: context)
|
|
365
|
-
end
|
|
271
|
+
def create_shared_facade
|
|
272
|
+
ExecutionFacade.build_default(root_task_class: @task.class)
|
|
273
|
+
end
|
|
366
274
|
|
|
367
|
-
|
|
275
|
+
def notify_skipped
|
|
276
|
+
notify_state_change(previous_state: :pending, current_state: :skipped, phase: :run)
|
|
368
277
|
end
|
|
369
278
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
# @param state [Symbol] The completion state (unused, kept for API consistency).
|
|
374
|
-
# @param duration [Numeric, nil] The execution duration in milliseconds.
|
|
375
|
-
# @param error [Exception, nil] The error if the task failed.
|
|
376
|
-
def update_progress(state, duration: nil, error: nil)
|
|
377
|
-
# Defensive fallback: try to get current context if not set during initialization
|
|
378
|
-
@execution_context ||= ExecutionContext.current
|
|
379
|
-
return unless @execution_context
|
|
279
|
+
def update_progress(state)
|
|
280
|
+
notify_state_change(previous_state: :running, current_state: state, phase: :run)
|
|
281
|
+
end
|
|
380
282
|
|
|
381
|
-
|
|
283
|
+
def update_clean_progress(state)
|
|
284
|
+
notify_state_change(previous_state: :running, current_state: state, phase: :clean)
|
|
382
285
|
end
|
|
383
286
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return unless @execution_context
|
|
287
|
+
def notify_state_change(previous_state:, current_state:, phase:)
|
|
288
|
+
@execution_facade.notify_task_updated(
|
|
289
|
+
@task.class,
|
|
290
|
+
previous_state: previous_state,
|
|
291
|
+
current_state: current_state,
|
|
292
|
+
phase: phase,
|
|
293
|
+
timestamp: Time.now
|
|
294
|
+
)
|
|
295
|
+
end
|
|
394
296
|
|
|
395
|
-
|
|
297
|
+
def notify_fiber_waiters_completed(waiters)
|
|
298
|
+
waiters.each do |thread_queue, fiber, method|
|
|
299
|
+
value = @task.public_send(method)
|
|
300
|
+
thread_queue.push(FiberProtocol::Resume.new(fiber, value))
|
|
301
|
+
end
|
|
396
302
|
end
|
|
397
303
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return unless ENV["TASKI_DEBUG"]
|
|
403
|
-
puts "[TaskWrapper] #{message}"
|
|
304
|
+
def notify_fiber_waiters_failed(waiters, error)
|
|
305
|
+
waiters.each do |thread_queue, fiber, _method|
|
|
306
|
+
thread_queue.push(FiberProtocol::ResumeError.new(fiber, error))
|
|
307
|
+
end
|
|
404
308
|
end
|
|
405
309
|
end
|
|
406
310
|
end
|