postburner 1.0.0.rc.6 → 1.0.0.rc.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b48c13a76dd9b1b6e54f825bbc50d95574c0decd4a57b8b6ba3d3fc8c5ea369c
4
- data.tar.gz: 5a867eef5d36cce234b2874b5c18ff95d0ddb648fd1576e71c0a8c5946ddc12c
3
+ metadata.gz: 41b3c95f8abde17bb19b725dd0c39ce65b97a53e73afb77c7237e2e51f37d7e9
4
+ data.tar.gz: 7e5de5424a027d59c9552624bd555ae38da4f30823f3ffc28ef86601c96301e0
5
5
  SHA512:
6
- metadata.gz: 82e60e8af9d983550d7a07dac46b1c5611eb11245944e2ddac5a7411cf2d0b63a59697cab1a83965379bb92c274c1abd33a1a475498f981cbcb8589d520ac61c
7
- data.tar.gz: 430829a786da22122f8ce4a47e8e966feb524877539f7a0e4086db912731235064c6888d0662d67c438e0c2e2d590a2a4496e33a56658b3022f70fbbb58ca050
6
+ metadata.gz: d2f90b3d590adfc16247f285f8ea27cef03d9dd72af5671bc92117d2418442dcf6f331c68d5380a2917687ea2353524f972fdd7edeca4fe9a8e1b6b965dff9ef
7
+ data.tar.gz: 06accd7ecd5c01aa83532e7f8cd69e2aca53459e7b8f8f8c0a290163555a2f24f47df8ecb4407e39d7401680547b7f016b9111221688e5dc10c2fea43eb49ba6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.0.0.rc.7 - 2026-08-03
4
+
5
+ ### Fixed
6
+
7
+ - **Delayed jobs can release before their `run_at`.** Beanstalkd delays are whole seconds, so a fractional delay has to round. Insertions are now computed as `(at - now).ceil`.
8
+ - **Premature re-insert could spin.** `NiceQueue`/`DefaultQueue` re-inserted a premature job with a fractional second value. Re-insert delay is now `[early.ceil, 1].max`.
9
+
10
+ ### Changed
11
+
12
+ - Premature-job messages now report how early the job arrived: the `PREMATURE; RE-INSERTED` audit log entry and the `Postburner::Job::PrematurePerform` message both include the delta in seconds, which makes sub-second scheduling drift visible instead of silent.
13
+
3
14
  ## v1.0.0.rc.6 - 2026-06-27
4
15
 
5
16
  ### Highlights
@@ -85,7 +85,15 @@ module Postburner
85
85
  self.run_at = case
86
86
  when at.present?
87
87
  # this is rudimentary, add error handling
88
- options[:delay] ||= at.to_i - now.to_i
88
+ #
89
+ # Ceil, don't floor. Beanstalkd delays are whole seconds,
90
+ # so some rounding is unavoidable -- but it must round
91
+ # *away* from now. Flooring both sides (`at.to_i -
92
+ # now.to_i`) drops frac(at) and frac(now) independently,
93
+ # releasing the job early by frac(at) - frac(now)
94
+ # whenever that is positive. Consumers then see a job
95
+ # arrive before its own run_at.
96
+ options[:delay] ||= [(at.to_time - now).ceil, 0].max
89
97
  at
90
98
  when options[:delay].present?
91
99
  now + options[:delay].seconds
@@ -33,12 +33,12 @@ module Postburner
33
33
  # application code.
34
34
  #
35
35
  # @example Typical lifecycle
36
- # # A DB row exists with type: 'Flex::TriggerNoShowDeliveriesJob' (class deleted)
36
+ # # A DB row exists with type: 'MyApp::RemovedJob' (class deleted)
37
37
  # job = Postburner::Job.find(42)
38
38
  # job.class # => Postburner::OrphanedJob
39
- # job.type # => 'Flex::TriggerNoShowDeliveriesJob'
39
+ # job.type # => 'MyApp::RemovedJob'
40
40
  # job.orphaned? # => true
41
- # job.missing_class_name # => 'Flex::TriggerNoShowDeliveriesJob'
41
+ # job.missing_class_name # => 'MyApp::RemovedJob'
42
42
  # job.perform # => raises Postburner::Job::OrphanedJobError
43
43
  # job.remove! # soft-deletes fine despite readonly?
44
44
  #
@@ -895,7 +895,7 @@ module Postburner
895
895
 
896
896
  # Calculate next time for month-based intervals.
897
897
  #
898
- # Handles variable month lengths like Flex subscription billing.
898
+ # Handles variable month lengths, as monthly billing cycles require.
899
899
  # Always snaps to the anchor's day-of-month (or end of month if shorter).
900
900
  #
901
901
  # Uses Rails Duration for month arithmetic with day adjustment.
@@ -944,7 +944,7 @@ module Postburner
944
944
  next_time = anchor_time + (n * interval).months
945
945
 
946
946
  # Adjust day to match anchor's day (or end of month if shorter)
947
- # This is the Flex pattern for "snap back" behavior
947
+ # "Snap back" behavior: a short month never permanently shifts the day
948
948
  if next_time.end_of_month.day >= requested_day
949
949
  next_time.change(day: requested_day)
950
950
  else
@@ -276,9 +276,8 @@ module Postburner
276
276
 
277
277
  # Validate that the next execution matches the cached next_run_at.
278
278
  #
279
- # Similar to Flex::SubscriptionIteration validation. Ensures that the
280
- # pre-calculated next_run_at in this execution matches the actual run_at
281
- # of the next execution created.
279
+ # Ensures that the pre-calculated next_run_at in this execution matches
280
+ # the actual run_at of the next execution created.
282
281
  #
283
282
  # This validation helps detect calculation bugs or manual tampering with
284
283
  # execution records.
@@ -407,7 +406,9 @@ module Postburner
407
406
  job.priority = cached_schedule['priority'] if cached_schedule['priority']
408
407
 
409
408
  # Enqueue with scheduled time (use instance method enqueue, not class method perform_later)
410
- delay = (run_at - Time.current).to_i
409
+ # ceil so the job is never released before run_at; see
410
+ # Postburner::Insertion#queue!
411
+ delay = (run_at - Time.current).ceil
411
412
  delay = 0 if delay < 0
412
413
 
413
414
  if delay > 0
@@ -170,7 +170,9 @@ module ActiveJob
170
170
  )
171
171
 
172
172
  # Calculate delay for Beanstalkd
173
- delay = timestamp ? [timestamp.to_i - Time.current.to_i, 0].max : 0
173
+ # ceil so the job is never released before its timestamp; see
174
+ # Postburner::Insertion#queue!
175
+ delay = timestamp ? [(Time.zone.at(timestamp) - Time.current).ceil, 0].max : 0
174
176
 
175
177
  # Queue to Beanstalkd with minimal payload
176
178
  bkid = nil
@@ -232,7 +234,9 @@ module ActiveJob
232
234
  # @return [void]
233
235
  #
234
236
  def enqueue_default(job, timestamp)
235
- delay = timestamp ? [timestamp.to_i - Time.current.to_i, 0].max : 0
237
+ # ceil so the job is never released before its timestamp; see
238
+ # Postburner::Insertion#queue!
239
+ delay = timestamp ? [(Time.zone.at(timestamp) - Time.current).ceil, 0].max : 0
236
240
  bkid = nil
237
241
 
238
242
  Postburner.connected do |conn|
@@ -77,8 +77,13 @@ module Postburner
77
77
  # @note Logs "PREMATURE; RE-INSERTED" message to job's audit trail
78
78
  #
79
79
  def handle_premature_perform(job)
80
- response = job.insert! delay: job.run_at - Time.current
81
- job.log! "PREMATURE; RE-INSERTED: #{response}"
80
+ early = job.run_at - Time.current
81
+ # Never re-insert with a delay under a second. A fractional delay
82
+ # truncates to 0, so the job comes straight back, is judged premature
83
+ # again, and burns reserve/delete/put plus a DB write per pass until
84
+ # the clock catches up.
85
+ response = job.insert! delay: [early.ceil, 1].max
86
+ job.log! "PREMATURE (#{early.round(3)}s early); RE-INSERTED: #{response}"
82
87
  end
83
88
  end
84
89
  end
@@ -81,7 +81,10 @@ module Postburner
81
81
 
82
82
  # Get priority, TTR from job instance (respects instance overrides) or options
83
83
  pri = options[:pri] || job.priority || Postburner.configuration.default_priority
84
- delay = options[:delay].present? ? [0, options[:delay].to_i].max : 0
84
+ # ceil, not to_i: callers may pass a Float delay (see
85
+ # DefaultQueue#handle_premature_perform), and truncating it would
86
+ # re-introduce the early release this guards against.
87
+ delay = options[:delay].present? ? [0, options[:delay].to_f.ceil].max : 0
85
88
  ttr = options[:ttr] || job.ttr || Postburner.configuration.default_ttr
86
89
  payload = JSON.generate(data)
87
90
 
@@ -119,7 +122,9 @@ module Postburner
119
122
  # @raise [Postburner::Job::PrematurePerform] Always raises for this strategy
120
123
  #
121
124
  def handle_premature_perform(job)
122
- raise Postburner::Job::PrematurePerform, "Job has future run_at: #{job.run_at}"
125
+ early = job.run_at - Time.current
126
+ raise Postburner::Job::PrematurePerform,
127
+ "Job has future run_at: #{job.run_at} (#{early.round(3)}s early)"
123
128
  end
124
129
  end
125
130
  end
@@ -1,3 +1,3 @@
1
1
  module Postburner
2
- VERSION = '1.0.0.rc.6'
2
+ VERSION = '1.0.0.rc.7'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postburner
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc.6
4
+ version: 1.0.0.rc.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Smith