Module: Wurk::Component

Overview

Shared mixin for runtime components (Launcher, Manager, Processor, Fetcher, Scheduler, Cron). Wraps clock readings, identity, thread spawning, lifecycle event dispatch, and exception forwarding so each component stays single-purpose.

Host class must expose ‘#config` returning either a Wurk::Configuration or a Wurk::Capsule — both duck-type the methods we delegate to.

Spec: docs/target/sidekiq-free.md §11 (Sidekiq::Component).

Constant Summary collapse

DEFAULT_THREAD_PRIORITY =
-1
PROCESS_NONCE =

Stable for the life of the process — survives fork (children inherit the same nonce). Identity differs across forks because Process.pid does.

SecureRandom.hex(6)

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



23
24
25
# File 'lib/wurk/component.rb', line 23

def config
  @config
end

Instance Method Details

#default_tag(dir = Dir.pwd) ⇒ Object



53
54
55
# File 'lib/wurk/component.rb', line 53

def default_tag(dir = Dir.pwd)
  File.basename(dir)
end

#fire_event(event, oneshot: true, reverse: false, reraise: false) ⇒ Object

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.



118
119
120
121
122
123
124
125
# File 'lib/wurk/component.rb', line 118

def fire_event(event, oneshot: true, reverse: false, reraise: false)
  bucket = config[:lifecycle_events][event]
  return if bucket.nil? || bucket.empty?

  iter = reverse ? bucket.reverse : bucket
  iter.each { |hook| run_lifecycle_hook(hook, event, reraise) }
  bucket.clear if oneshot
end

#handle_exception(ex, ctx = {}) ⇒ Object



67
68
69
# File 'lib/wurk/component.rb', line 67

def handle_exception(ex, ctx = {})
  config.handle_exception(ex, ctx)
end

#hostnameObject



41
42
43
# File 'lib/wurk/component.rb', line 41

def hostname
  ENV['DYNO'] || Socket.gethostname
end

#identityObject



49
50
51
# File 'lib/wurk/component.rb', line 49

def identity
  "#{hostname}:#{::Process.pid}:#{process_nonce}"
end

#leader?Boolean

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` 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.

Returns:

  • (Boolean)


81
82
83
84
85
86
87
# File 'lib/wurk/component.rb', line 81

def leader?
  return false if ENV[Wurk::Leader::OPT_OUT_ENV].to_s.downcase == 'false'

  redis { |c| c.call('GET', Wurk::Leader::DEFAULT_KEY) } == identity
rescue StandardError
  false
end

#loggerObject

— delegated to config ——————————————-



59
60
61
# File 'lib/wurk/component.rb', line 59

def logger
  config.logger
end

#mono_msObject



31
32
33
# File 'lib/wurk/component.rb', line 31

def mono_ms
  ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
end

#process_nonceObject



45
46
47
# File 'lib/wurk/component.rb', line 45

def process_nonce
  PROCESS_NONCE
end

#real_msObject

— clocks ———————————————————



27
28
29
# File 'lib/wurk/component.rb', line 27

def real_ms
  ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
end

#redisObject



63
64
65
# File 'lib/wurk/component.rb', line 63

def redis(&)
  config.redis(&)
end

#safe_thread(name, priority: nil, &block) ⇒ Object

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.



104
105
106
107
108
109
110
111
# File 'lib/wurk/component.rb', line 104

def safe_thread(name, priority: nil, &block)
  Thread.new do
    Thread.current.name = name
    Thread.current.priority = priority || DEFAULT_THREAD_PRIORITY
    Thread.current.report_on_exception = false
    watchdog(name, &block)
  end
end

#tidObject

— identity ——————————————————-



37
38
39
# File 'lib/wurk/component.rb', line 37

def tid
  (Thread.current.object_id ^ ::Process.pid).to_s(36)
end

#watchdog(last_words) ⇒ Object

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.



94
95
96
97
98
99
# File 'lib/wurk/component.rb', line 94

def watchdog(last_words)
  yield
rescue StandardError => e
  handle_exception(e, { context: last_words })
  raise
end