Module: RoundhouseUi::Cancellation
- Defined in:
- lib/roundhouse_ui/cancellation.rb
Overview
Cooperative job cancellation — pure OSS, no preemption (Ruby can't safely kill a running thread). Cancelled JIDs live in a Redis set:
* RoundhouseUi::CancelMiddleware skips a job whose JID is cancelled when it
is *about* to run (covers queued/scheduled/retry jobs).
* A long-running job can call RoundhouseUi.cancelled?(jid) and bail out.
The set expires so stale flags clean themselves up.
Constant Summary collapse
- KEY =
"roundhouse:cancelled"- TTL =
seconds
86_400- CHECK_EVERY =
seconds — max staleness of the "nothing is cancelled" gate
2.0
Class Method Summary collapse
- .any? ⇒ Boolean
- .cancel!(jid) ⇒ Object
- .cancelled?(jid) ⇒ Boolean
- .cancelled_jids ⇒ Object
- .clear!(jid) ⇒ Object
- .monotonic_now ⇒ Object
-
.pending? ⇒ Boolean
The middleware's hot-path gate: "is anything cancelled at all?" — almost always no, so answering it with one EXISTS per process per CHECK_EVERY (instead of a SISMEMBER per job) takes the common case to zero Redis round-trips.
-
.reset_gate! ⇒ Object
Forget the cached gate state (tests; or after flushing Redis by hand).
Class Method Details
.any? ⇒ Boolean
66 67 68 |
# File 'lib/roundhouse_ui/cancellation.rb', line 66 def any? Sidekiq.redis { |conn| conn.call("EXISTS", KEY) } == 1 end |
.cancel!(jid) ⇒ Object
21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/roundhouse_ui/cancellation.rb', line 21 def cancel!(jid) Sidekiq.redis do |conn| conn.call("SADD", KEY, jid.to_s) conn.call("EXPIRE", KEY, TTL) end # Bust the local gate: the cancelling process sees its own cancel # immediately; other processes converge within CHECK_EVERY. @gate.synchronize do @pending = true @checked_at = monotonic_now end end |
.cancelled?(jid) ⇒ Boolean
34 35 36 |
# File 'lib/roundhouse_ui/cancellation.rb', line 34 def cancelled?(jid) Sidekiq.redis { |conn| conn.call("SISMEMBER", KEY, jid.to_s) } == 1 end |
.cancelled_jids ⇒ Object
42 43 44 |
# File 'lib/roundhouse_ui/cancellation.rb', line 42 def cancelled_jids Sidekiq.redis { |conn| conn.call("SMEMBERS", KEY) } end |
.clear!(jid) ⇒ Object
38 39 40 |
# File 'lib/roundhouse_ui/cancellation.rb', line 38 def clear!(jid) Sidekiq.redis { |conn| conn.call("SREM", KEY, jid.to_s) } end |
.monotonic_now ⇒ Object
78 |
# File 'lib/roundhouse_ui/cancellation.rb', line 78 def monotonic_now = Process.clock_gettime(Process::CLOCK_MONOTONIC) |
.pending? ⇒ Boolean
The middleware's hot-path gate: "is anything cancelled at all?" — almost always no, so answering it with one EXISTS per process per CHECK_EVERY (instead of a SISMEMBER per job) takes the common case to zero Redis round-trips. While cancellations are pending, the middleware still does the exact per-job SISMEMBER. A cancel issued by another process is invisible here for up to CHECK_EVERY — acceptable because cancellation is cooperative and racy by design (a job may already be running when the flag lands), and cancel! busts the local gate so the cancelling process itself is always exact.
55 56 57 58 59 60 61 62 63 64 |
# File 'lib/roundhouse_ui/cancellation.rb', line 55 def pending? now = monotonic_now @gate.synchronize do if @checked_at.nil? || now - @checked_at >= CHECK_EVERY @pending = any? @checked_at = now end @pending end end |
.reset_gate! ⇒ Object
Forget the cached gate state (tests; or after flushing Redis by hand).
71 72 73 74 75 76 |
# File 'lib/roundhouse_ui/cancellation.rb', line 71 def reset_gate! @gate.synchronize do @pending = nil @checked_at = nil end end |