Class: ActiveJob::DatabaseQueue
- Defined in:
- runtime/active_job/database_queue.rbs,
runtime/active_job/database_queue.rb
Constant Summary collapse
- ORPHANED_AFTER_SECONDS =
A row running longer than this belongs to a dead process — the next drain returns it to pending (at-least-once: the row's attempts counter has already recorded the lost run).
600- BATCH_SIZE =
The most rows one drain runs — the rest waits for the next drain, so one slow queue never holds the worker loop.
10- DRAIN_INTERVAL_SECONDS =
The seconds between idle drains: a drain that fills its batch keeps the loop coming straight back on its 50ms poll, so this interval gates only the idle check — the database sees one drain per interval instead of one per poll while the queue is empty.
1- LAST_DRAIN_AT =
The clock of the last drain — epoch seconds, shared state by design: one adapter object serves the process's worker loop.
[ 0 ]
- LAST_DRAIN_WAS_FULL =
True when the last drain ran
BATCH_SIZErows — a full drain means more due rows may be waiting, so the loop comes straight back instead of waiting out the clock. A shorter drain clears it, so an idle queue still checks once per second and no more. [ false ]
- DRAIN_FLAGS_LOCK =
Guards the two flags above.
WORKER_CONCURRENCYruns several worker threads in one process, and they read/write these together; the lock covers the flags only, never a drain's job time. Mutex.new
Class Method Summary collapse
-
.drain ⇒ Integer
release orphaned claims, then run up to
BATCH_SIZEdue rows oldest first, and answer how many ran. -
.drain_due? ⇒ Boolean
whether a drain is due: the interval passed since the last drain, OR the last drain ran a full batch.
-
.enqueue(job_name, work, payload) ⇒ void
hand off a job due now: write one pending row and return.
-
.enqueue_at(job_name, work, payload, scheduled_at) ⇒ void
hand off a delayed one-off: the row waits in pending until a drain's clock passes
scheduled_at. -
.enqueue_once(name, work, payload, scheduled_at) ⇒ Boolean
the recurring half: one row per (name, interval start), so several processes firing the same beat still queue the occurrence once.
Class Method Details
.drain ⇒ Integer
release orphaned claims, then run up to BATCH_SIZE due rows oldest
first, and answer how many ran. An unresolvable name or a raising body is
recorded on its row and the drain moves on — one bad job never costs the
queue the rest. A worker thread calls this under its own database lease,
and several worker threads may call it at once: the claim is a single
atomic UPDATE, so the rows are split between them and never duplicated (a
claim that lost the race answers nil and the drain ends). The drain flags
are mutex-guarded; a full batch sets LAST_DRAIN_WAS_FULL so the next
drain is due at once, a shorter drain — even an empty one — clears it and
the idle interval takes over. With N worker threads, N rows run at once
(one per worker thread) and a drain processes up to BATCH_SIZE rows per
worker thread; a row abandoned by a dead process returns to pending after
ORPHANED_AFTER_SECONDS.
138 |
# File 'runtime/active_job/database_queue.rb', line 138
def self.drain: () -> Integer
|
.drain_due? ⇒ Boolean
whether a drain is due: the interval passed since the last drain, OR the last drain ran a full batch. A full batch means more due rows may be waiting, so a worker thread (its 50ms poll) comes straight back and works through the backlog; a shorter drain clears the flag and the idle interval takes over. What runs inside a drain is decided by the rows, not by this gate. The read is mutex-guarded: concurrent worker threads share the flags.
117 |
# File 'runtime/active_job/database_queue.rb', line 117
def self.drain_due?: () -> bool
|
.enqueue(job_name, work, payload) ⇒ void
hand off a job due now: write one pending row and return. The body does not travel — the row carries the name and the payload, and a drain (this process or another) resolves them when the row is claimed.
60 |
# File 'runtime/active_job/database_queue.rb', line 60
def self.enqueue: (String job_name, ^() -> void work, untyped? payload) -> void
|
.enqueue_at(job_name, work, payload, scheduled_at) ⇒ void
hand off a delayed one-off: the row waits in pending until a drain's clock
passes scheduled_at. A one-off is NOT deduplicated: every call writes
its own row, so the same name and run time with different payloads are
different jobs.
76 |
# File 'runtime/active_job/database_queue.rb', line 76
def self.enqueue_at: (String job_name, ^() -> void work, untyped? payload, Time scheduled_at) -> void
|
.enqueue_once(name, work, payload, scheduled_at) ⇒ Boolean
the recurring half: one row per (name, interval start), so several
processes firing the same beat still queue the occurrence once. The ledger
insert and the queue row are one transaction — a ledger collision (another
process won) answers false with no row written, so the ledger can never
keep an occurrence whose row was lost. A one-off enqueue_at never comes
through here.
95 |
# File 'runtime/active_job/database_queue.rb', line 95
def self.enqueue_once: (String name, ^() -> void work, untyped? payload, Time scheduled_at) -> bool
|