Class: Wurk::Capsule
- Inherits:
-
Object
- Object
- Wurk::Capsule
- Defined in:
- lib/wurk/capsule.rb
Overview
One processing unit: a set of threads + queues sharing a fetcher and a Redis pool. Configurations can hold many capsules; each maps to its own Manager and Processors.
Spec: docs/target/sidekiq-free.md §5 (Sidekiq::Capsule).
Constant Summary collapse
- POOL_HEADROOM =
Headroom above
concurrencyfor the main pool; the whole pool is then floored at MIN_POOL_SIZE. Blocking BLMOVE fetch has its own pool (#fetch_redis_pool), so the main pool serves only the background loops — heartbeat, scheduled poller, leader election, cron, the two metrics rollups, reaper, history, health probe — plus the host's own job-code checkouts.concurrency + 5(floor 10) gives each an unstarvable slot; the oldconcurrency + 2starved them once fetch also drew from here — the #1010/Npool-exhaustion incident. Override viaconfig.redis[:size]. 5- MIN_POOL_SIZE =
10
Instance Attribute Summary collapse
-
#concurrency ⇒ Object
Returns the value of attribute concurrency.
-
#config ⇒ Object
readonly
Returns the value of attribute config.
-
#fetcher ⇒ Object
Returns the value of attribute fetcher.
-
#mode ⇒ Object
readonly
Returns the value of attribute mode.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#queues ⇒ Object
Returns the value of attribute queues.
-
#watchdog ⇒ Object
readonly
Returns the value of attribute watchdog.
-
#weights ⇒ Object
readonly
Returns the value of attribute weights.
Instance Method Summary collapse
- #client_middleware {|chain| ... } ⇒ Object
-
#fetch_poll_interval ⇒ Object
Empty-poll BLMOVE backoff for this capsule's reliable fetcher (Pro super_fetch §3.3).
-
#fetch_redis(idempotent: false) ⇒ Object
Checkout from the dedicated fetch pool.
-
#fetch_redis_pool ⇒ Object
Dedicated pool for the reliable fetcher's blocking BLMOVE: one slot per processor thread (
concurrency), since at most that many threads park in fetch at once. -
#initialize(name, config) ⇒ Capsule
constructor
A new instance of Capsule.
- #logger ⇒ Object
- #lookup(name) ⇒ Object
-
#prepare! ⇒ Object
Materialize everything that lazy-inits via
||=and default the fetcher, BEFORE Configuration#freeze! freezes the capsule — otherwise the first post-freeze access (a fetch tick, a middleware call) hits a nil fetcher or FrozenErrors building a pool. -
#prepare_shared! ⇒ Object
The half of
prepare!a forking parent can run on every child's behalf: the chains are a pure function of this capsule's identity, not of the slot (queues + concurrency) a swarm child is assigned later, andcopy_foropens nothing. -
#prepared? ⇒ Boolean
True once this capsule has been prepared to run jobs —
prepare_shared!in the swarm parent before the fork,prepare!in the child at boot. -
#queue_specs ⇒ Object
Lossless
name[,weight]specs (unlikequeues, which is the weight-expanded list). - #redis(idempotent: false) ⇒ Object
- #redis_pool ⇒ Object
- #redis_pools_built? ⇒ Boolean
-
#reload_redis_pools! ⇒ Object
The fork-path form: closes idle sockets but keeps every pool usable, for a parent whose own threads may be mid-checkout.
-
#reset_redis_pools! ⇒ Object
Disconnect and drop cached pools.
- #server_middleware {|chain| ... } ⇒ Object
-
#server_middleware_missing?(klass) ⇒ Boolean
True when this capsule's server middleware chain has already been snapshotted and that snapshot does not carry
klass. -
#stop ⇒ Object
No-op in OSS.
-
#thread_priority ⇒ Object
Capsule-hosted components (Manager, Processor, Fetcher) hand their capsule to Component as
config, andsafe_threadreads the priority off it. - #to_h ⇒ Object
Constructor Details
#initialize(name, config) ⇒ Capsule
Returns a new instance of Capsule.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/wurk/capsule.rb', line 24 def initialize(name, config) @name = name.to_s @config = config @concurrency = config[:concurrency] || 5 @queues = ['default'] @mode = :strict @weights = { 'default' => 0 } @fetcher = nil # One mutable Hash rather than two ivars: the pools are the only part of a # capsule that legitimately changes after Configuration#freeze! — fork # closes them, Launcher#stop releases them, an embedded host that boots # again rebuilds them. `Object#freeze` is shallow, so the Hash stays # writable and freezing a capsule keeps meaning "no more configuration" # instead of "these sockets are yours forever". Before this, a reset on a # frozen capsule disconnected the pool and then raised FrozenError on the # memo, leaving `redis_pool` answering with a shut-down pool for good. @pools = {} @client_chain = nil @server_chain = nil @server_middleware_seen = [] @prepared = false @watchdog = nil end |
Instance Attribute Details
#concurrency ⇒ Object
Returns the value of attribute concurrency.
17 18 19 |
# File 'lib/wurk/capsule.rb', line 17 def concurrency @concurrency end |
#config ⇒ Object (readonly)
Returns the value of attribute config.
16 17 18 |
# File 'lib/wurk/capsule.rb', line 16 def config @config end |
#fetcher ⇒ Object
Returns the value of attribute fetcher.
17 18 19 |
# File 'lib/wurk/capsule.rb', line 17 def fetcher @fetcher end |
#mode ⇒ Object (readonly)
Returns the value of attribute mode.
16 17 18 |
# File 'lib/wurk/capsule.rb', line 16 def mode @mode end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
16 17 18 |
# File 'lib/wurk/capsule.rb', line 16 def name @name end |
#queues ⇒ Object
Returns the value of attribute queues.
16 17 18 |
# File 'lib/wurk/capsule.rb', line 16 def queues @queues end |
#watchdog ⇒ Object (readonly)
Returns the value of attribute watchdog.
16 17 18 |
# File 'lib/wurk/capsule.rb', line 16 def watchdog @watchdog end |
#weights ⇒ Object (readonly)
Returns the value of attribute weights.
16 17 18 |
# File 'lib/wurk/capsule.rb', line 16 def weights @weights end |
Instance Method Details
#client_middleware {|chain| ... } ⇒ Object
141 142 143 144 145 |
# File 'lib/wurk/capsule.rb', line 141 def client_middleware chain = (@client_chain ||= @config.client_middleware.copy_for(self)) yield chain if block_given? chain end |
#fetch_poll_interval ⇒ Object
Empty-poll BLMOVE backoff for this capsule's reliable fetcher (Pro super_fetch §3.3). nil → the fetcher falls back to its TIMEOUT default.
219 220 221 |
# File 'lib/wurk/capsule.rb', line 219 def fetch_poll_interval @config[:fetch_poll_interval] end |
#fetch_redis(idempotent: false) ⇒ Object
Checkout from the dedicated fetch pool. Only the reliable fetcher's blocking BLMOVE uses this, so a parked fetch never holds a main-pool slot.
209 210 211 |
# File 'lib/wurk/capsule.rb', line 209 def fetch_redis(idempotent: false, &) PoolCheckout.with(fetch_redis_pool, idempotent, &) end |
#fetch_redis_pool ⇒ Object
Dedicated pool for the reliable fetcher's blocking BLMOVE: one slot per
processor thread (concurrency), since at most that many threads park in
fetch at once. Keeping fetch off the main pool is what lets an idle worker
hold zero main-pool connections again.
179 180 181 |
# File 'lib/wurk/capsule.rb', line 179 def fetch_redis_pool @pools[:fetch] ||= build_pool(size: @concurrency, name: "#{@name}-fetch") end |
#logger ⇒ Object
223 224 225 |
# File 'lib/wurk/capsule.rb', line 223 def logger @config.logger end |
#lookup(name) ⇒ Object
213 214 215 |
# File 'lib/wurk/capsule.rb', line 213 def lookup(name) @config.lookup(name) end |
#prepare! ⇒ Object
Materialize everything that lazy-inits via ||= and default the fetcher,
BEFORE Configuration#freeze! freezes the capsule — otherwise the first
post-freeze access (a fetch tick, a middleware call) hits a nil fetcher
or FrozenErrors building a pool. The swarm's ChildBoot used to do this
by hand; centralizing it here covers the standalone CLI and embedded
paths too (the bug behind a nil fetcher in exe/wurk). Idempotent.
82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/wurk/capsule.rb', line 82 def prepare! prepare_shared! @fetcher ||= build_fetcher # Here rather than in the constructor so a client-only process — a web # dyno building the same Configuration — allocates nothing, and so the # scanner thread a bounded job spawns belongs to the child that runs it. # The object itself holds no thread until something is bounded. @watchdog ||= Watchdog.new(self) redis_pool fetch_redis_pool self end |
#prepare_shared! ⇒ Object
The half of prepare! a forking parent can run on every child's behalf:
the chains are a pure function of this capsule's identity, not of the slot
(queues + concurrency) a swarm child is assigned later, and copy_for
opens nothing. Run before the fork, the entries are allocated once and
inherited copy-on-write instead of rebuilt in every child.
The rest of prepare! deliberately stays post-fork: both pools are sized
off the slot's concurrency and one built here would hand every child an
inherited socket, and build_fetcher fires the host's
config[:fetch_setup] hook, which is per-child — running it in the parent
would let a custom fetcher snapshot the wrong queues, or leak whatever the
hook opened across the fork. Idempotent.
107 108 109 110 111 112 113 114 |
# File 'lib/wurk/capsule.rb', line 107 def prepare_shared! client_middleware refresh_server_middleware! # A second boot (a launcher run twice) finds the capsule frozen — the # flag is already set, so it is not written again. @prepared ||= true self end |
#prepared? ⇒ Boolean
True once this capsule has been prepared to run jobs — prepare_shared!
in the swarm parent before the fork, prepare! in the child at boot.
Only then is the memoized chain the one the workers run, and only then
is it final: a middleware registered on the Configuration afterwards can
never reach it.
121 122 123 |
# File 'lib/wurk/capsule.rb', line 121 def prepared? @prepared end |
#queue_specs ⇒ Object
Lossless name[,weight] specs (unlike queues, which is the
weight-expanded list). Lets Configuration#topology rebuild a slot that
round-trips back through queues= without flattening weights.
72 73 74 |
# File 'lib/wurk/capsule.rb', line 72 def queue_specs @weights.map { |q, w| w.positive? ? "#{q},#{w}" : q } end |
#redis(idempotent: false) ⇒ Object
203 204 205 |
# File 'lib/wurk/capsule.rb', line 203 def redis(idempotent: false, &) PoolCheckout.with(redis_pool, idempotent, &) end |
#redis_pool ⇒ Object
171 172 173 |
# File 'lib/wurk/capsule.rb', line 171 def redis_pool @pools[:main] ||= build_pool(size: main_pool_size, name: "#{@name}-main") end |
#redis_pools_built? ⇒ Boolean
199 200 201 |
# File 'lib/wurk/capsule.rb', line 199 def redis_pools_built? !@pools.empty? end |
#reload_redis_pools! ⇒ Object
The fork-path form: closes idle sockets but keeps every pool usable, for a parent whose own threads may be mid-checkout.
195 196 197 |
# File 'lib/wurk/capsule.rb', line 195 def reload_redis_pools! @pools.each_value(&:reload!) end |
#reset_redis_pools! ⇒ Object
Disconnect and drop cached pools. Called by Wurk::Swarm just before fork
(parent side: close inherited sockets), just after fork (child side:
rebuild lazily), and by Launcher#stop (release what this process held).
Connection_pool#shutdown is terminal, so dropping the reference is
required — redis_pool will rebuild.
188 189 190 191 |
# File 'lib/wurk/capsule.rb', line 188 def reset_redis_pools! @pools.each_value(&:disconnect!) @pools.clear end |
#server_middleware {|chain| ... } ⇒ Object
147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/wurk/capsule.rb', line 147 def server_middleware # copy_for(self) — not dup — binds the chain's `@config` to this capsule, # so middleware that reach for `redis_pool`/`redis`/`logger` resolve them # instead of hitting `nil` (a plain dup leaves @config nil). chain = (@server_chain ||= begin config_chain = @config.server_middleware @server_middleware_seen = config_chain.entries.map(&:klass) config_chain.copy_for(self) end) yield chain if block_given? chain end |
#server_middleware_missing?(klass) ⇒ Boolean
True when this capsule's server middleware chain has already been
snapshotted and that snapshot does not carry klass.
The snapshot is the copy server_middleware memoized; a middleware
registered on the Configuration after that copy was taken can never
reach this capsule — which is what Wurk::Encryption.enable has to
detect before it seals arguments the workers could not open.
Pure read: it never materializes the chain and has no side effects.
134 135 136 137 138 139 |
# File 'lib/wurk/capsule.rb', line 134 def server_middleware_missing?(klass) chain = @server_chain return false unless chain !chain.exists?(klass) end |
#stop ⇒ Object
No-op in OSS. Reserved for Pro/Ent hooks that need to flush per-capsule
state on shutdown. Manager#stop invokes this in ensure so the contract
is honored regardless of how stop unwinds.
Spec: docs/target/sidekiq-free.md §5.
232 |
# File 'lib/wurk/capsule.rb', line 232 def stop; end |
#thread_priority ⇒ Object
Capsule-hosted components (Manager, Processor, Fetcher) hand their capsule
to Component as config, and safe_thread reads the priority off it.
Sidekiq delegates the same accessor (capsule.rb:30).
22 |
# File 'lib/wurk/capsule.rb', line 22 def thread_priority = @config.thread_priority |
#to_h ⇒ Object
48 49 50 |
# File 'lib/wurk/capsule.rb', line 48 def to_h { concurrency: @concurrency, mode: @mode, weights: @weights } end |