Class: Wurk::Swarm::ChildBoot
- Inherits:
-
Object
- Object
- Wurk::Swarm::ChildBoot
- Includes:
- Component
- Defined in:
- lib/wurk/swarm/child_boot.rb
Overview
Step 5 of the boot ordering. Runs inside each forked child:
* reset signal traps inherited from the parent,
* open a fresh Redis pool (ActiveRecord reconnects by its own fork hook),
* apply the slot's queues + concurrency to the default capsule,
* install child signal handlers (TERM/INT drain, TSTP quiet,
USR2 reopen logs),
* launch the Wurk::Launcher and block until shutdown.
Kept separate from Wurk::Swarm so the parent supervisor stays focused on PID supervision (SRP).
Constant Summary collapse
- STORE_NOT_SERVING =
TTIN is trapped because its default disposition STOPS the process: an operator's
kill -TTIN <child>asking for a thread dump would suspend the worker instead. /\A(?:(?:LOADING|BUSY|CLUSTERDOWN|MASTERDOWN|READONLY|OOM|MISCONF)\b|ERR Out of memory)/- CHILD_SIGNALS =
{ 'TERM' => :term, 'INT' => :term, 'TSTP' => :tstp, 'USR2' => :usr2, 'TTIN' => :ttin }.freeze
Instance Attribute Summary collapse
-
#config ⇒ Object
included
from Component
readonly
Returns the value of attribute config.
Instance Method Summary collapse
- #default_tag(dir = Dir.pwd) ⇒ Object included from Component
-
#fire_event(event, oneshot: true, reverse: false, reraise: false) ⇒ Object
included
from Component
Invokes lifecycle hooks for
event. - #handle_exception(ex, ctx = {}) ⇒ Object included from Component
- #hostname ⇒ Object included from Component
- #identity ⇒ Object included from Component
-
#initialize(config, slot, index, parent_pid: ::Process.ppid, start_quiet: false, fleet_size: nil, restart_counters: nil) ⇒ ChildBoot
constructor
parent_pidis captured by the swarm before it forks (race-free) and threaded through so OrphanGuard can tell "still supervised" from "reparented after the supervisor died". -
#leader? ⇒ Boolean
included
from Component
True iff this process currently holds the cluster
dear-leaderlock. -
#logger ⇒ Object
included
from Component
--- delegated to config -------------------------------------------.
- #mono_ms ⇒ Object included from Component
- #process_nonce ⇒ Object included from Component
-
#real_ms ⇒ Object
included
from Component
--- clocks ---------------------------------------------------------.
- #redis(idempotent: false) ⇒ Object included from Component
-
#run ⇒ Object
rubocop:disable Metrics/AbcSize.
-
#safe_thread(name, priority: nil, &block) ⇒ Object
included
from Component
Spawns a named thread that runs
blockunderwatchdog(name). - #tid ⇒ Object included from Component
-
#watchdog(last_words) ⇒ Object
included
from Component
Wraps a block at a thread boundary: any unhandled exception is reported via handle_exception (so it lands in error_handlers / the log) and then re-raised.
Constructor Details
#initialize(config, slot, index, parent_pid: ::Process.ppid, start_quiet: false, fleet_size: nil, restart_counters: nil) ⇒ ChildBoot
parent_pid is captured by the swarm before it forks (race-free) and
threaded through so OrphanGuard can tell "still supervised" from
"reparented after the supervisor died". Defaults to the live parent for
the non-swarm callers (tests) that construct a ChildBoot directly.
start_quiet: — the swarm was TSTP-quieted before this child was
forked (respawn/recycle during maintenance); boot the launcher already
quieted so the replacement doesn't resume fetching. Delivered as a
constructor flag, not a post-fork TSTP: a signal landing before this
child resets its traps hits the parent's inherited (inert) handler and
is dropped — the supervisor's TSTP re-relay is only the backstop.
fleet_size: — slots in the swarm's topology, published to
Health.fleet_size so this child's health listener (if it wins the
port) can judge /ready on the whole swarm, not just itself.
restart_counters: — the parent's Swarm::RestartCounters, read by this
child's health listener for wurk_swarm_child_restarts_total.
47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/wurk/swarm/child_boot.rb', line 47 def initialize(config, slot, index, parent_pid: ::Process.ppid, start_quiet: false, fleet_size: nil, restart_counters: nil) @config = config @restart_counters = restart_counters @slot = slot @index = index @fleet_size = fleet_size @parent_pid = parent_pid @start_quiet = start_quiet @signal_read = nil @signal_write = nil @pending_tstp = false end |
Instance Attribute Details
#config ⇒ Object (readonly) Originally defined in module Component
Returns the value of attribute config.
Instance Method Details
#default_tag(dir = Dir.pwd) ⇒ Object Originally defined in module Component
#fire_event(event, oneshot: true, reverse: false, reraise: false) ⇒ Object Originally defined in module Component
Invokes lifecycle hooks for event. Hooks run in registration order
(or LIFO when reverse: true, used for teardown). A raise in one hook
is reported via handle_exception and does NOT stop the next hook unless
reraise: true (used in tests / fail-fast boot). oneshot: true
clears the bucket after dispatch so the event can't fire twice.
#handle_exception(ex, ctx = {}) ⇒ Object Originally defined in module Component
#hostname ⇒ Object Originally defined in module Component
#identity ⇒ Object Originally defined in module Component
#leader? ⇒ Boolean Originally defined in module Component
True iff this process currently holds the cluster dear-leader lock.
Cached per Component instance for LEADER_CACHE_TTL_MS (~5s): cron and
the metrics rollups call this every tick, and an uncached GET would
double their Redis traffic at short intervals for no benefit — the
lock's own renewal cadence (60s+, spec §6.1) easily tolerates a
few-second-stale read. Returns false unconditionally when
WURK_LEADER=false (or SIDEKIQ_LEADER=false) is set on the process
(opt-out hot-standby). Any Redis error is swallowed → false, so a
transient partition can't propagate as an exception into user code.
Spec: docs/target/sidekiq-ent.md §6.1.
#logger ⇒ Object Originally defined in module Component
--- delegated to config -------------------------------------------
#mono_ms ⇒ Object Originally defined in module Component
#process_nonce ⇒ Object Originally defined in module Component
#real_ms ⇒ Object Originally defined in module Component
--- clocks ---------------------------------------------------------
#redis(idempotent: false) ⇒ Object Originally defined in module Component
#run ⇒ Object
rubocop:disable Metrics/AbcSize
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 |
# File 'lib/wurk/swarm/child_boot.rb', line 61 def run # rubocop:disable Metrics/AbcSize reset_inherited_signals reconnect_after_fork @config.reopen_in_forked_child Wurk.server = true TestModeGuard.enforce!(@config.logger) Wurk::Health.fleet_size = @fleet_size Wurk::Health.swarm_counters = @restart_counters # :fork runs in each child after our internal AR/Redis reconnect and # before fetching, so apps can reopen sockets / restart threads / # reconnect non-fork-safe libs (Ent §7.4). It never fires in the parent # (which only forks + supervises). Like :startup, the child's forked # copy of the bucket is cleared after dispatch, so siblings fire theirs. fire_boot_event(:fork) apply_slot_to_config # :startup must fire in each worker child before its managers spin up # (Sidekiq contract, reraise: true). The parent supervisor never runs # jobs, so the non-swarm CLI path fires it once per process — for the # swarm, each child fires it here. Its own forked copy of the bucket is # cleared after, so siblings still fire their own. fire_boot_event(:startup, reraise: true) run_launcher exit 0 rescue StandardError, ::Wurk::Shutdown => e @config.logger.error { "swarm child ##{@index} (#{::Process.pid}) crashed: #{e.class}: #{e.}" } report_crash(e) exit(redis_unavailable?(e) ? Swarm::REDIS_UNAVAILABLE_EXIT_STATUS : 1) end |
#safe_thread(name, priority: nil, &block) ⇒ Object Originally defined in module Component
Spawns a named thread that runs block under watchdog(name). The
parent must retain the returned Thread; otherwise GC may not, but
report_on_exception is disabled so we don't double-log on death.
Priority resolution matches Sidekiq (component.rb:44-48): explicit
argument, then config.thread_priority, then -1. Ruby's default of 0
buys a 100ms timeslice; each negative step halves it, so -1 keeps a
CPU-heavy capsule from starving its siblings for a whole tick.
#tid ⇒ Object Originally defined in module Component
#watchdog(last_words) ⇒ Object Originally defined in module Component
Wraps a block at a thread boundary: any unhandled exception is reported
via handle_exception (so it lands in error_handlers / the log) and then
re-raised. last_words is the component label included in the context.