tina4ruby 3.13.42 → 3.13.43
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/tina4/queue.rb +4 -0
- data/lib/tina4/queue_backends/mongo_backend.rb +85 -3
- data/lib/tina4/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: 209245b1743578b21530e31feb670940f068cc911725fff1b2a59ee6d8335e10
|
|
4
|
+
data.tar.gz: '059d0fde8d4fbc57db9e4aa408d0dd31f71b1c8065d5041258a8a502017ce7ab'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 30babc583e40444ec46069088e1af2a94f0485546b585530ccda22cca2a8ba29fafca3464bd08845fd1d9f88e92342533f2c58a82b89f38a517fce607096f905
|
|
7
|
+
data.tar.gz: 84960f5d44cb50d7ec4d8dd31a6645e10640526aaf6c9f777e81c7376a43290a6027d33d903f45a317f369834eee59518ec9d215d05e8933c820e5d3020ae49e
|
data/lib/tina4/queue.rb
CHANGED
|
@@ -305,6 +305,10 @@ module Tina4
|
|
|
305
305
|
config = resolve_mongo_config
|
|
306
306
|
config[:visibility_timeout] = vt
|
|
307
307
|
config[:max_retries] = max_retries
|
|
308
|
+
# Thread retry_backoff through so a failed/retried job's available_at is
|
|
309
|
+
# reset to now (or now + retry_backoff) instead of being stranded for the
|
|
310
|
+
# full visibility window (Bug B).
|
|
311
|
+
config[:retry_backoff] = retry_backoff
|
|
308
312
|
Tina4::QueueBackends::MongoBackend.new(config)
|
|
309
313
|
else
|
|
310
314
|
raise ArgumentError, "Unknown queue backend: #{chosen.inspect}. Use 'lite', 'rabbitmq', 'kafka', or 'mongodb'."
|
|
@@ -5,11 +5,16 @@ module Tina4
|
|
|
5
5
|
class MongoBackend
|
|
6
6
|
# Reservation/visibility + retry policy (settable so a Queue can propagate
|
|
7
7
|
# its own onto a backend instance passed directly — legacy path).
|
|
8
|
-
attr_accessor :visibility_timeout, :max_retries
|
|
8
|
+
attr_accessor :visibility_timeout, :max_retries, :retry_backoff
|
|
9
9
|
|
|
10
10
|
def initialize(options = {})
|
|
11
11
|
require "mongo"
|
|
12
12
|
@max_retries = options[:max_retries] || 3
|
|
13
|
+
# Seconds to delay a requeued (failed/retried) job before it is eligible
|
|
14
|
+
# again. Default 0 = available on the very next dequeue, so a fail()'d job
|
|
15
|
+
# retries immediately (matching the lite backend) instead of waiting out
|
|
16
|
+
# the visibility window.
|
|
17
|
+
@retry_backoff = (options[:retry_backoff] || 0).to_f
|
|
13
18
|
|
|
14
19
|
uri = options[:uri] || ENV["TINA4_MONGO_URI"]
|
|
15
20
|
host = options[:host] || ENV.fetch("TINA4_MONGO_HOST", "localhost")
|
|
@@ -134,10 +139,58 @@ module Tina4
|
|
|
134
139
|
collection.delete_one(_id: message.id)
|
|
135
140
|
end
|
|
136
141
|
|
|
142
|
+
# Terminal success — the job is done and removed (mirrors the lite
|
|
143
|
+
# backend's complete()). Without this, job.complete() was a no-op on
|
|
144
|
+
# MongoDB and the document stayed "processing" forever.
|
|
145
|
+
def complete(message)
|
|
146
|
+
collection.delete_one(_id: message.id)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Record a failed attempt (mirrors the lite backend + the Python Mongo
|
|
150
|
+
# adapter). Increments attempts; while attempts < max_retries the job is
|
|
151
|
+
# re-queued to pending (visible again immediately, or after retry_backoff),
|
|
152
|
+
# otherwise it is dead-lettered. The Queue/Job lifecycle expects fail() to
|
|
153
|
+
# route the requeue here — previously MongoBackend had no fail(), so
|
|
154
|
+
# job.fail() degraded to in-memory bookkeeping and never touched Mongo.
|
|
155
|
+
def fail(job, error = "")
|
|
156
|
+
job.attempts += 1
|
|
157
|
+
if job.attempts >= @max_retries
|
|
158
|
+
collection.find_one_and_update(
|
|
159
|
+
{ _id: job.id },
|
|
160
|
+
{ "$set" => { status: "dead", topic: "#{job.topic}.dead_letter",
|
|
161
|
+
error: error, reserved_at: nil } },
|
|
162
|
+
upsert: true
|
|
163
|
+
)
|
|
164
|
+
else
|
|
165
|
+
requeue_with_error(job, error)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Explicit re-queue requested by the caller (job.retry()). Always
|
|
170
|
+
# re-enqueues regardless of the retry limit — a manual override, distinct
|
|
171
|
+
# from the automatic fail() path. Increments attempts, clears the error.
|
|
172
|
+
def retry(job, delay_seconds: 0)
|
|
173
|
+
job.attempts += 1
|
|
174
|
+
backoff = delay_seconds.to_f > 0 ? delay_seconds.to_f : @retry_backoff
|
|
175
|
+
collection.find_one_and_update(
|
|
176
|
+
{ _id: job.id },
|
|
177
|
+
{ "$set" => { status: "pending", error: nil, reserved_at: nil,
|
|
178
|
+
available_at: requeue_available_at(backoff) } },
|
|
179
|
+
upsert: true
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
137
183
|
def requeue(message)
|
|
184
|
+
# Reset available_at so the requeued job is visible again right away (or
|
|
185
|
+
# after retry_backoff) and clear reserved_at. dequeue() pushed
|
|
186
|
+
# available_at out to the reservation expiry; leaving it there stranded a
|
|
187
|
+
# requeued job for the full visibility window instead of retrying it on
|
|
188
|
+
# the next pop().
|
|
138
189
|
collection.find_one_and_update(
|
|
139
190
|
{ _id: message.id },
|
|
140
|
-
{ "$set" => { status: "pending"
|
|
191
|
+
{ "$set" => { status: "pending", reserved_at: nil,
|
|
192
|
+
available_at: requeue_available_at(@retry_backoff) },
|
|
193
|
+
"$inc" => { attempts: 1 } },
|
|
141
194
|
upsert: true
|
|
142
195
|
)
|
|
143
196
|
end
|
|
@@ -172,7 +225,11 @@ module Tina4
|
|
|
172
225
|
def retry_failed(topic, max_retries: 3)
|
|
173
226
|
result = collection.update_many(
|
|
174
227
|
{ topic: topic, status: "failed", attempts: { "$lt" => max_retries } },
|
|
175
|
-
|
|
228
|
+
# Reset available_at so re-queued failed jobs are visible again — they
|
|
229
|
+
# were reserved with available_at in the future at dequeue. Clear
|
|
230
|
+
# reserved_at too. (Same Bug B reason as requeue/fail.)
|
|
231
|
+
{ "$set" => { status: "pending", error: nil, reserved_at: nil,
|
|
232
|
+
available_at: requeue_available_at(@retry_backoff) } }
|
|
176
233
|
)
|
|
177
234
|
result.modified_count
|
|
178
235
|
end
|
|
@@ -183,6 +240,31 @@ module Tina4
|
|
|
183
240
|
|
|
184
241
|
private
|
|
185
242
|
|
|
243
|
+
# Re-queue a failed job to pending, carrying the failure reason and
|
|
244
|
+
# resetting available_at (now, or now + retry_backoff) + clearing
|
|
245
|
+
# reserved_at so the next dequeue picks it up.
|
|
246
|
+
def requeue_with_error(job, error)
|
|
247
|
+
collection.find_one_and_update(
|
|
248
|
+
{ _id: job.id },
|
|
249
|
+
# Persist the incremented attempt count (fail() already did job.attempts
|
|
250
|
+
# += 1). Without this the doc's attempts stayed at its old value, so the
|
|
251
|
+
# next dequeue surfaced a stale count, fail()'s attempts >= max_retries
|
|
252
|
+
# check never tripped, and the job was re-queued forever instead of
|
|
253
|
+
# dead-lettering.
|
|
254
|
+
{ "$set" => { status: "pending", error: error, reserved_at: nil,
|
|
255
|
+
attempts: job.attempts,
|
|
256
|
+
available_at: requeue_available_at(@retry_backoff) } },
|
|
257
|
+
upsert: true
|
|
258
|
+
)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# available_at for a re-queued job: now (immediately retryable) or
|
|
262
|
+
# now + backoff when a backoff is configured.
|
|
263
|
+
def requeue_available_at(backoff)
|
|
264
|
+
b = (backoff || 0).to_f
|
|
265
|
+
b > 0 ? (Time.now.utc + b) : Time.now.utc
|
|
266
|
+
end
|
|
267
|
+
|
|
186
268
|
def collection
|
|
187
269
|
@db[@collection_name]
|
|
188
270
|
end
|
data/lib/tina4/version.rb
CHANGED