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 +4 -4
- data/CHANGELOG.md +11 -0
- data/app/concerns/postburner/insertion.rb +9 -1
- data/app/models/postburner/orphaned_job.rb +3 -3
- data/app/models/postburner/schedule.rb +2 -2
- data/app/models/postburner/schedule_execution.rb +5 -4
- data/lib/postburner/active_job/adapter.rb +6 -2
- data/lib/postburner/strategies/default_queue.rb +7 -2
- data/lib/postburner/strategies/strict_queue.rb +7 -2
- data/lib/postburner/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: 41b3c95f8abde17bb19b725dd0c39ce65b97a53e73afb77c7237e2e51f37d7e9
|
|
4
|
+
data.tar.gz: 7e5de5424a027d59c9552624bd555ae38da4f30823f3ffc28ef86601c96301e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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: '
|
|
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 # => '
|
|
39
|
+
# job.type # => 'MyApp::RemovedJob'
|
|
40
40
|
# job.orphaned? # => true
|
|
41
|
-
# job.missing_class_name # => '
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
280
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/postburner/version.rb
CHANGED