taski 0.4.2 → 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 +50 -0
- data/README.md +51 -33
- data/Steepfile +1 -0
- data/docs/GUIDE.md +340 -0
- data/examples/README.md +68 -20
- data/examples/{context_demo.rb → args_demo.rb} +27 -27
- data/examples/clean_demo.rb +204 -0
- data/examples/data_pipeline_demo.rb +3 -3
- data/examples/group_demo.rb +113 -0
- data/examples/nested_section_demo.rb +161 -0
- data/examples/parallel_progress_demo.rb +1 -1
- data/examples/reexecution_demo.rb +93 -80
- data/examples/system_call_demo.rb +56 -0
- data/examples/tree_progress_demo.rb +164 -0
- data/lib/taski/{context.rb → args.rb} +3 -3
- data/lib/taski/execution/execution_context.rb +379 -0
- data/lib/taski/execution/executor.rb +538 -0
- data/lib/taski/execution/registry.rb +26 -2
- data/lib/taski/execution/scheduler.rb +308 -0
- data/lib/taski/execution/task_output_pipe.rb +42 -0
- data/lib/taski/execution/task_output_router.rb +216 -0
- data/lib/taski/execution/task_wrapper.rb +295 -146
- data/lib/taski/execution/tree_progress_display.rb +793 -0
- data/lib/taski/execution/worker_pool.rb +104 -0
- data/lib/taski/section.rb +23 -0
- data/lib/taski/static_analysis/analyzer.rb +4 -2
- data/lib/taski/static_analysis/visitor.rb +86 -5
- data/lib/taski/task.rb +223 -120
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +147 -28
- data/sig/taski.rbs +310 -67
- metadata +17 -8
- data/docs/advanced-features.md +0 -625
- data/docs/api-guide.md +0 -509
- data/docs/error-handling.md +0 -684
- data/lib/taski/execution/coordinator.rb +0 -63
- data/lib/taski/execution/parallel_progress_display.rb +0 -201
|
@@ -22,233 +22,382 @@ module Taski
|
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
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.
|
|
25
28
|
class TaskWrapper
|
|
26
|
-
attr_reader :task, :result
|
|
29
|
+
attr_reader :task, :result, :error, :timing, :clean_error
|
|
27
30
|
|
|
28
31
|
STATE_PENDING = :pending
|
|
29
32
|
STATE_RUNNING = :running
|
|
30
33
|
STATE_COMPLETED = :completed
|
|
31
34
|
|
|
32
|
-
|
|
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
|
+
def initialize(task, registry:, execution_context: nil)
|
|
33
42
|
@task = task
|
|
34
43
|
@registry = registry
|
|
35
|
-
@
|
|
44
|
+
@execution_context = execution_context
|
|
36
45
|
@result = nil
|
|
37
46
|
@clean_result = nil
|
|
38
47
|
@error = nil
|
|
48
|
+
@clean_error = nil
|
|
39
49
|
@monitor = Monitor.new
|
|
40
50
|
@condition = @monitor.new_cond
|
|
41
51
|
@clean_condition = @monitor.new_cond
|
|
42
52
|
@state = STATE_PENDING
|
|
43
53
|
@clean_state = STATE_PENDING
|
|
44
54
|
@timing = nil
|
|
45
|
-
|
|
46
|
-
register_with_progress_display
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# @return [Object] The result of task execution
|
|
50
|
-
def run
|
|
51
|
-
execute_task_if_needed
|
|
52
|
-
raise @error if @error # steep:ignore
|
|
53
|
-
@result
|
|
55
|
+
@clean_timing = nil
|
|
54
56
|
end
|
|
55
57
|
|
|
56
|
-
# @return [
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
@clean_result
|
|
58
|
+
# @return [Symbol] Current state
|
|
59
|
+
def state
|
|
60
|
+
@monitor.synchronize { @state }
|
|
60
61
|
end
|
|
61
62
|
|
|
62
|
-
# @
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
execute_task_if_needed
|
|
66
|
-
raise @error if @error # steep:ignore
|
|
67
|
-
@task.public_send(method_name)
|
|
63
|
+
# @return [Boolean] true if task is pending
|
|
64
|
+
def pending?
|
|
65
|
+
state == STATE_PENDING
|
|
68
66
|
end
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
thread = Thread.new(&block)
|
|
74
|
-
@registry.register_thread(thread)
|
|
68
|
+
# @return [Boolean] true if task is completed
|
|
69
|
+
def completed?
|
|
70
|
+
state == STATE_COMPLETED
|
|
75
71
|
end
|
|
76
72
|
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
def
|
|
73
|
+
# Resets the wrapper state to allow re-execution.
|
|
74
|
+
# Clears all cached results and returns state to pending.
|
|
75
|
+
def reset!
|
|
80
76
|
@monitor.synchronize do
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return
|
|
90
|
-
end
|
|
77
|
+
@state = STATE_PENDING
|
|
78
|
+
@clean_state = STATE_PENDING
|
|
79
|
+
@result = nil
|
|
80
|
+
@clean_result = nil
|
|
81
|
+
@error = nil
|
|
82
|
+
@clean_error = nil
|
|
83
|
+
@timing = nil
|
|
84
|
+
@clean_timing = nil
|
|
91
85
|
end
|
|
86
|
+
@task.reset! if @task.respond_to?(:reset!)
|
|
87
|
+
@registry.reset!
|
|
92
88
|
end
|
|
93
89
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def start_async_execution
|
|
108
|
-
@state = STATE_RUNNING
|
|
109
|
-
@timing = TaskTiming.start_now
|
|
110
|
-
update_progress(:running)
|
|
111
|
-
start_thread_with { execute_task }
|
|
90
|
+
# Called by user code to get result. Triggers execution if needed.
|
|
91
|
+
# Sets up args if not already set (for Task.new.run usage).
|
|
92
|
+
# @return [Object] The result of task execution
|
|
93
|
+
def run
|
|
94
|
+
with_args_lifecycle do
|
|
95
|
+
trigger_execution_and_wait
|
|
96
|
+
raise @error if @error # steep:ignore
|
|
97
|
+
@result
|
|
98
|
+
end
|
|
112
99
|
end
|
|
113
100
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
log_start
|
|
122
|
-
@coordinator.start_dependencies(@task.class)
|
|
123
|
-
wait_for_dependencies
|
|
124
|
-
@result = @task.run
|
|
125
|
-
mark_completed
|
|
126
|
-
log_completion
|
|
127
|
-
rescue Taski::TaskAbortException => e
|
|
128
|
-
@registry.request_abort!
|
|
129
|
-
@error = e
|
|
130
|
-
mark_completed
|
|
131
|
-
rescue => e
|
|
132
|
-
@error = e
|
|
133
|
-
mark_completed
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def wait_for_dependencies
|
|
137
|
-
dependencies = @task.class.cached_dependencies
|
|
138
|
-
return if dependencies.empty?
|
|
139
|
-
|
|
140
|
-
dependencies.each do |dep_class|
|
|
141
|
-
dep_class.exported_methods.each do |method|
|
|
142
|
-
dep_class.public_send(method)
|
|
143
|
-
end
|
|
101
|
+
# Called by user code to clean. Triggers clean execution if needed.
|
|
102
|
+
# Sets up args if not already set (for Task.new.clean usage).
|
|
103
|
+
# @return [Object] The result of cleanup
|
|
104
|
+
def clean
|
|
105
|
+
with_args_lifecycle do
|
|
106
|
+
trigger_clean_and_wait
|
|
107
|
+
@clean_result
|
|
144
108
|
end
|
|
145
109
|
end
|
|
146
110
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
111
|
+
# Called by user code to run and clean. Runs execution followed by cleanup.
|
|
112
|
+
# If run fails, clean is still executed for resource release.
|
|
113
|
+
# Pre-increments progress display nest_level to prevent double rendering.
|
|
114
|
+
# @return [Object] The result of task execution
|
|
115
|
+
def run_and_clean
|
|
116
|
+
context = ensure_execution_context
|
|
117
|
+
context.notify_start # Pre-increment nest_level to prevent double rendering
|
|
118
|
+
run
|
|
119
|
+
ensure
|
|
120
|
+
clean
|
|
121
|
+
context&.notify_stop # Final decrement and render
|
|
153
122
|
end
|
|
154
123
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
124
|
+
# Called by user code to get exported value. Triggers execution if needed.
|
|
125
|
+
# Sets up args if not already set (for Task.new usage).
|
|
126
|
+
# @param method_name [Symbol] The name of the exported method
|
|
127
|
+
# @return [Object] The exported value
|
|
128
|
+
def get_exported_value(method_name)
|
|
129
|
+
with_args_lifecycle do
|
|
130
|
+
trigger_execution_and_wait
|
|
131
|
+
raise @error if @error # steep:ignore
|
|
132
|
+
@task.public_send(method_name)
|
|
133
|
+
end
|
|
158
134
|
end
|
|
159
135
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
@
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
136
|
+
# Called by Executor to mark task as running
|
|
137
|
+
def mark_running
|
|
138
|
+
@monitor.synchronize do
|
|
139
|
+
return false unless @state == STATE_PENDING
|
|
140
|
+
@state = STATE_RUNNING
|
|
141
|
+
@timing = TaskTiming.start_now
|
|
142
|
+
true
|
|
143
|
+
end
|
|
166
144
|
end
|
|
167
145
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
146
|
+
# Called by Executor after task.run completes successfully
|
|
147
|
+
# @param result [Object] The result of task execution
|
|
148
|
+
def mark_completed(result)
|
|
149
|
+
@timing = @timing&.with_end_now
|
|
150
|
+
@monitor.synchronize do
|
|
151
|
+
@result = result
|
|
152
|
+
@state = STATE_COMPLETED
|
|
153
|
+
@condition.broadcast
|
|
176
154
|
end
|
|
177
|
-
|
|
178
|
-
wait_threads.each(&:join)
|
|
155
|
+
update_progress(:completed, duration: @timing&.duration_ms)
|
|
179
156
|
end
|
|
180
157
|
|
|
181
|
-
|
|
158
|
+
# Called by Executor when task.run raises an error
|
|
159
|
+
##
|
|
160
|
+
# Marks the task as failed and records the error.
|
|
161
|
+
# 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.
|
|
162
|
+
# @param [Exception] error - The exception raised during task execution.
|
|
163
|
+
def mark_failed(error)
|
|
182
164
|
@timing = @timing&.with_end_now
|
|
183
165
|
@monitor.synchronize do
|
|
166
|
+
@error = error
|
|
184
167
|
@state = STATE_COMPLETED
|
|
185
168
|
@condition.broadcast
|
|
186
169
|
end
|
|
170
|
+
update_progress(:failed, error: error)
|
|
171
|
+
end
|
|
187
172
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
173
|
+
# Called by Executor to mark clean as running
|
|
174
|
+
##
|
|
175
|
+
# Mark the task's cleanup state as running and start timing.
|
|
176
|
+
# @return [Boolean] `true` if the clean state was changed from pending to running, `false` otherwise.
|
|
177
|
+
def mark_clean_running
|
|
178
|
+
@monitor.synchronize do
|
|
179
|
+
return false unless @clean_state == STATE_PENDING
|
|
180
|
+
@clean_state = STATE_RUNNING
|
|
181
|
+
@clean_timing = TaskTiming.start_now
|
|
182
|
+
true
|
|
192
183
|
end
|
|
193
184
|
end
|
|
194
185
|
|
|
195
|
-
|
|
186
|
+
# Called by Executor after clean completes
|
|
187
|
+
##
|
|
188
|
+
# Marks the cleanup run as completed, stores the cleanup result, sets the clean state to COMPLETED,
|
|
189
|
+
# notifies any waiters, and reports completion to observers.
|
|
190
|
+
# @param [Object] result - The result of the cleanup operation.
|
|
191
|
+
def mark_clean_completed(result)
|
|
192
|
+
@clean_timing = @clean_timing&.with_end_now
|
|
196
193
|
@monitor.synchronize do
|
|
194
|
+
@clean_result = result
|
|
197
195
|
@clean_state = STATE_COMPLETED
|
|
198
196
|
@clean_condition.broadcast
|
|
199
197
|
end
|
|
198
|
+
update_clean_progress(:clean_completed, duration: @clean_timing&.duration_ms)
|
|
200
199
|
end
|
|
201
200
|
|
|
201
|
+
# Called by Executor when clean raises an error
|
|
202
|
+
##
|
|
203
|
+
# Marks the cleanup as failed by storing the cleanup error, transitioning the cleanup state to completed,
|
|
204
|
+
# notifying any waiters, and reports failure to observers.
|
|
205
|
+
# @param [Exception] error - The exception raised during the cleanup run.
|
|
206
|
+
def mark_clean_failed(error)
|
|
207
|
+
@clean_timing = @clean_timing&.with_end_now
|
|
208
|
+
@monitor.synchronize do
|
|
209
|
+
@clean_error = error
|
|
210
|
+
@clean_state = STATE_COMPLETED
|
|
211
|
+
@clean_condition.broadcast
|
|
212
|
+
end
|
|
213
|
+
update_clean_progress(:clean_failed, duration: @clean_timing&.duration_ms, error: error)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
##
|
|
217
|
+
# Blocks the current thread until the task reaches the completed state.
|
|
218
|
+
#
|
|
219
|
+
# The caller will be suspended until the wrapper's state becomes STATE_COMPLETED.
|
|
220
|
+
# This method does not raise on its own; any errors from task execution are surfaced elsewhere.
|
|
202
221
|
def wait_for_completion
|
|
203
|
-
@
|
|
222
|
+
@monitor.synchronize do
|
|
223
|
+
@condition.wait_until { @state == STATE_COMPLETED }
|
|
224
|
+
end
|
|
204
225
|
end
|
|
205
226
|
|
|
227
|
+
##
|
|
228
|
+
# Blocks the current thread until the task's clean phase reaches the completed state.
|
|
229
|
+
# The caller will be suspended until the wrapper's clean_state becomes STATE_COMPLETED.
|
|
206
230
|
def wait_for_clean_completion
|
|
207
|
-
@
|
|
231
|
+
@monitor.synchronize do
|
|
232
|
+
@clean_condition.wait_until { @clean_state == STATE_COMPLETED }
|
|
233
|
+
end
|
|
208
234
|
end
|
|
209
235
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
236
|
+
##
|
|
237
|
+
# Delegates method calls to get_exported_value for exported task methods.
|
|
238
|
+
# @param method_name [Symbol] The method name being called.
|
|
239
|
+
# @param args [Array] Arguments passed to the method.
|
|
240
|
+
# @param block [Proc] Block passed to the method.
|
|
241
|
+
# @return [Object] The exported value for the method.
|
|
242
|
+
def method_missing(method_name, *args, &block)
|
|
243
|
+
if @task.class.method_defined?(method_name)
|
|
244
|
+
get_exported_value(method_name)
|
|
245
|
+
else
|
|
246
|
+
super
|
|
247
|
+
end
|
|
213
248
|
end
|
|
214
249
|
|
|
215
|
-
|
|
216
|
-
|
|
250
|
+
##
|
|
251
|
+
# Returns true if the task class defines the given method.
|
|
252
|
+
# @param method_name [Symbol] The method name to check.
|
|
253
|
+
# @param include_private [Boolean] Whether to include private methods.
|
|
254
|
+
# @return [Boolean] true if the task responds to the method.
|
|
255
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
256
|
+
@task.class.method_defined?(method_name) || super
|
|
217
257
|
end
|
|
218
258
|
|
|
219
|
-
|
|
220
|
-
|
|
259
|
+
private
|
|
260
|
+
|
|
261
|
+
##
|
|
262
|
+
# Ensures args are set during block execution, then resets if they weren't set before.
|
|
263
|
+
# This allows Task.new.run usage without requiring explicit args setup.
|
|
264
|
+
# @yield The block to execute with args lifecycle management
|
|
265
|
+
# @return [Object] The result of the block
|
|
266
|
+
def with_args_lifecycle
|
|
267
|
+
args_was_nil = Taski.args.nil?
|
|
268
|
+
Taski.start_args(options: {}, root_task: @task.class) if args_was_nil
|
|
269
|
+
yield
|
|
270
|
+
ensure
|
|
271
|
+
Taski.reset_args! if args_was_nil
|
|
221
272
|
end
|
|
222
273
|
|
|
223
|
-
|
|
224
|
-
|
|
274
|
+
##
|
|
275
|
+
# Ensures the task is executed if still pending and waits for completion.
|
|
276
|
+
# 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.
|
|
277
|
+
# @raise [Taski::TaskAbortException] If the registry requested an abort before execution begins.
|
|
278
|
+
def trigger_execution_and_wait
|
|
279
|
+
should_execute = false
|
|
280
|
+
@monitor.synchronize do
|
|
281
|
+
case @state
|
|
282
|
+
when STATE_PENDING
|
|
283
|
+
check_abort!
|
|
284
|
+
should_execute = true
|
|
285
|
+
when STATE_RUNNING
|
|
286
|
+
@condition.wait_until { @state == STATE_COMPLETED }
|
|
287
|
+
when STATE_COMPLETED
|
|
288
|
+
# Already done
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
if should_execute
|
|
293
|
+
# Execute outside the lock to avoid deadlock
|
|
294
|
+
# Use ensure_execution_context to create a shared context if not set
|
|
295
|
+
context = ensure_execution_context
|
|
296
|
+
context.trigger_execution(@task.class, registry: @registry)
|
|
297
|
+
# After execution returns, the task is completed
|
|
298
|
+
end
|
|
225
299
|
end
|
|
226
300
|
|
|
227
|
-
|
|
228
|
-
|
|
301
|
+
##
|
|
302
|
+
# Triggers task cleanup through the configured execution mechanism and waits until the cleanup completes.
|
|
303
|
+
#
|
|
304
|
+
# If an ExecutionContext is configured the cleanup is invoked through it; otherwise a fallback executor is used.
|
|
305
|
+
# @raise [Taski::TaskAbortException] if the registry has requested an abort.
|
|
306
|
+
def trigger_clean_and_wait
|
|
307
|
+
should_execute = false
|
|
308
|
+
@monitor.synchronize do
|
|
309
|
+
case @clean_state
|
|
310
|
+
when STATE_PENDING
|
|
311
|
+
check_abort!
|
|
312
|
+
should_execute = true
|
|
313
|
+
when STATE_RUNNING
|
|
314
|
+
@clean_condition.wait_until { @clean_state == STATE_COMPLETED }
|
|
315
|
+
when STATE_COMPLETED
|
|
316
|
+
# Already done
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
if should_execute
|
|
321
|
+
# Execute outside the lock to avoid deadlock
|
|
322
|
+
# Use ensure_execution_context to reuse the context from run phase
|
|
323
|
+
context = ensure_execution_context
|
|
324
|
+
context.trigger_clean(@task.class, registry: @registry)
|
|
325
|
+
# After execution returns, the task is completed
|
|
326
|
+
end
|
|
229
327
|
end
|
|
230
328
|
|
|
231
|
-
|
|
232
|
-
|
|
329
|
+
##
|
|
330
|
+
# Checks whether the registry has requested an abort and raises an exception to stop starting new tasks.
|
|
331
|
+
# @raise [Taski::TaskAbortException] if `@registry.abort_requested?` is true — raised with the message "Execution aborted - no new tasks will start".
|
|
332
|
+
def check_abort!
|
|
333
|
+
if @registry.abort_requested?
|
|
334
|
+
raise Taski::TaskAbortException, "Execution aborted - no new tasks will start"
|
|
335
|
+
end
|
|
233
336
|
end
|
|
234
337
|
|
|
235
|
-
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
|
|
239
|
-
|
|
338
|
+
##
|
|
339
|
+
# Ensures an execution context exists for this wrapper.
|
|
340
|
+
# Returns the existing context if set, otherwise creates a shared context.
|
|
341
|
+
# This enables run and clean phases to share state like runtime dependencies.
|
|
342
|
+
# @return [ExecutionContext] The execution context for this wrapper
|
|
343
|
+
def ensure_execution_context
|
|
344
|
+
@execution_context ||= create_shared_context
|
|
240
345
|
end
|
|
241
346
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
347
|
+
##
|
|
348
|
+
# Creates a shared execution context with proper triggers for run and clean.
|
|
349
|
+
# The context is configured to reuse itself when triggering nested executions.
|
|
350
|
+
# @return [ExecutionContext] A new execution context
|
|
351
|
+
def create_shared_context
|
|
352
|
+
context = ExecutionContext.new
|
|
353
|
+
progress = Taski.progress_display
|
|
354
|
+
context.add_observer(progress) if progress
|
|
355
|
+
|
|
356
|
+
# Set triggers to reuse this context for nested executions
|
|
357
|
+
context.execution_trigger = ->(task_class, registry) do
|
|
358
|
+
Executor.execute(task_class, registry: registry, execution_context: context)
|
|
359
|
+
end
|
|
360
|
+
context.clean_trigger = ->(task_class, registry) do
|
|
361
|
+
Executor.execute_clean(task_class, registry: registry, execution_context: context)
|
|
247
362
|
end
|
|
363
|
+
|
|
364
|
+
context
|
|
248
365
|
end
|
|
249
366
|
|
|
250
|
-
|
|
251
|
-
|
|
367
|
+
##
|
|
368
|
+
# Notifies the execution context of task completion or failure.
|
|
369
|
+
# Falls back to getting the current context if not set during initialization.
|
|
370
|
+
# @param state [Symbol] The completion state (unused, kept for API consistency).
|
|
371
|
+
# @param duration [Numeric, nil] The execution duration in milliseconds.
|
|
372
|
+
# @param error [Exception, nil] The error if the task failed.
|
|
373
|
+
def update_progress(state, duration: nil, error: nil)
|
|
374
|
+
# Defensive fallback: try to get current context if not set during initialization
|
|
375
|
+
@execution_context ||= ExecutionContext.current
|
|
376
|
+
return unless @execution_context
|
|
377
|
+
|
|
378
|
+
@execution_context.notify_task_completed(@task.class, duration: duration, error: error)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
##
|
|
382
|
+
# Notifies the execution context of clean completion or failure.
|
|
383
|
+
# Falls back to getting the current context if not set during initialization.
|
|
384
|
+
# @param state [Symbol] The clean state (unused, kept for API consistency).
|
|
385
|
+
# @param duration [Numeric, nil] The clean duration in milliseconds.
|
|
386
|
+
# @param error [Exception, nil] The error if the clean failed.
|
|
387
|
+
def update_clean_progress(state, duration: nil, error: nil)
|
|
388
|
+
# Defensive fallback: try to get current context if not set during initialization
|
|
389
|
+
@execution_context ||= ExecutionContext.current
|
|
390
|
+
return unless @execution_context
|
|
391
|
+
|
|
392
|
+
@execution_context.notify_clean_completed(@task.class, duration: duration, error: error)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
##
|
|
396
|
+
# Outputs a debug message if TASKI_DEBUG environment variable is set.
|
|
397
|
+
# @param message [String] The debug message to output.
|
|
398
|
+
def debug_log(message)
|
|
399
|
+
return unless ENV["TASKI_DEBUG"]
|
|
400
|
+
puts "[TaskWrapper] #{message}"
|
|
252
401
|
end
|
|
253
402
|
end
|
|
254
403
|
end
|