Class: Henitai::SlotScheduler

Inherits:
Object
  • Object
show all
Includes:
Draining, ProcessControl
Defined in:
lib/henitai/slot_scheduler.rb,
lib/henitai/slot_scheduler/draining.rb,
lib/henitai/slot_scheduler/slot_table.rb,
lib/henitai/slot_scheduler/retry_policy.rb,
lib/henitai/slot_scheduler/drain_verdict.rb,
lib/henitai/slot_scheduler/slot_deadline.rb,
lib/henitai/slot_scheduler/process_control.rb,
lib/henitai/slot_scheduler/test_file_selection.rb,
sig/henitai.rbs

Overview

Owns the process-slot table for a single parallel mutation run.

ProcessWorkerRunner drives the event loop and OS signal handling and delegates every slot operation here: filling idle slots, reaping completed children, retrying flaky survivors, detecting timeouts and running the drain/broadcast state machine. Keeping the table behind one collaborator means Process.wait* has a single caller, so there are no races between threads reaping the same child.

The drain/timeout state machine lives in Draining; the low-level process and signal primitives live in ProcessControl. host is the owning ProcessWorkerRunner, which supplies runtime, wakeup, worker_count and the shutdown flag.

Defined Under Namespace

Modules: Draining, ProcessControl Classes: DrainVerdict, RetryPolicy, SlotDeadline, SlotTable, TestFileSelection

Constant Summary collapse

PROCESS_DRAIN_WINDOW =

Returns:

  • (Float)
0.2
WORKER_SLOT_ENV =

Environment variable exposing a stable worker-slot index (0..jobs-1) to each forked child so test suites can isolate shared resources per slot (e.g. "myapp_test_#ENV['HENITAI_WORKER_SLOT']"). A flaky-retry respawn keeps the original attempt's value.

Returns:

  • (String)
"HENITAI_WORKER_SLOT"
Slot =

Returns:

  • (Object)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(integration:, config:, progress_reporter:, options:, host:, slot_table: SlotTable.new) ⇒ SlotScheduler

slot_table is injectable so a spec can seed mid-run state, or assert against the table through its own public interface, without reaching a private reader here. rubocop:disable Metrics/ParameterLists -- every one of these is a distinct collaborator supplied by ProcessWorkerRunner; bundling them into a context object would only move the list.



54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/henitai/slot_scheduler.rb', line 54

def initialize(integration:, config:, progress_reporter:, options:, host:, slot_table: SlotTable.new)
  # rubocop:enable Metrics/ParameterLists
  @integration = integration
  @config = config
  @progress_reporter = progress_reporter
  @options = options
  @host = host
  @slot_table = slot_table
  @pending = []
  @results = []
  @flaky_retry_count = 0
end

Instance Attribute Details

#flaky_retry_countInteger, Array<ScenarioExecutionResult> (readonly)

Returns:

  • (Integer)

    mutants that required at least one retry during the run.

  • (Array<ScenarioExecutionResult>)

    verdicts accumulated so far.



46
47
48
# File 'lib/henitai/slot_scheduler.rb', line 46

def flaky_retry_count
  @flaky_retry_count
end

#resultsInteger, Array<ScenarioExecutionResult> (readonly)

Returns:

  • (Integer)

    mutants that required at least one retry during the run.

  • (Array<ScenarioExecutionResult>)

    verdicts accumulated so far.



46
47
48
# File 'lib/henitai/slot_scheduler.rb', line 46

def results
  @results
end

Instance Method Details

#check_timeoutsvoid

This method returns an undefined value.



645
# File 'sig/henitai.rbs', line 645

def check_timeouts: () -> void

#done?Boolean

Returns:

  • (Boolean)


72
73
74
# File 'lib/henitai/slot_scheduler.rb', line 72

def done?
  pending.empty? && slot_table.empty?
end

#drain_draining_slotsvoid

This method returns an undefined value.



647
# File 'sig/henitai.rbs', line 647

def drain_draining_slots: () -> void

#draining_slots?Boolean

Returns:

  • (Boolean)


646
# File 'sig/henitai.rbs', line 646

def draining_slots?: () -> bool

#enqueue(mutants) ⇒ Array[Mutant]

Queues the mutants to be scheduled into worker slots.

Parameters:

Returns:



68
69
70
# File 'lib/henitai/slot_scheduler.rb', line 68

def enqueue(mutants)
  @pending = mutants.dup
end

#fill_idle_slotsvoid

This method returns an undefined value.



76
77
78
79
80
81
# File 'lib/henitai/slot_scheduler.rb', line 76

def fill_idle_slots
  while slot_table.size < worker_count && !pending.empty?
    mutant = pending.shift
    spawn_into_slot(mutant)
  end
end

#interrupt_active_slotsvoid

This method returns an undefined value.



648
# File 'sig/henitai.rbs', line 648

def interrupt_active_slots: () -> void

#next_event_timeoutFloat?

Returns:

  • (Float, nil)


94
95
96
97
98
99
100
101
# File 'lib/henitai/slot_scheduler.rb', line 94

def next_event_timeout
  now = monotonic_time
  slot_timeouts = slot_table.each_value.filter_map do |slot|
    slot_deadline.remaining(slot, now)
  end

  slot_timeouts.min
end

#reap_all_completed_childrenvoid

This method returns an undefined value.



83
84
85
86
87
88
89
90
91
92
# File 'lib/henitai/slot_scheduler.rb', line 83

def reap_all_completed_children
  loop do
    pid, status = runtime.wait2(-1, Process::WNOHANG)
    break unless pid

    complete_slot(pid, status)
  end
rescue Errno::ECHILD
  nil
end