solid_queue-listen_notify 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -1
- data/CLAUDE.md +1 -1
- data/lib/solid_queue/listen_notify/connection_provider.rb +21 -4
- data/lib/solid_queue/listen_notify/listener.rb +10 -28
- data/lib/solid_queue/listen_notify/preflight.rb +2 -15
- data/lib/solid_queue/listen_notify/registry.rb +36 -9
- data/lib/solid_queue/listen_notify/version.rb +1 -1
- data/lib/solid_queue/listen_notify.rb +29 -46
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e7cb2a646ad717d865cea7a0806f9e1606bcc8a7b1c75aefdcf0211a280b4dc
|
|
4
|
+
data.tar.gz: c973ecd38ed1cc6aac33ebf297ba5d7c8adbf9d6379973ebc9685cb4e131e6a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b1970e7d67ad65596a81e9edd4af43d8d38775296e9b97b4372c8eafb27a02206cd37ea86a82d0d5753fff7d12551e55b25f32b5343db914c16d17e8c14c498d
|
|
7
|
+
data.tar.gz: cdca29cbc3ad90d3337e8bd942b553612cd61951c75723fb10ff9fc3024acc6aec19bbc558b1e1b972fd493b29f2ce952a2153fd1e9d651491500ca8712e0b50
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.2] - 2026-07-28
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- A raising `ActiveSupport::Notifications` subscriber can no longer strand a worker at the raised polling interval mid-registration, or run between a notification and the worker wake-ups it triggers.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Internal: the registry is now the single owner of per-worker state (queue snapshot and replaced polling interval); the `notify` event's duration no longer includes the dispatch itself (payload unchanged).
|
|
19
|
+
|
|
10
20
|
## [0.5.1] - 2026-07-28
|
|
11
21
|
|
|
12
22
|
### Fixed
|
|
@@ -19,7 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
29
|
|
|
20
30
|
|
|
21
31
|
|
|
22
|
-
[Unreleased]: https://github.com/cmer/solid_queue-listen_notify/compare/v0.5.
|
|
32
|
+
[Unreleased]: https://github.com/cmer/solid_queue-listen_notify/compare/v0.5.2...HEAD
|
|
33
|
+
[0.5.2]: https://github.com/cmer/solid_queue-listen_notify/compare/v0.5.1...v0.5.2
|
|
23
34
|
[0.5.1]: https://github.com/cmer/solid_queue-listen_notify/compare/v0.5.0...v0.5.1
|
|
24
35
|
[0.5.0]: https://github.com/cmer/solid_queue-listen_notify/compare/v0.1.0...v0.5.0
|
|
25
36
|
[0.1.0]: https://github.com/cmer/solid_queue-listen_notify/releases/tag/v0.1.0
|
data/CLAUDE.md
CHANGED
|
@@ -36,7 +36,7 @@ bundle exec appraisal generate # after editi
|
|
|
36
36
|
|
|
37
37
|
`lib/solid_queue/listen_notify.rb` is the facade: config (`mattr_accessor`, mirroring solid_queue's style; copied from `config.solid_queue_listen_notify` by the Railtie), `register`/`deregister` (called from the lifecycle hooks; fully rescued — this gem must never make a worker look broken), the per-pid memoized `operational?`, and `after_fork`.
|
|
38
38
|
|
|
39
|
-
Flow at worker boot: `register(worker)` → `operational?` runs **Preflight** once per process → if operational, raise the worker's `polling_interval` to `fallback_polling_interval` (only ever raise
|
|
39
|
+
Flow at worker boot: `register(worker)` → `operational?` runs **Preflight** once per process → if operational, raise the worker's `polling_interval` to `fallback_polling_interval` (only ever raise) → `Registry.instance.register(worker, restore_interval:)` records the replaced interval with the worker and starts the **Listener** on first registration. The registry is the single owner of per-worker state (queue snapshot + replaced interval); on a fatal listener death it hands the `[worker, original_interval]` pairs back to the facade, which restores and wakes them.
|
|
40
40
|
|
|
41
41
|
- **`Preflight`** (`preflight.rb`) — decides operational-or-not; never raises; each failure prints an actionable WARN/ERROR banner (loud failure is a product requirement, not a nicety). Checks in order: enabled → channel 1–63 bytes → adapter is PG → trigger installed (auto-installed by default when missing) → trigger function body contains the exact `pg_notify('<channel>', ...)` call → **cross-connection self-test**: LISTEN on the listener's connection path, NOTIFY from a pooled queue-DB connection. Cross-connection is what detects both PgBouncer transaction pooling and a `listen_database` pointing at the wrong database.
|
|
42
42
|
- **`Registry`** (`registry.rb`) — per-process singleton multiplexing one listener to N workers (async mode runs N workers as threads in one process). Locking discipline is documented in its header comment and must be preserved: the mutex is never held across `wake_up`, `listener.stop`, or anything blocking; listeners are detached under the lock and stopped outside it. Cleanup is crash-tolerant, never hook-dependent (`on_worker_stop` is skipped on SIGQUIT `exit!`): dead workers are reaped on register and on the keepalive tick.
|
|
@@ -17,6 +17,27 @@ module SolidQueue
|
|
|
17
17
|
# Everything ActiveRecord-shaped in here is resolved inside method bodies:
|
|
18
18
|
# requiring this file must not require ActiveRecord.
|
|
19
19
|
class ConnectionProvider
|
|
20
|
+
# Best-effort teardown for a connection this provider handed out: nobody
|
|
21
|
+
# else can close it (it was removed from its pool), and it may already be
|
|
22
|
+
# broken, so both statements are attempted and both failures swallowed.
|
|
23
|
+
def self.release(connection, channel:)
|
|
24
|
+
return if connection.nil?
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
connection.execute("UNLISTEN #{connection.quote_column_name(channel)}")
|
|
28
|
+
rescue StandardError
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
begin
|
|
33
|
+
connection.disconnect!
|
|
34
|
+
rescue StandardError
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
20
41
|
def initialize
|
|
21
42
|
@mutex = Mutex.new
|
|
22
43
|
@pools = {}
|
|
@@ -30,10 +51,6 @@ module SolidQueue
|
|
|
30
51
|
end
|
|
31
52
|
end
|
|
32
53
|
|
|
33
|
-
def to_proc
|
|
34
|
-
method(:call).to_proc
|
|
35
|
-
end
|
|
36
|
-
|
|
37
54
|
# Drops the memoized isolated pools. The connections handed out so far are
|
|
38
55
|
# not affected: they were removed from those pools and belong to whoever
|
|
39
56
|
# asked for them.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "connection_provider"
|
|
4
|
+
|
|
3
5
|
module SolidQueue
|
|
4
6
|
module ListenNotify
|
|
5
7
|
# One background thread owning one dedicated Postgres connection: LISTENs on
|
|
@@ -220,24 +222,18 @@ module SolidQueue
|
|
|
220
222
|
end
|
|
221
223
|
end
|
|
222
224
|
|
|
225
|
+
# Dispatches first, instruments after: the ordering is what guarantees
|
|
226
|
+
# that no notifications subscriber — however it subscribed — can stand
|
|
227
|
+
# between a notification and the wake-ups it owes.
|
|
223
228
|
def handle_notification(payload)
|
|
224
229
|
@consecutive_failures = 0
|
|
225
230
|
queue_name = payload.to_s
|
|
226
|
-
dispatched = false
|
|
227
231
|
|
|
228
|
-
|
|
229
|
-
dispatched = true
|
|
230
|
-
counts = registry.dispatch(queue_name)
|
|
231
|
-
event.merge!(counts) if counts.is_a?(Hash)
|
|
232
|
-
end
|
|
232
|
+
counts = registry.dispatch(queue_name)
|
|
233
233
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
# form does run before it, though, and a notification dropped because
|
|
238
|
-
# somebody's metrics object raised would be a job left waiting for the
|
|
239
|
-
# next poll.
|
|
240
|
-
registry.dispatch(queue_name) unless dispatched
|
|
234
|
+
event_payload = { queue_name: queue_name }
|
|
235
|
+
event_payload.merge!(counts) if counts.is_a?(Hash)
|
|
236
|
+
instrument(:notify, **event_payload)
|
|
241
237
|
end
|
|
242
238
|
|
|
243
239
|
def tick_keepalive
|
|
@@ -319,24 +315,10 @@ module SolidQueue
|
|
|
319
315
|
nil
|
|
320
316
|
end
|
|
321
317
|
|
|
322
|
-
# Best effort on both statements: this runs on connections we already
|
|
323
|
-
# know may be broken.
|
|
324
318
|
def safe_disconnect
|
|
325
319
|
connection = @connection
|
|
326
320
|
@connection = nil
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
begin
|
|
330
|
-
connection.execute("UNLISTEN #{connection.quote_column_name(channel)}")
|
|
331
|
-
rescue StandardError
|
|
332
|
-
nil
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
begin
|
|
336
|
-
connection.disconnect!
|
|
337
|
-
rescue StandardError
|
|
338
|
-
nil
|
|
339
|
-
end
|
|
321
|
+
ConnectionProvider.release(connection, channel: channel)
|
|
340
322
|
end
|
|
341
323
|
|
|
342
324
|
# Sleeps in wait_timeout-sized slices so that #stop is never delayed by
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "trigger_installer"
|
|
4
|
+
require_relative "connection_provider"
|
|
4
5
|
|
|
5
6
|
module SolidQueue
|
|
6
7
|
module ListenNotify
|
|
@@ -176,7 +177,7 @@ module SolidQueue
|
|
|
176
177
|
end
|
|
177
178
|
ensure
|
|
178
179
|
# Nobody else can: this connection was removed from its pool.
|
|
179
|
-
|
|
180
|
+
ConnectionProvider.release(connection, channel: channel)
|
|
180
181
|
end
|
|
181
182
|
end
|
|
182
183
|
|
|
@@ -190,20 +191,6 @@ module SolidQueue
|
|
|
190
191
|
end
|
|
191
192
|
end
|
|
192
193
|
|
|
193
|
-
def disconnect(connection)
|
|
194
|
-
begin
|
|
195
|
-
connection.execute("UNLISTEN #{connection.quote_column_name(channel)}")
|
|
196
|
-
rescue StandardError
|
|
197
|
-
nil
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
begin
|
|
201
|
-
connection.disconnect!
|
|
202
|
-
rescue StandardError
|
|
203
|
-
nil
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
|
|
207
194
|
# Two statements, one connection: a transaction-mode pooler will very
|
|
208
195
|
# likely serve them from two different backends. Only ever used to
|
|
209
196
|
# enrich the self-test failure, never to decide anything.
|
|
@@ -20,7 +20,15 @@ module SolidQueue
|
|
|
20
20
|
# is what guarantees "at most one listener per non-empty transition" when
|
|
21
21
|
# several worker threads register concurrently.
|
|
22
22
|
# * #dispatch runs on the listener thread and performs zero database work.
|
|
23
|
+
#
|
|
24
|
+
# The registry is the single owner of per-worker state: the queue snapshot
|
|
25
|
+
# used to route notifications, and the polling interval the module replaced
|
|
26
|
+
# at registration time — kept so that a listener which dies for good can
|
|
27
|
+
# hand every worker back to be put where it was found. One map, one lock,
|
|
28
|
+
# one fork-reset path.
|
|
23
29
|
class Registry
|
|
30
|
+
# Everything this process knows about a registered worker.
|
|
31
|
+
Entry = Struct.new(:queues, :restore_interval)
|
|
24
32
|
class << self
|
|
25
33
|
# A class-level ivar rather than a constant, because the fork hook has to
|
|
26
34
|
# be able to REPLACE it: fork() copies only the calling thread, and this
|
|
@@ -70,17 +78,23 @@ module SolidQueue
|
|
|
70
78
|
def initialize(listener_factory: nil)
|
|
71
79
|
@listener_factory = listener_factory || method(:build_default_listener)
|
|
72
80
|
@mutex = Mutex.new
|
|
73
|
-
@workers =
|
|
81
|
+
@workers = empty_workers
|
|
74
82
|
@listener = nil
|
|
75
83
|
@pid = ::Process.pid
|
|
76
84
|
end
|
|
77
85
|
|
|
78
|
-
|
|
86
|
+
# `restore_interval` is the polling interval the module replaced when it
|
|
87
|
+
# raised this worker's — nil when nothing was changed. It is recorded here,
|
|
88
|
+
# with the worker, so that the crash path has one place to look.
|
|
89
|
+
def register(worker, restore_interval: nil)
|
|
79
90
|
guard_fork!
|
|
80
91
|
|
|
81
92
|
@mutex.synchronize do
|
|
82
93
|
sweep_dead_locked
|
|
83
|
-
@workers[worker] ||= snapshot_queues(worker)
|
|
94
|
+
entry = (@workers[worker] ||= Entry.new(snapshot_queues(worker), nil))
|
|
95
|
+
# ||=, not =: a re-registration must never overwrite the interval the
|
|
96
|
+
# worker originally had with one this gem already raised.
|
|
97
|
+
entry.restore_interval ||= restore_interval
|
|
84
98
|
start_listener_locked
|
|
85
99
|
end
|
|
86
100
|
|
|
@@ -118,7 +132,13 @@ module SolidQueue
|
|
|
118
132
|
next false unless @listener.equal?(listener)
|
|
119
133
|
|
|
120
134
|
@listener = nil
|
|
121
|
-
|
|
135
|
+
# Hand each interval over exactly once: a second crash after a revival
|
|
136
|
+
# must not "restore" workers that are already back on their own.
|
|
137
|
+
stranded = @workers.map do |worker, entry|
|
|
138
|
+
pair = [ worker, entry.restore_interval ]
|
|
139
|
+
entry.restore_interval = nil
|
|
140
|
+
pair
|
|
141
|
+
end
|
|
122
142
|
true
|
|
123
143
|
end
|
|
124
144
|
|
|
@@ -141,8 +161,8 @@ module SolidQueue
|
|
|
141
161
|
skipped_saturated = 0
|
|
142
162
|
unreachable = nil
|
|
143
163
|
|
|
144
|
-
pairs.each do |worker,
|
|
145
|
-
next unless QueueMatcher.matches?(queues, queue_name)
|
|
164
|
+
pairs.each do |worker, entry|
|
|
165
|
+
next unless QueueMatcher.matches?(entry.queues, queue_name)
|
|
146
166
|
|
|
147
167
|
if !wake_saturated && pool_saturated?(worker)
|
|
148
168
|
skipped_saturated += 1
|
|
@@ -202,7 +222,7 @@ module SolidQueue
|
|
|
202
222
|
# single-threaded here: replace it instead of trying to take it.
|
|
203
223
|
@mutex = Mutex.new
|
|
204
224
|
@listener = nil
|
|
205
|
-
@workers =
|
|
225
|
+
@workers = empty_workers
|
|
206
226
|
@pid = ::Process.pid
|
|
207
227
|
|
|
208
228
|
ListenNotify.instrument(:fork_detected, parent_pid: parent_pid, pid: @pid)
|
|
@@ -217,7 +237,7 @@ module SolidQueue
|
|
|
217
237
|
listener = @mutex.synchronize do
|
|
218
238
|
current = @listener
|
|
219
239
|
@listener = nil
|
|
220
|
-
@workers =
|
|
240
|
+
@workers = empty_workers
|
|
221
241
|
current
|
|
222
242
|
end
|
|
223
243
|
|
|
@@ -250,7 +270,14 @@ module SolidQueue
|
|
|
250
270
|
|
|
251
271
|
# Caller holds @mutex.
|
|
252
272
|
def sweep_dead_locked
|
|
253
|
-
@workers.delete_if { |worker,
|
|
273
|
+
@workers.delete_if { |worker, _entry| !worker_alive?(worker) }
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Keyed by identity: a Solid Queue worker defines no #hash or #eql?, and
|
|
277
|
+
# relying on the default object identity implicitly would be relying on
|
|
278
|
+
# something nobody wrote down.
|
|
279
|
+
def empty_workers
|
|
280
|
+
{}.compare_by_identity
|
|
254
281
|
end
|
|
255
282
|
|
|
256
283
|
def reap(workers)
|
|
@@ -60,12 +60,6 @@ module SolidQueue
|
|
|
60
60
|
@@preflight_result = nil
|
|
61
61
|
@@fallback_logger = nil
|
|
62
62
|
|
|
63
|
-
# worker => the polling interval it had before we raised it. Kept so that a
|
|
64
|
-
# listener which dies for good can put every worker back where it found it,
|
|
65
|
-
# rather than leaving it polling every 10 seconds with nothing to wake it.
|
|
66
|
-
@@overrides_lock = Mutex.new
|
|
67
|
-
@@overrides = {}.compare_by_identity
|
|
68
|
-
|
|
69
63
|
# `SOLID_QUEUE_LISTEN_NOTIFY_ENABLED=false` is documented as a kill switch
|
|
70
64
|
# that needs no deploy, and an application that sets `enabled` explicitly —
|
|
71
65
|
# to either value — would otherwise overwrite the mattr default and take
|
|
@@ -107,8 +101,15 @@ module SolidQueue
|
|
|
107
101
|
return nil unless operational?
|
|
108
102
|
|
|
109
103
|
restore_to = raise_polling_interval(worker)
|
|
110
|
-
Registry.instance.register(worker)
|
|
111
|
-
|
|
104
|
+
Registry.instance.register(worker, restore_interval: restore_to)
|
|
105
|
+
|
|
106
|
+
# Instrumented only once the registry has recorded the override, so that
|
|
107
|
+
# a subscriber raising here finds the bookkeeping already consistent — it
|
|
108
|
+
# can never strand a raised interval that nobody remembers.
|
|
109
|
+
if restore_to
|
|
110
|
+
instrument :override_polling_interval,
|
|
111
|
+
worker_name: worker_name(worker), from: restore_to, to: fallback_polling_interval.to_f
|
|
112
|
+
end
|
|
112
113
|
nil
|
|
113
114
|
rescue StandardError, ScriptError => e
|
|
114
115
|
# A worker left polling every 10 seconds with nothing to wake it is the one
|
|
@@ -121,12 +122,11 @@ module SolidQueue
|
|
|
121
122
|
# on_worker_stop is not guaranteed to run at all (a supervisor that exceeds
|
|
122
123
|
# its shutdown timeout exits without callbacks), so this is a convenience,
|
|
123
124
|
# never the thing cleanup depends on.
|
|
125
|
+
#
|
|
126
|
+
# Never instantiate the registry just to deregister: this hook also runs in
|
|
127
|
+
# processes where nothing ever registered, and building a registry there
|
|
128
|
+
# would be building one that only exists to be empty.
|
|
124
129
|
def deregister(worker)
|
|
125
|
-
forget_override(worker)
|
|
126
|
-
|
|
127
|
-
# Never instantiate the registry just to deregister: this hook also runs in
|
|
128
|
-
# processes where nothing ever registered, and building a registry there
|
|
129
|
-
# would be building one that only exists to be empty.
|
|
130
130
|
return nil unless Registry.instantiated?
|
|
131
131
|
|
|
132
132
|
Registry.instance.deregister(worker)
|
|
@@ -136,7 +136,9 @@ module SolidQueue
|
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
# Called by the registry when the listener thread died for good — a fatal,
|
|
139
|
-
# non-connection error, which no reconnect will fix.
|
|
139
|
+
# non-connection error, which no reconnect will fix. `stranded` is the
|
|
140
|
+
# registry's list of [worker, original_interval] pairs; the interval is nil
|
|
141
|
+
# for workers whose interval was never raised.
|
|
140
142
|
#
|
|
141
143
|
# The gem raised these workers' polling intervals on the strength of a
|
|
142
144
|
# promise it can no longer keep, and a worker left polling every 10 seconds
|
|
@@ -144,8 +146,8 @@ module SolidQueue
|
|
|
144
146
|
# gem. So the promise is withdrawn: every interval we raised goes back, every
|
|
145
147
|
# worker is woken so it re-reads it rather than finishing its current sleep,
|
|
146
148
|
# and the whole thing is said out loud.
|
|
147
|
-
def listener_crashed(error,
|
|
148
|
-
restored = Array(
|
|
149
|
+
def listener_crashed(error, stranded)
|
|
150
|
+
restored = Array(stranded).count { |worker, interval| restore_override(worker, interval) }
|
|
149
151
|
announce_listener_death(error, restored)
|
|
150
152
|
nil
|
|
151
153
|
rescue StandardError, ScriptError => e
|
|
@@ -184,18 +186,16 @@ module SolidQueue
|
|
|
184
186
|
# belongs to the preflight lock: `operational?` holds it across connecting to
|
|
185
187
|
# Postgres and a self-test that waits up to two seconds, and a child that
|
|
186
188
|
# inherited it locked would hang the first worker to register — permanently,
|
|
187
|
-
# inside a lifecycle hook, with no error and no output. Replacing
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
189
|
+
# inside a lifecycle hook, with no error and no output. Replacing a mutex in
|
|
190
|
+
# a process that is still single-threaded costs nothing, and it means the
|
|
191
|
+
# gem is not relying on an interpreter implementation detail to avoid its
|
|
192
|
+
# worst failure mode.
|
|
191
193
|
#
|
|
192
|
-
# Order matters:
|
|
194
|
+
# Order matters: the lock first, then the guards that use it. The preflight
|
|
193
195
|
# VERDICT needs no reset (it is stamped with the pid that reached it, so a
|
|
194
196
|
# child recomputes its own).
|
|
195
197
|
def after_fork
|
|
196
198
|
@@preflight_lock = Mutex.new
|
|
197
|
-
@@overrides_lock = Mutex.new
|
|
198
|
-
@@overrides = {}.compare_by_identity
|
|
199
199
|
|
|
200
200
|
connection_provider.forget_pools! if connection_provider.respond_to?(:forget_pools!)
|
|
201
201
|
Registry.after_fork!
|
|
@@ -228,7 +228,6 @@ module SolidQueue
|
|
|
228
228
|
# preflight to run again after reconfiguring.
|
|
229
229
|
def reset!
|
|
230
230
|
@@preflight_lock.synchronize { @@preflight_result = nil }
|
|
231
|
-
@@overrides_lock.synchronize { @@overrides = {}.compare_by_identity }
|
|
232
231
|
connection_provider.reset! if connection_provider.respond_to?(:reset!)
|
|
233
232
|
nil
|
|
234
233
|
end
|
|
@@ -292,6 +291,8 @@ module SolidQueue
|
|
|
292
291
|
# all only because operational? proved notifications arrive.
|
|
293
292
|
#
|
|
294
293
|
# Returns the interval that was replaced, or nil when nothing changed.
|
|
294
|
+
# Deliberately does NOT instrument: the caller does, once the registry
|
|
295
|
+
# has recorded what happened here.
|
|
295
296
|
def raise_polling_interval(worker)
|
|
296
297
|
target = fallback_polling_interval&.to_f
|
|
297
298
|
return nil if target.nil?
|
|
@@ -301,7 +302,6 @@ module SolidQueue
|
|
|
301
302
|
return nil if current.nil? || target <= current.to_f
|
|
302
303
|
|
|
303
304
|
worker.polling_interval = target
|
|
304
|
-
instrument :override_polling_interval, worker_name: worker_name(worker), from: current, to: target
|
|
305
305
|
current
|
|
306
306
|
end
|
|
307
307
|
|
|
@@ -313,27 +313,10 @@ module SolidQueue
|
|
|
313
313
|
nil
|
|
314
314
|
end
|
|
315
315
|
|
|
316
|
-
#
|
|
317
|
-
#
|
|
318
|
-
#
|
|
319
|
-
|
|
320
|
-
# something nobody wrote down.
|
|
321
|
-
|
|
322
|
-
def remember_override(worker, previous)
|
|
323
|
-
return if previous.nil?
|
|
324
|
-
|
|
325
|
-
@@overrides_lock.synchronize { @@overrides[worker] = previous }
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
def forget_override(worker)
|
|
329
|
-
@@overrides_lock.synchronize { @@overrides.delete(worker) }
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
# Puts one worker back on its own interval and wakes it, so that it
|
|
333
|
-
# re-reads the interval now rather than at the end of the sleep it is
|
|
334
|
-
# already in. Returns whether anything was actually restored.
|
|
335
|
-
def restore_override(worker)
|
|
336
|
-
previous = @@overrides_lock.synchronize { @@overrides.delete(worker) }
|
|
316
|
+
# Puts one worker back on the interval the registry remembered for it and
|
|
317
|
+
# wakes it, so that it re-reads the interval now rather than at the end of
|
|
318
|
+
# the sleep it is already in. Returns whether anything was restored.
|
|
319
|
+
def restore_override(worker, previous)
|
|
337
320
|
return false if previous.nil?
|
|
338
321
|
|
|
339
322
|
current = current_polling_interval(worker)
|