Class: Wurk::CLI
Overview
Standalone CLI. Loads from exe/wurk — never loads wurk/rails so the
binary works without the Rails engine (the host app might not be Rails).
Singleton because there is exactly one process-wide CLI; tests construct
fresh .new instances to keep state isolated.
Spec: docs/target/sidekiq-free.md §21 (Sidekiq::CLI).
Constant Summary collapse
- MIN_REDIS_VERSION =
Minimum Redis version Wurk supports — same as Sidekiq 8.x. The job JSON format and Lua scripts rely on commands introduced in Redis 7.
'7.0.0'- BACKTRACE_DUMPER =
Thread-backtrace dumper used by both TTIN and INFO. Same body — INFO is the modern name, TTIN is kept for parity with older Sidekiq users.
lambda do |cli| Thread.list.each do |thread| cli.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}" if thread.backtrace cli.logger.warn thread.backtrace.join("\n") else cli.logger.warn '<no backtrace available>' end end end
- SIGNAL_HANDLERS =
{ 'INT' => ->(_cli) { raise Interrupt }, 'TERM' => ->(_cli) { raise Interrupt }, 'TSTP' => lambda do |cli| cli.logger.info 'Received TSTP, no longer accepting new work' cli.launcher.quiet end, 'TTIN' => BACKTRACE_DUMPER, 'INFO' => BACKTRACE_DUMPER }.freeze
- OPTION_FLAGS =
Table-driven so adding a flag doesn't grow
define_value_flags's ABC size and the surface matches the Sidekiq docs row-for-row. The 5th column is the assignment transform::to_iparses as Integer,:appendpushes onto a list (only-quses that), nil = assign as-is. [ ['-c', '--concurrency INT', :concurrency, 'processor threads to use', :to_i], ['-e', '--environment ENV', :environment, 'Application environment'], ['-g', '--tag TAG', :tag, 'Process tag for procline'], ['-q', '--queue QUEUE[,WEIGHT]', :queues, 'Queues to process with optional weights', :append], ['-r', '--require [PATH|DIR]', :require, 'Location of Rails app or .rb file to require'], ['-t', '--timeout NUM', :timeout, 'Shutdown timeout', :to_i], ['-v', '--verbose', :verbose, 'Print more verbose output'], ['-C', '--config PATH', :config_file, 'path to YAML config file'] ].freeze
Instance Attribute Summary collapse
-
#config ⇒ Object
Returns the value of attribute config.
-
#config ⇒ Object
included
from Component
readonly
Returns the value of attribute config.
-
#environment ⇒ Object
Returns the value of attribute environment.
-
#launcher ⇒ Object
Returns the value of attribute launcher.
Class Method Summary collapse
- .instance ⇒ Object
-
.reset_instance! ⇒ Object
Test seam: parallel suites can't share the singleton.
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
- #handle_signal(sig) ⇒ Object
- #hostname ⇒ Object included from Component
- #identity ⇒ Object included from Component
-
#initialize ⇒ CLI
constructor
A new instance of CLI.
-
#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
-
#parse(args = ARGV.dup) ⇒ Object
parseis split fromrunso tests can drive option parsing without touching Redis or booting the host app. - #process_nonce ⇒ Object included from Component
-
#real_ms ⇒ Object
included
from Component
--- clocks ---------------------------------------------------------.
- #redis ⇒ Object included from Component
-
#run(boot_app: true, warmup: true) ⇒ Object
boot_app:/warmup:are test seams. -
#run_swarm(boot_app: true, warmup: true) ⇒ Object
Standalone multi-process boot — the
sidekiqswarmentry point (Ent §7). -
#safe_thread(name, priority: nil, &block) ⇒ Object
included
from Component
Spawns a named thread that runs
blockunderwatchdog(name). -
#tid ⇒ Object
included
from Component
--- identity -------------------------------------------------------.
-
#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 ⇒ CLI
Returns a new instance of CLI.
77 78 79 80 81 82 |
# File 'lib/wurk/cli.rb', line 77 def initialize @config = nil @launcher = nil @environment = nil @parser = nil end |
Instance Attribute Details
#config ⇒ Object
Returns the value of attribute config.
66 67 68 |
# File 'lib/wurk/cli.rb', line 66 def config @config end |
#config ⇒ Object (readonly) Originally defined in module Component
Returns the value of attribute config.
#environment ⇒ Object
Returns the value of attribute environment.
66 67 68 |
# File 'lib/wurk/cli.rb', line 66 def environment @environment end |
#launcher ⇒ Object
Returns the value of attribute launcher.
66 67 68 |
# File 'lib/wurk/cli.rb', line 66 def launcher @launcher end |
Class Method Details
.instance ⇒ Object
68 69 70 |
# File 'lib/wurk/cli.rb', line 68 def self.instance @instance ||= new end |
.reset_instance! ⇒ Object
Test seam: parallel suites can't share the singleton.
73 74 75 |
# File 'lib/wurk/cli.rb', line 73 def self.reset_instance! @instance = nil end |
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
#handle_signal(sig) ⇒ Object
145 146 147 148 149 150 151 |
# File 'lib/wurk/cli.rb', line 145 def handle_signal(sig) logger.debug { "Got #{sig} signal" } handler = SIGNAL_HANDLERS[sig] return logger.warn("No #{sig} signal handler registered, ignoring") unless handler handler.call(self) end |
#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.
Per spec, the check is performed at call time (Wurk does not cache);
callers must not poll faster than the 60s follower cadence. 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
#parse(args = ARGV.dup) ⇒ Object
parse is split from run so tests can drive option parsing without
touching Redis or booting the host app.
86 87 88 89 90 91 92 |
# File 'lib/wurk/cli.rb', line 86 def parse(args = ARGV.dup) @config ||= Wurk.default_configuration (args) initialize_logger validate! self end |
#process_nonce ⇒ Object Originally defined in module Component
#real_ms ⇒ Object Originally defined in module Component
--- clocks ---------------------------------------------------------
#redis ⇒ Object Originally defined in module Component
#run(boot_app: true, warmup: true) ⇒ Object
boot_app: / warmup: are test seams. Production always passes true.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/wurk/cli.rb', line 95 def run(boot_app: true, warmup: true) # Mark server mode BEFORE the app loads so `configure_server` blocks in # the required initializer actually fire (they gate on `config.server?`). # Matches Sidekiq, which sets `Sidekiq.server = true` before requiring # the app in its CLI. Skipping this silently drops server middleware, # error handlers, and lifecycle hooks registered via configure_server. enter_server_mode boot_application if boot_app self_read, self_write = ::IO.pipe trap_signals(self_write) validate_redis! validate_pool_sizes! @config[:identity] = identity # Force lazy server-middleware chain so worker threads don't race # against each other constructing it. Spec: Sidekiq::CLI line 104. @config.server_middleware ::Process.warmup if warmup && ::Process.respond_to?(:warmup) && ENV['RUBY_DISABLE_WARMUP'] != '1' fire_event(:startup, reverse: false, reraise: true) launch(self_read) end |
#run_swarm(boot_app: true, warmup: true) ⇒ Object
Standalone multi-process boot — the sidekiqswarm entry point (Ent §7).
The parent loads the app once, forks N worker children per the configured
topology, then supervises them (respawn on crash, rolling restart on
SIGUSR1, memory-based recycling). The parent itself never fetches.
This is the only way to get fork-based parallelism without Rails — the
railtie auto-boot path is Rails-only. Honors the swarm preload knobs
(WURK_PRELOAD/SIDEKIQ_PRELOAD Bundler groups, WURK_PRELOAD_APP/
SIDEKIQ_PRELOAD_APP whole-app eager-load) and boots Process.warmup
before the fork so children share warmed pages (copy-on-write). Spec §7.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/wurk/cli.rb', line 126 def run_swarm(boot_app: true, warmup: true) # Server mode before the app loads — see #run. The flag rides through the # fork into every child (the config object is copied), so configure_server # blocks registered in the parent take effect in the workers. enter_server_mode if boot_app preload_bundler_groups boot_application eager_load_application end validate_redis! validate_pool_sizes! @config[:identity] = identity ::Process.warmup if warmup && ::Process.respond_to?(:warmup) && ENV['RUBY_DISABLE_WARMUP'] != '1' @swarm = Wurk::Swarm.new(topology: @config.topology, config: @config) @swarm.boot(install_signals: true) @swarm.supervise 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.
#tid ⇒ Object Originally defined in module Component
--- identity -------------------------------------------------------
#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.