Class: Alplus::Worker
- Inherits:
-
Object
- Object
- Alplus::Worker
- Defined in:
- lib/alplus/worker.rb
Overview
Background-thread sender with a bounded queue: #enqueue never blocks
the caller. When the queue is full, the event is dropped (non-
authoritative, matching the JS SDK's own batching drop behavior) rather
than applying backpressure to the request thread (issue #14 story 7/8).
The worker thread is lazily started on first enqueue and is never
joined/killed explicitly: Ruby terminates all non-main threads when the
process exits, so there is nothing to supervise for a short-lived
script or a Rack::Handler process to leak.
One Worker instance is ONE independent delivery lane: its own
SizedQueue and its own background thread, fixed to one kind:
(:error or :session) for its whole lifetime (issue #12 fix). A
PRIOR version routed both kinds through a single shared queue/thread —
a stalled or slow /e/errors POST (up to ~20s across
Retry::MAX_ATTEMPTS retries) head-of-line-blocked every queued
session behind it, and a full queue during an error storm silently
dropped the next session, exactly when crash-free data matters most.
Client now owns two Workers so error backpressure can never delay
or drop session delivery, or vice versa.
Instance Method Summary collapse
-
#enqueue(envelope) ⇒ Object
Enqueues an envelope for background delivery on THIS worker's own lane (its
kind:, fixed at construction). -
#flush(timeout: 2) ⇒ Object
Blocks up to
timeoutseconds for the queue to drain and any in-flight send to finish. -
#initialize(config, transport, kind: :error) ⇒ Worker
constructor
A new instance of Worker.
- #queue_size ⇒ Object
Constructor Details
#initialize(config, transport, kind: :error) ⇒ Worker
Returns a new instance of Worker.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/alplus/worker.rb', line 27 def initialize(config, transport, kind: :error) @config = config @transport = transport @kind = kind @queue = SizedQueue.new(config.max_queue_size) @mutex = Mutex.new @thread = nil # Outstanding work count: incremented (under `@mutex`) the moment an # envelope is successfully pushed, decremented only after # `Transport#send_envelope` returns. `SizedQueue#pop` removes an item # from the queue *before* the worker thread finishes sending it, so # `@queue.empty?` alone goes true while a send is still in flight — # a concurrent `#flush`/`#close` reading only queue emptiness would # return early (TOCTOU). Counting outstanding work instead of # sampling a flag set after pop closes that window: the increment # happens atomically with the enqueue that a caller already observed # succeeding, not after some later point the reader could race. @outstanding = 0 end |
Instance Method Details
#enqueue(envelope) ⇒ Object
Enqueues an envelope for background delivery on THIS worker's own
lane (its kind:, fixed at construction). Returns true if queued,
false if dropped because the queue is full. Never raises.
50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/alplus/worker.rb', line 50 def enqueue(envelope) @mutex.synchronize do @queue.push(envelope, true) @outstanding += 1 end ensure_thread_started true rescue ThreadError @config.logger&.warn("[alplus] #{@kind} queue full (max #{@config.max_queue_size}); dropping event") false end |
#flush(timeout: 2) ⇒ Object
Blocks up to timeout seconds for the queue to drain and any
in-flight send to finish. Returns true if it drained in time,
false on timeout. Never raises.
65 66 67 68 69 |
# File 'lib/alplus/worker.rb', line 65 def flush(timeout: 2) deadline = Time.now + timeout sleep(0.01) while !idle? && Time.now < deadline idle? end |
#queue_size ⇒ Object
71 72 73 |
# File 'lib/alplus/worker.rb', line 71 def queue_size @queue.size end |