taski 0.8.3 → 0.9.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 +39 -0
- data/README.md +65 -50
- data/docs/GUIDE.md +41 -56
- 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 +156 -357
- 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 +238 -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/visitor.rb +1 -39
- data/lib/taski/task.rb +44 -58
- data/lib/taski/test_helper/errors.rb +1 -1
- data/lib/taski/test_helper.rb +21 -35
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +60 -61
- data/sig/taski.rbs +194 -203
- metadata +31 -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
|
@@ -24,16 +24,14 @@ module Taski
|
|
|
24
24
|
# @param task_class [Class] The task class
|
|
25
25
|
# @param wrapper [TaskWrapper] The wrapper instance to register
|
|
26
26
|
def register(task_class, wrapper)
|
|
27
|
-
@tasks[task_class] = wrapper
|
|
27
|
+
@monitor.synchronize { @tasks[task_class] = wrapper }
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# Check if a task wrapper has been registered (created during run phase).
|
|
30
31
|
# @param task_class [Class] The task class
|
|
31
|
-
# @return [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@tasks.fetch(task_class) do
|
|
35
|
-
raise "Task #{task_class} not registered"
|
|
36
|
-
end
|
|
32
|
+
# @return [Boolean] true if a wrapper exists for this task
|
|
33
|
+
def registered?(task_class)
|
|
34
|
+
@monitor.synchronize { @tasks.key?(task_class) }
|
|
37
35
|
end
|
|
38
36
|
|
|
39
37
|
# @param thread [Thread] The thread to register
|
|
@@ -77,19 +75,17 @@ module Taski
|
|
|
77
75
|
end
|
|
78
76
|
end
|
|
79
77
|
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
# @
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
# Create or retrieve a TaskWrapper for the given task class.
|
|
79
|
+
# Encapsulates the standard wrapper creation pattern used by Executor and WorkerPool.
|
|
80
|
+
# @param task_class [Class] The task class
|
|
81
|
+
# @param execution_facade [ExecutionFacade] The execution facade
|
|
82
|
+
# @return [TaskWrapper] The wrapper instance
|
|
83
|
+
def create_wrapper(task_class, execution_facade:)
|
|
84
|
+
get_or_create(task_class) do
|
|
85
|
+
task_instance = task_class.allocate
|
|
86
|
+
task_instance.send(:initialize)
|
|
87
|
+
TaskWrapper.new(task_instance, registry: self, execution_facade: execution_facade)
|
|
86
88
|
end
|
|
87
|
-
|
|
88
|
-
wait_all
|
|
89
|
-
|
|
90
|
-
# @type var wrapper: Taski::Execution::TaskWrapper
|
|
91
|
-
wrapper = get_task(task_class)
|
|
92
|
-
wrapper.result
|
|
93
89
|
end
|
|
94
90
|
end
|
|
95
91
|
end
|
|
@@ -3,13 +3,19 @@
|
|
|
3
3
|
module Taski
|
|
4
4
|
module Execution
|
|
5
5
|
# Scheduler manages task dependency state and determines execution order.
|
|
6
|
-
#
|
|
7
|
-
#
|
|
6
|
+
# Both run and clean phases use the same unified state set:
|
|
7
|
+
# :pending, :running, :completed, :failed, :skipped.
|
|
8
|
+
#
|
|
9
|
+
# == State Transitions
|
|
10
|
+
#
|
|
11
|
+
# Run phase: pending → running → completed | failed
|
|
12
|
+
# pending → skipped (when a dependency fails)
|
|
13
|
+
# Clean phase: pending → running → completed
|
|
8
14
|
#
|
|
9
15
|
# == Responsibilities
|
|
10
16
|
#
|
|
11
|
-
# -
|
|
12
|
-
# - Track task states: pending,
|
|
17
|
+
# - Load pre-built dependency graph from Executor
|
|
18
|
+
# - Track task states: pending, running, completed
|
|
13
19
|
# - Determine which tasks are ready to execute (all dependencies completed)
|
|
14
20
|
# - Provide next_ready_tasks for the Executor's event loop
|
|
15
21
|
# - Build reverse dependency graph for clean operations
|
|
@@ -19,19 +25,19 @@ module Taski
|
|
|
19
25
|
# == API
|
|
20
26
|
#
|
|
21
27
|
# Run operations:
|
|
22
|
-
# - {#
|
|
28
|
+
# - {#load_graph} - Load pre-built dependency graph
|
|
23
29
|
# - {#next_ready_tasks} - Get tasks ready for execution
|
|
24
|
-
# - {#
|
|
30
|
+
# - {#mark_running} - Mark task as sent to worker pool
|
|
25
31
|
# - {#mark_completed} - Mark task as finished
|
|
26
|
-
# - {#
|
|
32
|
+
# - {#finished?} - Check if task is completed
|
|
27
33
|
# - {#running_tasks?} - Check if any tasks are currently executing
|
|
28
34
|
#
|
|
29
35
|
# Clean operations:
|
|
30
36
|
# - {#build_reverse_dependency_graph} - Build reverse graph for clean order
|
|
31
37
|
# - {#next_ready_clean_tasks} - Get tasks ready for clean (reverse order)
|
|
32
|
-
# - {#
|
|
38
|
+
# - {#mark_clean_running} - Mark task as sent for clean
|
|
33
39
|
# - {#mark_clean_completed} - Mark task as clean finished
|
|
34
|
-
# - {#
|
|
40
|
+
# - {#clean_finished?} - Check if task clean is completed
|
|
35
41
|
# - {#running_clean_tasks?} - Check if any clean tasks are currently executing
|
|
36
42
|
#
|
|
37
43
|
# == Thread Safety
|
|
@@ -40,59 +46,56 @@ module Taski
|
|
|
40
46
|
# so no synchronization is needed. The Executor serializes all
|
|
41
47
|
# access to the Scheduler through its event loop.
|
|
42
48
|
class Scheduler
|
|
43
|
-
#
|
|
49
|
+
# Unified task execution states (used by both run and clean phases)
|
|
44
50
|
STATE_PENDING = :pending
|
|
45
|
-
|
|
51
|
+
STATE_RUNNING = :running
|
|
46
52
|
STATE_COMPLETED = :completed
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
CLEAN_STATE_PENDING = :clean_pending
|
|
50
|
-
CLEAN_STATE_ENQUEUED = :clean_enqueued
|
|
51
|
-
CLEAN_STATE_COMPLETED = :clean_completed
|
|
53
|
+
STATE_FAILED = :failed
|
|
54
|
+
STATE_SKIPPED = :skipped
|
|
52
55
|
|
|
53
56
|
##
|
|
54
57
|
# Initializes internal data structures used to track normal and clean task execution.
|
|
55
|
-
#
|
|
56
|
-
# Sets up:
|
|
57
|
-
# - @dependencies: map from task class to its dependency task classes.
|
|
58
|
-
# - @task_states: map from task class to its normal execution state.
|
|
59
|
-
# - @completed_tasks: set of task classes that have completed normal execution.
|
|
60
|
-
# - @reverse_dependencies: map from task class to dependent task classes (used for clean ordering).
|
|
61
|
-
# - @clean_task_states: map from task class to its clean execution state.
|
|
62
|
-
# - @clean_completed_tasks: set of task classes that have completed clean execution.
|
|
63
58
|
def initialize
|
|
64
59
|
# Run execution state
|
|
65
60
|
@dependencies = {}
|
|
66
61
|
@task_states = {}
|
|
67
|
-
@
|
|
62
|
+
@finished_tasks = Set.new
|
|
63
|
+
@run_reverse_deps = {}
|
|
68
64
|
|
|
69
|
-
# Clean execution state (independent tracking)
|
|
65
|
+
# Clean execution state (independent tracking, same state values)
|
|
70
66
|
@reverse_dependencies = {}
|
|
71
67
|
@clean_task_states = {}
|
|
72
|
-
@
|
|
68
|
+
@clean_finished_tasks = Set.new
|
|
73
69
|
end
|
|
74
70
|
|
|
75
|
-
#
|
|
76
|
-
# Populates internal state with all tasks and their dependencies.
|
|
71
|
+
# Load dependency graph from a pre-built DependencyGraph.
|
|
72
|
+
# Populates internal state with all tasks and their dependencies via BFS from root.
|
|
77
73
|
#
|
|
74
|
+
# @param dependency_graph [StaticAnalysis::DependencyGraph] Pre-built graph
|
|
78
75
|
# @param root_task_class [Class] The root task class to start from
|
|
79
|
-
def
|
|
76
|
+
def load_graph(dependency_graph, root_task_class)
|
|
80
77
|
# @type var queue: Array[singleton(Taski::Task)]
|
|
81
78
|
queue = [root_task_class]
|
|
82
79
|
|
|
83
80
|
while (task_class = queue.shift)
|
|
84
81
|
next if @task_states.key?(task_class)
|
|
85
82
|
|
|
86
|
-
deps = task_class
|
|
83
|
+
deps = dependency_graph.dependencies_for(task_class)
|
|
87
84
|
@dependencies[task_class] = deps.dup
|
|
88
85
|
@task_states[task_class] = STATE_PENDING
|
|
86
|
+
@run_reverse_deps[task_class] ||= Set.new
|
|
89
87
|
|
|
90
|
-
deps.each
|
|
88
|
+
deps.each do |dep|
|
|
89
|
+
@run_reverse_deps[dep] ||= Set.new
|
|
90
|
+
@run_reverse_deps[dep].add(task_class)
|
|
91
|
+
log_dependency_resolved(task_class, dep)
|
|
92
|
+
queue << dep
|
|
93
|
+
end
|
|
91
94
|
end
|
|
92
95
|
end
|
|
93
96
|
|
|
94
97
|
# Get all tasks that are ready to execute.
|
|
95
|
-
# A task is ready when all its dependencies are completed.
|
|
98
|
+
# A task is ready when it is pending and all its dependencies are completed.
|
|
96
99
|
#
|
|
97
100
|
# @return [Array<Class>] Array of task classes ready for execution
|
|
98
101
|
def next_ready_tasks
|
|
@@ -105,11 +108,12 @@ module Taski
|
|
|
105
108
|
ready
|
|
106
109
|
end
|
|
107
110
|
|
|
108
|
-
# Mark a task as
|
|
111
|
+
# Mark a task as running (sent to worker pool).
|
|
112
|
+
# Prevents the task from being selected again by next_ready_tasks.
|
|
109
113
|
#
|
|
110
114
|
# @param task_class [Class] The task class to mark
|
|
111
|
-
def
|
|
112
|
-
@task_states[task_class] =
|
|
115
|
+
def mark_running(task_class)
|
|
116
|
+
@task_states[task_class] = STATE_RUNNING
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
# Mark a task as completed.
|
|
@@ -117,65 +121,101 @@ module Taski
|
|
|
117
121
|
# @param task_class [Class] The task class to mark
|
|
118
122
|
def mark_completed(task_class)
|
|
119
123
|
@task_states[task_class] = STATE_COMPLETED
|
|
120
|
-
@
|
|
124
|
+
@finished_tasks.add(task_class)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Mark a task as failed.
|
|
128
|
+
# Failed tasks are added to the finished set so dependents can proceed
|
|
129
|
+
# (they will be skipped by the Executor's skip_pending_dependents).
|
|
130
|
+
#
|
|
131
|
+
# @param task_class [Class] The task class to mark
|
|
132
|
+
def mark_failed(task_class)
|
|
133
|
+
@task_states[task_class] = STATE_FAILED
|
|
134
|
+
@finished_tasks.add(task_class)
|
|
121
135
|
end
|
|
122
136
|
|
|
123
|
-
# Check if a task is
|
|
137
|
+
# Check if a task is in pending state.
|
|
124
138
|
#
|
|
125
139
|
# @param task_class [Class] The task class to check
|
|
126
|
-
# @return [Boolean] true if the task is
|
|
127
|
-
def
|
|
128
|
-
@
|
|
140
|
+
# @return [Boolean] true if the task is pending
|
|
141
|
+
def pending?(task_class)
|
|
142
|
+
@task_states[task_class] == STATE_PENDING
|
|
129
143
|
end
|
|
130
144
|
|
|
131
|
-
# Check if
|
|
145
|
+
# Check if a task is finished (completed or failed).
|
|
132
146
|
#
|
|
133
|
-
|
|
134
|
-
#
|
|
135
|
-
|
|
147
|
+
# @param task_class [Class] The task class to check
|
|
148
|
+
# @return [Boolean] true if the task is finished
|
|
149
|
+
def finished?(task_class)
|
|
150
|
+
@finished_tasks.include?(task_class)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Check if there are any running tasks.
|
|
154
|
+
#
|
|
155
|
+
# @return [Boolean] true if any task is running, false otherwise.
|
|
136
156
|
def running_tasks?
|
|
137
|
-
@task_states.values.any? { |state| state ==
|
|
157
|
+
@task_states.values.any? { |state| state == STATE_RUNNING }
|
|
138
158
|
end
|
|
139
159
|
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
160
|
+
# Get the total number of tasks in the dependency graph.
|
|
161
|
+
#
|
|
162
|
+
# @return [Integer] The number of tasks
|
|
163
|
+
def task_count
|
|
164
|
+
@task_states.size
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Get task classes that were never executed (remained in STATE_PENDING).
|
|
168
|
+
# These are tasks discovered by the static dependency graph
|
|
169
|
+
# (via load_graph) but not reached at runtime — e.g.,
|
|
170
|
+
# skipped due to conditional logic inside Task#run or because the
|
|
171
|
+
# root task completed before all statically-discovered tasks were needed.
|
|
172
|
+
#
|
|
173
|
+
# @return [Array<Class>] Array of task classes still pending
|
|
174
|
+
def never_started_task_classes
|
|
175
|
+
@task_states.select { |_, state| state == STATE_PENDING }.keys
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Mark a task as skipped (never executed). Only transitions from pending.
|
|
179
|
+
#
|
|
180
|
+
# @param task_class [Class] The task class to mark as skipped
|
|
181
|
+
# @return [Boolean] true if the state was changed
|
|
182
|
+
def mark_skipped(task_class)
|
|
183
|
+
return false unless @task_states[task_class] == STATE_PENDING
|
|
184
|
+
@task_states[task_class] = STATE_SKIPPED
|
|
185
|
+
true
|
|
186
|
+
end
|
|
143
187
|
|
|
144
|
-
#
|
|
145
|
-
# Used to incorporate dynamically selected dependencies (e.g., Section implementations)
|
|
146
|
-
# that were determined during the run phase.
|
|
188
|
+
# Get the count of tasks in STATE_SKIPPED.
|
|
147
189
|
#
|
|
148
|
-
#
|
|
149
|
-
|
|
190
|
+
# @return [Integer] Number of explicitly skipped tasks
|
|
191
|
+
def skipped_count
|
|
192
|
+
@task_states.count { |_, state| state == STATE_SKIPPED }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Find all pending tasks that transitively depend on the given task.
|
|
196
|
+
# Traverses the reverse dependency graph (run phase) using BFS.
|
|
197
|
+
# Only returns tasks in STATE_PENDING; running/completed tasks are
|
|
198
|
+
# traversed through but not included in results.
|
|
150
199
|
#
|
|
151
|
-
# @param
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# Update reverse dependencies if they exist (for clean operations)
|
|
169
|
-
# If A depends on B (from_class→to_class), then B→[A] in reverse graph
|
|
170
|
-
if @reverse_dependencies.any?
|
|
171
|
-
@reverse_dependencies[to_class] ||= Set.new
|
|
172
|
-
@reverse_dependencies[to_class].add(from_class)
|
|
173
|
-
|
|
174
|
-
# Ensure to_class has clean state initialized
|
|
175
|
-
@clean_task_states[to_class] ||= CLEAN_STATE_PENDING
|
|
176
|
-
end
|
|
200
|
+
# @param task_class [Class] The task to find dependents of
|
|
201
|
+
# @return [Array<Class>] Pending transitive dependents
|
|
202
|
+
def pending_dependents_of(task_class)
|
|
203
|
+
result = []
|
|
204
|
+
queue = [task_class]
|
|
205
|
+
visited = Set.new([task_class])
|
|
206
|
+
|
|
207
|
+
while (tc = queue.shift)
|
|
208
|
+
dependents = @run_reverse_deps[tc] || Set.new
|
|
209
|
+
dependents.each do |dep|
|
|
210
|
+
next if visited.include?(dep)
|
|
211
|
+
visited.add(dep)
|
|
212
|
+
|
|
213
|
+
result << dep if @task_states[dep] == STATE_PENDING
|
|
214
|
+
queue << dep
|
|
177
215
|
end
|
|
178
216
|
end
|
|
217
|
+
|
|
218
|
+
result
|
|
179
219
|
end
|
|
180
220
|
|
|
181
221
|
# ========================================
|
|
@@ -186,28 +226,18 @@ module Taski
|
|
|
186
226
|
# Clean operations run in reverse order: if A depends on B, then B must
|
|
187
227
|
# be cleaned after A (so A→[B] in reverse graph means B depends on A's clean).
|
|
188
228
|
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
|
|
192
|
-
# Builds the reverse dependency graph and initializes per-task clean execution state starting from the given root task.
|
|
193
|
-
#
|
|
194
|
-
# Ensures the forward dependency graph is present, clears prior clean-state data, initializes each discovered task with
|
|
195
|
-
# an empty reverse-dependency set and `CLEAN_STATE_PENDING`, and populates reverse mappings so a task's clean run
|
|
196
|
-
# depends on the clean completion of tasks that depend on it.
|
|
197
|
-
# @param [Class] root_task_class The root task class from which to discover tasks and their dependencies.
|
|
198
|
-
def build_reverse_dependency_graph(root_task_class)
|
|
199
|
-
# First, ensure we have the forward dependency graph
|
|
200
|
-
build_dependency_graph(root_task_class) if @dependencies.empty?
|
|
201
|
-
|
|
229
|
+
# Requires load_graph to have been called first to populate @dependencies.
|
|
230
|
+
# Also initializes clean states for all tasks to STATE_PENDING.
|
|
231
|
+
def build_reverse_dependency_graph
|
|
202
232
|
# Clear previous clean state
|
|
203
233
|
@reverse_dependencies.clear
|
|
204
234
|
@clean_task_states.clear
|
|
205
|
-
@
|
|
235
|
+
@clean_finished_tasks.clear
|
|
206
236
|
|
|
207
237
|
# Initialize all tasks with empty reverse dependency sets
|
|
208
238
|
@dependencies.each_key do |task_class|
|
|
209
239
|
@reverse_dependencies[task_class] = Set.new
|
|
210
|
-
@clean_task_states[task_class] =
|
|
240
|
+
@clean_task_states[task_class] = STATE_PENDING
|
|
211
241
|
end
|
|
212
242
|
|
|
213
243
|
# Build reverse mappings: if A depends on B, then B→[A] in reverse graph
|
|
@@ -220,88 +250,79 @@ module Taski
|
|
|
220
250
|
end
|
|
221
251
|
|
|
222
252
|
# Get all tasks that are ready to clean.
|
|
223
|
-
# A task is ready to clean when all its reverse
|
|
224
|
-
# have completed their clean operation.
|
|
253
|
+
# A task is ready to clean when it is pending and all its reverse
|
|
254
|
+
# dependencies (dependents) have completed their clean operation.
|
|
225
255
|
#
|
|
226
|
-
##
|
|
227
|
-
# Lists task classes that are ready for clean execution.
|
|
228
|
-
# A task is considered ready when its clean state is `CLEAN_STATE_PENDING` and all of its reverse dependencies have completed their clean execution.
|
|
229
256
|
# @return [Array<Class>] Array of task classes ready for clean execution.
|
|
230
257
|
def next_ready_clean_tasks
|
|
231
258
|
ready = []
|
|
232
259
|
@clean_task_states.each_key do |task_class|
|
|
233
|
-
next unless @clean_task_states[task_class] ==
|
|
260
|
+
next unless @clean_task_states[task_class] == STATE_PENDING
|
|
234
261
|
next unless ready_to_clean?(task_class)
|
|
235
262
|
ready << task_class
|
|
236
263
|
end
|
|
237
264
|
ready
|
|
238
265
|
end
|
|
239
266
|
|
|
240
|
-
# Mark a task as
|
|
267
|
+
# Mark a task as running for clean execution.
|
|
241
268
|
#
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
def mark_clean_enqueued(task_class)
|
|
246
|
-
@clean_task_states[task_class] = CLEAN_STATE_ENQUEUED
|
|
269
|
+
# @param task_class [Class] The task class to mark as running for clean.
|
|
270
|
+
def mark_clean_running(task_class)
|
|
271
|
+
@clean_task_states[task_class] = STATE_RUNNING
|
|
247
272
|
end
|
|
248
273
|
|
|
249
274
|
# Mark a task as clean completed.
|
|
250
275
|
#
|
|
251
|
-
|
|
252
|
-
# Marks the clean execution of the given task class as completed and records it.
|
|
253
|
-
# @param [Class] task_class - The task class to mark as clean completed.
|
|
276
|
+
# @param task_class [Class] The task class to mark as clean completed.
|
|
254
277
|
def mark_clean_completed(task_class)
|
|
255
|
-
@clean_task_states[task_class] =
|
|
256
|
-
@
|
|
278
|
+
@clean_task_states[task_class] = STATE_COMPLETED
|
|
279
|
+
@clean_finished_tasks.add(task_class)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Mark a task as clean failed.
|
|
283
|
+
# Adds to @clean_finished_tasks so dependents are not blocked.
|
|
284
|
+
#
|
|
285
|
+
# @param task_class [Class] The task class to mark as clean failed.
|
|
286
|
+
def mark_clean_failed(task_class)
|
|
287
|
+
@clean_task_states[task_class] = STATE_FAILED
|
|
288
|
+
@clean_finished_tasks.add(task_class)
|
|
257
289
|
end
|
|
258
290
|
|
|
259
291
|
# Check if a task's clean is completed.
|
|
260
292
|
#
|
|
261
|
-
# @param task_class [Class] The task class to check
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
# @return [Boolean] `true` if the task's clean is completed, `false` otherwise.
|
|
266
|
-
def clean_completed?(task_class)
|
|
267
|
-
@clean_completed_tasks.include?(task_class)
|
|
293
|
+
# @param task_class [Class] The task class to check.
|
|
294
|
+
# @return [Boolean] true if the task's clean is completed, false otherwise.
|
|
295
|
+
def clean_finished?(task_class)
|
|
296
|
+
@clean_finished_tasks.include?(task_class)
|
|
268
297
|
end
|
|
269
298
|
|
|
270
|
-
# Check if there are any running
|
|
299
|
+
# Check if there are any running clean tasks.
|
|
271
300
|
#
|
|
272
|
-
|
|
273
|
-
# Indicates whether any clean tasks are currently enqueued for execution.
|
|
274
|
-
# @return [Boolean] `true` if at least one clean task is enqueued, `false` otherwise.
|
|
301
|
+
# @return [Boolean] true if at least one clean task is running, false otherwise.
|
|
275
302
|
def running_clean_tasks?
|
|
276
|
-
@clean_task_states.values.any? { |state| state ==
|
|
303
|
+
@clean_task_states.values.any? { |state| state == STATE_RUNNING }
|
|
277
304
|
end
|
|
278
305
|
|
|
279
306
|
private
|
|
280
307
|
|
|
281
308
|
# Check if a task is ready to execute (all dependencies completed).
|
|
282
|
-
#
|
|
283
|
-
# @param task_class [Class] The task class to check
|
|
284
|
-
##
|
|
285
|
-
# Determines whether a task's dependencies have all completed.
|
|
286
|
-
# @param [Class] task_class - The task class to check.
|
|
287
|
-
# @return [Boolean] `true` if every dependency of `task_class` is in the set of completed tasks, `false` otherwise.
|
|
288
309
|
def ready_to_execute?(task_class)
|
|
289
310
|
task_deps = @dependencies[task_class] || Set.new
|
|
290
|
-
task_deps.subset?(@
|
|
311
|
+
task_deps.subset?(@finished_tasks)
|
|
291
312
|
end
|
|
292
313
|
|
|
293
314
|
# Check if a task is ready to clean (all reverse dependencies completed).
|
|
294
|
-
# Reverse dependencies are tasks that depend on this task, which must
|
|
295
|
-
# be cleaned before this task can be cleaned.
|
|
296
|
-
#
|
|
297
|
-
# @param task_class [Class] The task class to check
|
|
298
|
-
##
|
|
299
|
-
# Determines whether a task is ready for clean execution.
|
|
300
|
-
# @param [Class] task_class - The task class to check.
|
|
301
|
-
# @return [Boolean] `true` if all tasks that depend on `task_class` have completed their clean execution, `false` otherwise.
|
|
302
315
|
def ready_to_clean?(task_class)
|
|
303
316
|
reverse_deps = @reverse_dependencies[task_class] || Set.new
|
|
304
|
-
reverse_deps.subset?(@
|
|
317
|
+
reverse_deps.subset?(@clean_finished_tasks)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def log_dependency_resolved(from_task, to_task)
|
|
321
|
+
Taski::Logging.debug(
|
|
322
|
+
Taski::Logging::Events::DEPENDENCY_RESOLVED,
|
|
323
|
+
from_task: from_task.name,
|
|
324
|
+
to_task: to_task.name
|
|
325
|
+
)
|
|
305
326
|
end
|
|
306
327
|
end
|
|
307
328
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Taski
|
|
4
|
+
module Execution
|
|
5
|
+
# Base class for observers of the execution lifecycle.
|
|
6
|
+
# Subclasses override only the events they care about.
|
|
7
|
+
#
|
|
8
|
+
# All events are defined as no-op methods. The +context+ accessor
|
|
9
|
+
# is auto-injected by ExecutionFacade#add_observer.
|
|
10
|
+
#
|
|
11
|
+
# == Event Methods
|
|
12
|
+
#
|
|
13
|
+
# - on_ready — facade is configured, observer can pull from context
|
|
14
|
+
# - on_start — execution is about to begin
|
|
15
|
+
# - on_stop — execution has finished
|
|
16
|
+
# - on_task_updated(task_class, previous_state:, current_state:, phase:, timestamp:)
|
|
17
|
+
# - on_group_started(task_class, group_name, phase:, timestamp:)
|
|
18
|
+
# - on_group_completed(task_class, group_name, phase:, timestamp:)
|
|
19
|
+
class TaskObserver
|
|
20
|
+
attr_accessor :context
|
|
21
|
+
|
|
22
|
+
def on_ready
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def on_start
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def on_stop
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def on_task_updated(task_class, previous_state:, current_state:, phase:, timestamp:)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def on_group_started(task_class, group_name, phase:, timestamp:)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def on_group_completed(task_class, group_name, phase:, timestamp:)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|