Class: Wurk::CLI

Inherits:
Object
  • Object
show all
Includes:
Component
Defined in:
lib/wurk/cli.rb

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_i` parses as Integer, `:append` pushes onto a list (only `-q` uses 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

Constants included from Component

Wurk::Component::DEFAULT_THREAD_PRIORITY, Wurk::Component::PROCESS_NONCE

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Component

#default_tag, #fire_event, #handle_exception, #hostname, #identity, #leader?, #logger, #mono_ms, #process_nonce, #real_ms, #redis, #safe_thread, #tid, #watchdog

Constructor Details

#initializeCLI

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

#configObject

Returns the value of attribute config.



66
67
68
# File 'lib/wurk/cli.rb', line 66

def config
  @config
end

#environmentObject

Returns the value of attribute environment.



66
67
68
# File 'lib/wurk/cli.rb', line 66

def environment
  @environment
end

#launcherObject

Returns the value of attribute launcher.



66
67
68
# File 'lib/wurk/cli.rb', line 66

def launcher
  @launcher
end

Class Method Details

.instanceObject



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

#handle_signal(sig) ⇒ Object



110
111
112
113
114
115
116
# File 'lib/wurk/cli.rb', line 110

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

#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
  setup_options(args)
  initialize_logger
  validate!
  self
end

#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
# File 'lib/wurk/cli.rb', line 95

def run(boot_app: true, warmup: true)
  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