Module: Lich::Common::ShutdownScriptDrain

Defined in:
documented/common/shutdown_script_drain.rb

Overview

Tracks script teardown during process shutdown.

This helper keeps the shutdown orchestration in main.rb readable while making the slow-script accounting independently testable.

Defined Under Namespace

Classes: Result

Constant Summary collapse

DEFAULT_DRAIN_ATTEMPTS =

Maximum number of polling attempts while waiting for scripts to exit.

200
DEFAULT_DRAIN_INTERVAL =

Seconds slept between drain polling attempts.

0.1

Class Method Summary collapse

Class Method Details

.run(initial_scripts:, remaining_scripts:, slow_threshold:, drain_attempts: DEFAULT_DRAIN_ATTEMPTS, drain_interval: DEFAULT_DRAIN_INTERVAL, clock: nil, sleeper: nil) ⇒ Result

Kills each supplied script with shutdown context and watches the existing bounded drain loop for scripts that exit slowly or not at all.

Parameters:

  • initial_scripts (Array<#kill,#name>)

    scripts present at shutdown start

  • remaining_scripts (#call)

    returns scripts still registered as alive

  • slow_threshold (Float)

    seconds before a script is reported as slow

  • drain_attempts (Integer) (defaults to: DEFAULT_DRAIN_ATTEMPTS)

    maximum drain polling attempts

  • drain_interval (Float) (defaults to: DEFAULT_DRAIN_INTERVAL)

    seconds slept between drain polls

  • clock (#call) (defaults to: nil)

    monotonic clock returning seconds

  • sleeper (#call) (defaults to: nil)

    sleep adapter

Returns:



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'documented/common/shutdown_script_drain.rb', line 56

def self.run(initial_scripts:, remaining_scripts:, slow_threshold:, drain_attempts: DEFAULT_DRAIN_ATTEMPTS, drain_interval: DEFAULT_DRAIN_INTERVAL, clock: nil, sleeper: nil)
  clock ||= proc { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
  sleeper ||= proc { |duration| sleep duration }

  scripts = initial_scripts.uniq
  started_at = clock.call
  finished_at_by_script = {}
  remaining = []

  scripts.each { |script| kill_script(script) }

  drain_attempts.times do
    remaining = remaining_scripts.call.uniq
    observe_scripts!(scripts, remaining)
    now = clock.call

    scripts.each do |script|
      next if finished_at_by_script.key?(script)
      next if remaining.include?(script)

      finished_at_by_script[script] = now
    end

    break if remaining.empty?

    sleeper.call(drain_interval)
  end

  remaining = remaining_scripts.call.uniq
  newly_observed = observe_scripts!(scripts, remaining)
  unless newly_observed.empty?
    remaining = remaining_scripts.call.uniq
  end
  finished_at = clock.call

  Result.new(
    scripts_started: scripts.length,
    slow_script_threshold: slow_threshold,
    slow_scripts: slow_script_names(scripts, finished_at_by_script, started_at, finished_at, slow_threshold),
    scripts_remaining: remaining.length,
    remaining_scripts: script_names(remaining)
  )
end