Class: Taski::Execution::Scheduler
- Inherits:
-
Object
- Object
- Taski::Execution::Scheduler
- Includes:
- TestHelper::SchedulerExtension
- Defined in:
- lib/taski/execution/scheduler.rb
Overview
Scheduler manages task dependency state and determines execution order. Both run and clean phases use the same unified state set: :pending, :running, :completed, :failed, :skipped.
State Transitions
Run phase: pending → running → completed | failed pending → skipped (when a dependency fails) Clean phase: pending → running → completed
Responsibilities
- Load pre-built dependency graph from Executor
- Track task states: pending, running, completed
- Determine which tasks are ready to execute (all dependencies completed)
- Provide next_ready_tasks for the Executor's event loop
- Build reverse dependency graph for clean operations
- Track clean states independently from run states
- Provide next_ready_clean_tasks for reverse dependency order execution
API
Run operations:
- #load_graph - Load pre-built dependency graph
- #next_ready_tasks - Get tasks ready for execution
- #mark_running - Mark task as sent to worker pool
- #mark_completed - Mark task as finished
- #finished? - Check if task is completed
- #running_tasks? - Check if any tasks are currently executing
Clean operations:
- #build_reverse_dependency_graph - Build reverse graph for clean order
- #next_ready_clean_tasks - Get tasks ready for clean (reverse order)
- #mark_clean_running - Mark task as sent for clean
- #mark_clean_completed - Mark task as clean finished
- #clean_finished? - Check if task clean is completed
- #running_clean_tasks? - Check if any clean tasks are currently executing
Thread Safety
Scheduler is only accessed from the main thread in Executor, so no synchronization is needed. The Executor serializes all access to the Scheduler through its event loop.
Constant Summary collapse
- STATE_PENDING =
Unified task execution states (used by both run and clean phases)
:pending- STATE_RUNNING =
:running- STATE_COMPLETED =
:completed- STATE_FAILED =
:failed- STATE_SKIPPED =
:skipped
Instance Method Summary collapse
-
#build_reverse_dependency_graph ⇒ Object
Build reverse dependency graph for clean operations.
-
#clean_finished?(task_class) ⇒ Boolean
Check if a task's clean is completed.
-
#finished?(task_class) ⇒ Boolean
Check if a task is finished (completed or failed).
-
#initialize ⇒ Scheduler
constructor
Initializes internal data structures used to track normal and clean task execution.
-
#load_graph(dependency_graph, root_task_class) ⇒ Object
Load dependency graph from a pre-built DependencyGraph.
-
#mark_clean_completed(task_class) ⇒ Object
Mark a task as clean completed.
-
#mark_clean_failed(task_class) ⇒ Object
Mark a task as clean failed.
-
#mark_clean_running(task_class) ⇒ Object
Mark a task as running for clean execution.
-
#mark_completed(task_class) ⇒ Object
Mark a task as completed.
-
#mark_failed(task_class) ⇒ Object
Mark a task as failed.
-
#mark_running(task_class) ⇒ Object
Mark a task as running (sent to worker pool).
-
#mark_skipped(task_class) ⇒ Boolean
Mark a task as skipped (never executed).
-
#never_started_task_classes ⇒ Array<Class>
Get task classes that were never executed (remained in STATE_PENDING).
-
#next_ready_clean_tasks ⇒ Array<Class>
Get all tasks that are ready to clean.
-
#next_ready_tasks ⇒ Array<Class>
Get all tasks that are ready to execute.
-
#pending?(task_class) ⇒ Boolean
Check if a task is in pending state.
-
#pending_dependents_of(task_class) ⇒ Array<Class>
Find all pending tasks that transitively depend on the given task.
-
#running_clean_tasks? ⇒ Boolean
Check if there are any running clean tasks.
-
#running_tasks? ⇒ Boolean
Check if there are any running tasks.
-
#skipped_count ⇒ Integer
Get the count of tasks in STATE_SKIPPED.
-
#task_count ⇒ Integer
Get the total number of tasks in the dependency graph.
Constructor Details
#initialize ⇒ Scheduler
Initializes internal data structures used to track normal and clean task execution.
58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/taski/execution/scheduler.rb', line 58 def initialize # Run execution state @dependencies = {} @task_states = {} @finished_tasks = Set.new @run_reverse_deps = {} # Clean execution state (independent tracking, same state values) @reverse_dependencies = {} @clean_task_states = {} @clean_finished_tasks = Set.new end |
Instance Method Details
#build_reverse_dependency_graph ⇒ Object
Build reverse dependency graph for clean operations. Clean operations run in reverse order: if A depends on B, then B must be cleaned after A (so A→[B] in reverse graph means B depends on A's clean).
Requires load_graph to have been called first to populate @dependencies. Also initializes clean states for all tasks to STATE_PENDING.
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/taski/execution/scheduler.rb', line 231 def build_reverse_dependency_graph # Clear previous clean state @reverse_dependencies.clear @clean_task_states.clear @clean_finished_tasks.clear # Initialize all tasks with empty reverse dependency sets @dependencies.each_key do |task_class| @reverse_dependencies[task_class] = Set.new @clean_task_states[task_class] = STATE_PENDING end # Build reverse mappings: if A depends on B, then B→[A] in reverse graph # This means B's clean depends on A's clean completing first @dependencies.each do |task_class, deps| deps.each do |dep_class| @reverse_dependencies[dep_class].add(task_class) end end end |
#clean_finished?(task_class) ⇒ Boolean
Check if a task's clean is completed.
295 296 297 |
# File 'lib/taski/execution/scheduler.rb', line 295 def clean_finished?(task_class) @clean_finished_tasks.include?(task_class) end |
#finished?(task_class) ⇒ Boolean
Check if a task is finished (completed or failed).
149 150 151 |
# File 'lib/taski/execution/scheduler.rb', line 149 def finished?(task_class) @finished_tasks.include?(task_class) end |
#load_graph(dependency_graph, root_task_class) ⇒ Object
Load dependency graph from a pre-built DependencyGraph. Populates internal state with all tasks and their dependencies via BFS from root.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/taski/execution/scheduler.rb', line 76 def load_graph(dependency_graph, root_task_class) # @type var queue: Array[singleton(Taski::Task)] queue = [root_task_class] while (task_class = queue.shift) next if @task_states.key?(task_class) deps = dependency_graph.dependencies_for(task_class) @dependencies[task_class] = deps.dup @task_states[task_class] = STATE_PENDING @run_reverse_deps[task_class] ||= Set.new deps.each do |dep| @run_reverse_deps[dep] ||= Set.new @run_reverse_deps[dep].add(task_class) log_dependency_resolved(task_class, dep) queue << dep end end end |
#mark_clean_completed(task_class) ⇒ Object
Mark a task as clean completed.
277 278 279 280 |
# File 'lib/taski/execution/scheduler.rb', line 277 def mark_clean_completed(task_class) @clean_task_states[task_class] = STATE_COMPLETED @clean_finished_tasks.add(task_class) end |
#mark_clean_failed(task_class) ⇒ Object
Mark a task as clean failed. Adds to @clean_finished_tasks so dependents are not blocked.
286 287 288 289 |
# File 'lib/taski/execution/scheduler.rb', line 286 def mark_clean_failed(task_class) @clean_task_states[task_class] = STATE_FAILED @clean_finished_tasks.add(task_class) end |
#mark_clean_running(task_class) ⇒ Object
Mark a task as running for clean execution.
270 271 272 |
# File 'lib/taski/execution/scheduler.rb', line 270 def mark_clean_running(task_class) @clean_task_states[task_class] = STATE_RUNNING end |
#mark_completed(task_class) ⇒ Object
Mark a task as completed.
122 123 124 125 |
# File 'lib/taski/execution/scheduler.rb', line 122 def mark_completed(task_class) @task_states[task_class] = STATE_COMPLETED @finished_tasks.add(task_class) end |
#mark_failed(task_class) ⇒ Object
Mark a task as failed. Failed tasks are added to the finished set so dependents can proceed (they will be skipped by the Executor's skip_pending_dependents).
132 133 134 135 |
# File 'lib/taski/execution/scheduler.rb', line 132 def mark_failed(task_class) @task_states[task_class] = STATE_FAILED @finished_tasks.add(task_class) end |
#mark_running(task_class) ⇒ Object
Mark a task as running (sent to worker pool). Prevents the task from being selected again by next_ready_tasks.
115 116 117 |
# File 'lib/taski/execution/scheduler.rb', line 115 def mark_running(task_class) @task_states[task_class] = STATE_RUNNING end |
#mark_skipped(task_class) ⇒ Boolean
Mark a task as skipped (never executed). Only transitions from pending.
182 183 184 185 186 |
# File 'lib/taski/execution/scheduler.rb', line 182 def mark_skipped(task_class) return false unless @task_states[task_class] == STATE_PENDING @task_states[task_class] = STATE_SKIPPED true end |
#never_started_task_classes ⇒ Array<Class>
Get task classes that were never executed (remained in STATE_PENDING). These are tasks discovered by the static dependency graph (via load_graph) but not reached at runtime — e.g., skipped due to conditional logic inside Task#run or because the root task completed before all statically-discovered tasks were needed.
174 175 176 |
# File 'lib/taski/execution/scheduler.rb', line 174 def never_started_task_classes @task_states.select { |_, state| state == STATE_PENDING }.keys end |
#next_ready_clean_tasks ⇒ Array<Class>
Get all tasks that are ready to clean. A task is ready to clean when it is pending and all its reverse dependencies (dependents) have completed their clean operation.
257 258 259 260 261 262 263 264 265 |
# File 'lib/taski/execution/scheduler.rb', line 257 def next_ready_clean_tasks ready = [] @clean_task_states.each_key do |task_class| next unless @clean_task_states[task_class] == STATE_PENDING next unless ready_to_clean?(task_class) ready << task_class end ready end |
#next_ready_tasks ⇒ Array<Class>
Get all tasks that are ready to execute. A task is ready when it is pending and all its dependencies are completed.
101 102 103 104 105 106 107 108 109 |
# File 'lib/taski/execution/scheduler.rb', line 101 def next_ready_tasks ready = [] @task_states.each_key do |task_class| next unless @task_states[task_class] == STATE_PENDING next unless ready_to_execute?(task_class) ready << task_class end ready end |
#pending?(task_class) ⇒ Boolean
Check if a task is in pending state.
141 142 143 |
# File 'lib/taski/execution/scheduler.rb', line 141 def pending?(task_class) @task_states[task_class] == STATE_PENDING end |
#pending_dependents_of(task_class) ⇒ Array<Class>
Find all pending tasks that transitively depend on the given task. Traverses the reverse dependency graph (run phase) using BFS. Only returns tasks in STATE_PENDING; running/completed tasks are traversed through but not included in results.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/taski/execution/scheduler.rb', line 202 def pending_dependents_of(task_class) result = [] queue = [task_class] visited = Set.new([task_class]) while (tc = queue.shift) dependents = @run_reverse_deps[tc] || Set.new dependents.each do |dep| next if visited.include?(dep) visited.add(dep) result << dep if @task_states[dep] == STATE_PENDING queue << dep end end result end |
#running_clean_tasks? ⇒ Boolean
Check if there are any running clean tasks.
302 303 304 |
# File 'lib/taski/execution/scheduler.rb', line 302 def running_clean_tasks? @clean_task_states.values.any? { |state| state == STATE_RUNNING } end |
#running_tasks? ⇒ Boolean
Check if there are any running tasks.
156 157 158 |
# File 'lib/taski/execution/scheduler.rb', line 156 def running_tasks? @task_states.values.any? { |state| state == STATE_RUNNING } end |
#skipped_count ⇒ Integer
Get the count of tasks in STATE_SKIPPED.
191 192 193 |
# File 'lib/taski/execution/scheduler.rb', line 191 def skipped_count @task_states.count { |_, state| state == STATE_SKIPPED } end |
#task_count ⇒ Integer
Get the total number of tasks in the dependency graph.
163 164 165 |
# File 'lib/taski/execution/scheduler.rb', line 163 def task_count @task_states.size end |