pgbus 0.7.5 → 0.7.7
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/lib/pgbus/active_job/executor.rb +11 -1
- data/lib/pgbus/client/ensure_stream_queue.rb +12 -9
- data/lib/pgbus/client.rb +89 -43
- data/lib/pgbus/version.rb +1 -1
- 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: af9ea5eec454c7ecd4ca32a368382851d96ff7790ef9eb0661877e98eac02a07
|
|
4
|
+
data.tar.gz: 9b8b0d754071dc641cacf3691340ef499da00f7485987538f4c67dacc7d8f1cc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15058498b3ca28d3e882fa3e01358c55f7cdd78b3a17cdb02c59c047a7194565e157a3889fda99afadd1439cd50807fa1d19d6e07c6c24a61919406111d9592b
|
|
7
|
+
data.tar.gz: 7032ebd0693d0c6eb4276489587a57a16207f305b58f324c789da4e3c0bb9032b81fe9e1c1fcf5f1bac1d58fe50864a819c4ad896bc6d8fc06cc816c801a6ef3
|
|
@@ -107,12 +107,22 @@ module Pgbus
|
|
|
107
107
|
|
|
108
108
|
def execute_job(job)
|
|
109
109
|
if defined?(Rails) && Rails.respond_to?(:application) && Rails.application
|
|
110
|
-
Rails.application.
|
|
110
|
+
wrapper = reloading? ? Rails.application.reloader : Rails.application.executor
|
|
111
|
+
wrapper.wrap { job.perform_now }
|
|
111
112
|
else
|
|
112
113
|
job.perform_now
|
|
113
114
|
end
|
|
114
115
|
end
|
|
115
116
|
|
|
117
|
+
def reloading?
|
|
118
|
+
app_config = Rails.application.config
|
|
119
|
+
if app_config.respond_to?(:enable_reloading)
|
|
120
|
+
app_config.enable_reloading
|
|
121
|
+
else
|
|
122
|
+
!app_config.cache_classes
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
116
126
|
def monotonic_now
|
|
117
127
|
::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
118
128
|
end
|
|
@@ -19,17 +19,20 @@ module Pgbus
|
|
|
19
19
|
# stream and from the streamer on first subscription per stream.
|
|
20
20
|
module EnsureStreamQueue
|
|
21
21
|
def ensure_stream_queue(stream_name)
|
|
22
|
-
ensure_queue(stream_name)
|
|
23
22
|
full_name = config.queue_name(stream_name)
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
with_stale_connection_retry do
|
|
25
|
+
ensure_queue(stream_name)
|
|
26
|
+
|
|
27
|
+
# PGMQ's default NOTIFY throttle is 250ms — meant to coalesce
|
|
28
|
+
# high-frequency worker queue inserts. Streams are latency-
|
|
29
|
+
# sensitive and need every broadcast to fire a NOTIFY, even
|
|
30
|
+
# when several are batched within a single millisecond.
|
|
31
|
+
# Override the throttle to 0 specifically for stream queues.
|
|
32
|
+
# Use the idempotent path to avoid deadlocks when multiple
|
|
33
|
+
# processes race to set up the same stream queue.
|
|
34
|
+
synchronized { enable_notify_if_needed(full_name, 0) }
|
|
35
|
+
end
|
|
33
36
|
|
|
34
37
|
# CREATE INDEX IF NOT EXISTS is idempotent in Postgres but still
|
|
35
38
|
# requires a roundtrip and a brief ACCESS SHARE lock on the archive
|
data/lib/pgbus/client.rb
CHANGED
|
@@ -85,9 +85,9 @@ module Pgbus
|
|
|
85
85
|
|
|
86
86
|
def send_message(queue_name, payload, headers: nil, delay: 0, priority: nil)
|
|
87
87
|
target = @queue_strategy.target_queue(queue_name, priority)
|
|
88
|
-
ensure_queue(queue_name)
|
|
89
88
|
Instrumentation.instrument("pgbus.client.send_message", queue: target) do
|
|
90
89
|
with_stale_connection_retry do
|
|
90
|
+
ensure_queue(queue_name)
|
|
91
91
|
synchronized { @pgmq.produce(target, serialize(payload), headers: headers && serialize(headers), delay: delay) }
|
|
92
92
|
end
|
|
93
93
|
end
|
|
@@ -95,10 +95,10 @@ module Pgbus
|
|
|
95
95
|
|
|
96
96
|
def send_batch(queue_name, payloads, headers: nil, delay: 0)
|
|
97
97
|
full_name = config.queue_name(queue_name)
|
|
98
|
-
ensure_queue(queue_name)
|
|
99
98
|
serialized, serialized_headers = serialize_batch(payloads, headers)
|
|
100
99
|
Instrumentation.instrument("pgbus.client.send_batch", queue: full_name, size: payloads.size) do
|
|
101
100
|
with_stale_connection_retry do
|
|
101
|
+
ensure_queue(queue_name)
|
|
102
102
|
synchronized { @pgmq.produce_batch(full_name, serialized, headers: serialized_headers, delay: delay) }
|
|
103
103
|
end
|
|
104
104
|
end
|
|
@@ -107,14 +107,18 @@ module Pgbus
|
|
|
107
107
|
def read_message(queue_name, vt: nil)
|
|
108
108
|
full_name = config.queue_name(queue_name)
|
|
109
109
|
Instrumentation.instrument("pgbus.client.read_message", queue: full_name) do
|
|
110
|
-
|
|
110
|
+
with_stale_connection_retry do
|
|
111
|
+
synchronized { @pgmq.read(full_name, vt: vt || config.visibility_timeout) }
|
|
112
|
+
end
|
|
111
113
|
end
|
|
112
114
|
end
|
|
113
115
|
|
|
114
116
|
def read_batch(queue_name, qty:, vt: nil)
|
|
115
117
|
full_name = config.queue_name(queue_name)
|
|
116
118
|
Instrumentation.instrument("pgbus.client.read_batch", queue: full_name, qty: qty) do
|
|
117
|
-
|
|
119
|
+
with_stale_connection_retry do
|
|
120
|
+
synchronized { @pgmq.read_batch(full_name, vt: vt || config.visibility_timeout, qty: qty) }
|
|
121
|
+
end
|
|
118
122
|
end
|
|
119
123
|
end
|
|
120
124
|
|
|
@@ -134,7 +138,9 @@ module Pgbus
|
|
|
134
138
|
break if remaining <= 0
|
|
135
139
|
|
|
136
140
|
msgs = Instrumentation.instrument("pgbus.client.read_batch", queue: pq_name, qty: remaining) do
|
|
137
|
-
|
|
141
|
+
with_stale_connection_retry do
|
|
142
|
+
synchronized { @pgmq.read_batch(pq_name, vt: vt || config.visibility_timeout, qty: remaining) }
|
|
143
|
+
end
|
|
138
144
|
end || []
|
|
139
145
|
|
|
140
146
|
msgs.each { |m| results << [pq_name, m] }
|
|
@@ -146,14 +152,16 @@ module Pgbus
|
|
|
146
152
|
|
|
147
153
|
def read_with_poll(queue_name, qty:, vt: nil, max_poll_seconds: 5, poll_interval_ms: 100)
|
|
148
154
|
full_name = config.queue_name(queue_name)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
with_stale_connection_retry do
|
|
156
|
+
synchronized do
|
|
157
|
+
@pgmq.read_with_poll(
|
|
158
|
+
full_name,
|
|
159
|
+
vt: vt || config.visibility_timeout,
|
|
160
|
+
qty: qty,
|
|
161
|
+
max_poll_seconds: max_poll_seconds,
|
|
162
|
+
poll_interval_ms: poll_interval_ms
|
|
163
|
+
)
|
|
164
|
+
end
|
|
157
165
|
end
|
|
158
166
|
end
|
|
159
167
|
|
|
@@ -168,8 +176,10 @@ module Pgbus
|
|
|
168
176
|
def read_multi(queue_names, qty:, vt: nil, limit: nil)
|
|
169
177
|
full_names = queue_names.map { |q| config.queue_name(q) }
|
|
170
178
|
Instrumentation.instrument("pgbus.client.read_multi", queues: full_names, qty: qty, limit: limit) do
|
|
171
|
-
|
|
172
|
-
|
|
179
|
+
with_stale_connection_retry do
|
|
180
|
+
synchronized do
|
|
181
|
+
@pgmq.read_multi(full_names, vt: vt || config.visibility_timeout, qty: qty, limit: limit)
|
|
182
|
+
end
|
|
173
183
|
end
|
|
174
184
|
end
|
|
175
185
|
end
|
|
@@ -178,74 +188,99 @@ module Pgbus
|
|
|
178
188
|
# the full PGMQ queue name (e.g. from priority sub-queues or dashboard).
|
|
179
189
|
def delete_message(queue_name, msg_id, prefixed: true)
|
|
180
190
|
name = prefixed ? config.queue_name(queue_name) : queue_name
|
|
181
|
-
|
|
191
|
+
with_stale_connection_retry do
|
|
192
|
+
synchronized { @pgmq.delete(name, msg_id) }
|
|
193
|
+
end
|
|
182
194
|
end
|
|
183
195
|
|
|
184
196
|
# Archive a message. Pass prefixed: false when queue_name is already
|
|
185
197
|
# the full PGMQ queue name.
|
|
186
198
|
def archive_message(queue_name, msg_id, prefixed: true)
|
|
187
199
|
name = prefixed ? config.queue_name(queue_name) : queue_name
|
|
188
|
-
|
|
200
|
+
with_stale_connection_retry do
|
|
201
|
+
synchronized { @pgmq.archive(name, msg_id) }
|
|
202
|
+
end
|
|
189
203
|
end
|
|
190
204
|
|
|
191
205
|
# Batch archive — moves multiple messages to the archive table in one call.
|
|
192
206
|
def archive_batch(queue_name, msg_ids, prefixed: true)
|
|
193
207
|
name = prefixed ? config.queue_name(queue_name) : queue_name
|
|
194
|
-
|
|
208
|
+
with_stale_connection_retry do
|
|
209
|
+
synchronized { @pgmq.archive_batch(name, msg_ids) }
|
|
210
|
+
end
|
|
195
211
|
end
|
|
196
212
|
|
|
197
213
|
# Batch delete — permanently removes multiple messages in one call.
|
|
198
214
|
def delete_batch(queue_name, msg_ids, prefixed: true)
|
|
199
215
|
name = prefixed ? config.queue_name(queue_name) : queue_name
|
|
200
|
-
|
|
216
|
+
with_stale_connection_retry do
|
|
217
|
+
synchronized { @pgmq.delete_batch(name, msg_ids) }
|
|
218
|
+
end
|
|
201
219
|
end
|
|
202
220
|
|
|
203
221
|
# Set visibility timeout. Pass prefixed: false when queue_name is already
|
|
204
222
|
# the full PGMQ queue name.
|
|
205
223
|
def set_visibility_timeout(queue_name, msg_id, vt:, prefixed: true)
|
|
206
224
|
name = prefixed ? config.queue_name(queue_name) : queue_name
|
|
207
|
-
|
|
225
|
+
with_stale_connection_retry do
|
|
226
|
+
synchronized { @pgmq.set_vt(name, msg_id, vt: vt) }
|
|
227
|
+
end
|
|
208
228
|
end
|
|
209
229
|
|
|
230
|
+
# Open a PGMQ transaction. The caller block may run twice if the first
|
|
231
|
+
# attempt hits a pre-flight stale-connection error — safe because no SQL
|
|
232
|
+
# was sent on the first attempt (the connection was dead before the BEGIN).
|
|
210
233
|
def transaction(&block)
|
|
211
|
-
|
|
234
|
+
with_stale_connection_retry do
|
|
235
|
+
synchronized { @pgmq.transaction(&block) }
|
|
236
|
+
end
|
|
212
237
|
end
|
|
213
238
|
|
|
214
239
|
def move_to_dead_letter(queue_name, message)
|
|
215
|
-
ensure_dead_letter_queue(queue_name)
|
|
216
240
|
dlq_name = config.dead_letter_queue_name(queue_name)
|
|
217
241
|
full_queue = config.queue_name(queue_name)
|
|
218
242
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
243
|
+
with_stale_connection_retry do
|
|
244
|
+
ensure_dead_letter_queue(queue_name)
|
|
245
|
+
synchronized do
|
|
246
|
+
@pgmq.transaction do |txn|
|
|
247
|
+
txn.produce(dlq_name, message.message, headers: message.headers)
|
|
248
|
+
txn.delete(full_queue, message.msg_id.to_i)
|
|
249
|
+
end
|
|
223
250
|
end
|
|
224
251
|
end
|
|
225
252
|
end
|
|
226
253
|
|
|
227
254
|
def metrics(queue_name = nil)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
255
|
+
with_stale_connection_retry do
|
|
256
|
+
synchronized do
|
|
257
|
+
if queue_name
|
|
258
|
+
@pgmq.metrics(config.queue_name(queue_name))
|
|
259
|
+
else
|
|
260
|
+
@pgmq.metrics_all
|
|
261
|
+
end
|
|
233
262
|
end
|
|
234
263
|
end
|
|
235
264
|
end
|
|
236
265
|
|
|
237
266
|
def list_queues
|
|
238
|
-
|
|
267
|
+
with_stale_connection_retry do
|
|
268
|
+
synchronized { @pgmq.list_queues }
|
|
269
|
+
end
|
|
239
270
|
end
|
|
240
271
|
|
|
241
272
|
def purge_queue(queue_name, prefixed: true)
|
|
242
273
|
name = prefixed ? config.queue_name(queue_name) : queue_name
|
|
243
|
-
|
|
274
|
+
with_stale_connection_retry do
|
|
275
|
+
synchronized { @pgmq.purge_queue(name) }
|
|
276
|
+
end
|
|
244
277
|
end
|
|
245
278
|
|
|
246
279
|
def drop_queue(queue_name, prefixed: true)
|
|
247
280
|
name = prefixed ? config.queue_name(queue_name) : queue_name
|
|
248
|
-
result =
|
|
281
|
+
result = with_stale_connection_retry do
|
|
282
|
+
synchronized { @pgmq.drop_queue(name) }
|
|
283
|
+
end
|
|
249
284
|
@queues_created.delete(name)
|
|
250
285
|
result
|
|
251
286
|
end
|
|
@@ -317,8 +352,10 @@ module Pgbus
|
|
|
317
352
|
# Topic routing
|
|
318
353
|
def bind_topic(pattern, queue_name)
|
|
319
354
|
full_name = config.queue_name(queue_name)
|
|
320
|
-
|
|
321
|
-
|
|
355
|
+
with_stale_connection_retry do
|
|
356
|
+
ensure_queue(queue_name)
|
|
357
|
+
synchronized { @pgmq.bind_topic(pattern, full_name) }
|
|
358
|
+
end
|
|
322
359
|
end
|
|
323
360
|
|
|
324
361
|
def publish_to_topic(routing_key, payload, headers: nil, delay: 0)
|
|
@@ -541,15 +578,24 @@ module Pgbus
|
|
|
541
578
|
"connection is closed",
|
|
542
579
|
"connection has been closed",
|
|
543
580
|
"connection not open",
|
|
544
|
-
"no connection to the server"
|
|
581
|
+
"no connection to the server",
|
|
582
|
+
"ssl error: unexpected eof",
|
|
583
|
+
"ssl syscall error"
|
|
545
584
|
].freeze
|
|
546
585
|
private_constant :STALE_CONNECTION_PATTERNS
|
|
547
586
|
|
|
548
|
-
#
|
|
549
|
-
#
|
|
550
|
-
#
|
|
551
|
-
#
|
|
552
|
-
#
|
|
587
|
+
# Rescue PGMQ::Errors::ConnectionError once if its message matches a
|
|
588
|
+
# known stale-socket pattern. pgmq-ruby's auto_reconnect + verify_connection!
|
|
589
|
+
# already recovers on the *next* checkout, so a single retry is sufficient.
|
|
590
|
+
# Other connection errors (pool timeout, misconfiguration, truly unreachable
|
|
591
|
+
# DB) propagate.
|
|
592
|
+
#
|
|
593
|
+
# Wraps every @pgmq.* call site. Pattern matching is intentionally narrow
|
|
594
|
+
# (pre-flight / idle-socket signals only), so retry is safe even for
|
|
595
|
+
# non-idempotent ops like delete/archive — a matched error means the
|
|
596
|
+
# connection was dead *before* pgmq-ruby tried to use it, so no SQL was
|
|
597
|
+
# ever sent. Mid-flight errors like "server closed the connection" are
|
|
598
|
+
# excluded from the pattern list for this reason.
|
|
553
599
|
def with_stale_connection_retry
|
|
554
600
|
attempts = 0
|
|
555
601
|
begin
|
|
@@ -559,7 +605,7 @@ module Pgbus
|
|
|
559
605
|
raise unless attempts == 1 && stale_connection_error?(e)
|
|
560
606
|
|
|
561
607
|
Pgbus.logger.warn do
|
|
562
|
-
"[Pgbus::Client] Retrying
|
|
608
|
+
"[Pgbus::Client] Retrying after stale pgmq connection: #{e.message}"
|
|
563
609
|
end
|
|
564
610
|
retry
|
|
565
611
|
end
|
data/lib/pgbus/version.rb
CHANGED