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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fdfe0b3f854e7ecea3419e6c10efe9e527be73952c8d570fb46106e0476255ec
4
- data.tar.gz: 1c63029ec5a6e61fb91c7add6377735f6a8b740d0a416826333d400f493a623a
3
+ metadata.gz: 209245b1743578b21530e31feb670940f068cc911725fff1b2a59ee6d8335e10
4
+ data.tar.gz: '059d0fde8d4fbc57db9e4aa408d0dd31f71b1c8065d5041258a8a502017ce7ab'
5
5
  SHA512:
6
- metadata.gz: 53d20a66c7f796f36befa7777c8e62cf494ff659ef0e9d8aa7f5b7cb078c2abef4c3fc4fe99bc61df797681212d3a91bdd73bf67e6be16a4cd4da15cf91c3f02
7
- data.tar.gz: 93a184a4b081ad26a81162ac08d61b4ab6051dd1ea90097dfce1dbd177610d2154dc741eb77a0ca31a3d68eb3813dfea1f99801a92550e568450cd3e9a9a2869
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" }, "$inc" => { attempts: 1 } },
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
- { "$set" => { status: "pending" } }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.13.42"
4
+ VERSION = "3.13.43"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.42
4
+ version: 3.13.43
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team