postburner 1.0.0.pre.11 → 1.0.0.pre.12
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/README.md +961 -555
- data/app/concerns/postburner/commands.rb +1 -1
- data/app/concerns/postburner/execution.rb +11 -11
- data/app/concerns/postburner/insertion.rb +1 -1
- data/app/concerns/postburner/logging.rb +2 -2
- data/app/concerns/postburner/statistics.rb +1 -1
- data/app/models/postburner/job.rb +27 -4
- data/app/models/postburner/mailer.rb +1 -1
- data/app/models/postburner/schedule.rb +703 -0
- data/app/models/postburner/schedule_execution.rb +353 -0
- data/app/views/postburner/jobs/show.html.haml +3 -3
- data/lib/generators/postburner/install/install_generator.rb +1 -0
- data/lib/generators/postburner/install/templates/config/postburner.yml +15 -6
- data/lib/generators/postburner/install/templates/migrations/create_postburner_schedules.rb.erb +71 -0
- data/lib/postburner/active_job/adapter.rb +3 -3
- data/lib/postburner/active_job/payload.rb +5 -0
- data/lib/postburner/advisory_lock.rb +123 -0
- data/lib/postburner/configuration.rb +43 -7
- data/lib/postburner/connection.rb +7 -6
- data/lib/postburner/runner.rb +26 -3
- data/lib/postburner/scheduler.rb +427 -0
- data/lib/postburner/strategies/immediate_test_queue.rb +24 -7
- data/lib/postburner/strategies/nice_queue.rb +1 -1
- data/lib/postburner/strategies/null_queue.rb +2 -2
- data/lib/postburner/strategies/test_queue.rb +2 -2
- data/lib/postburner/time_helpers.rb +4 -2
- data/lib/postburner/tube.rb +9 -1
- data/lib/postburner/version.rb +1 -1
- data/lib/postburner/worker.rb +684 -0
- data/lib/postburner.rb +32 -13
- metadata +7 -3
- data/lib/postburner/workers/base.rb +0 -205
- data/lib/postburner/workers/worker.rb +0 -396
data/lib/postburner.rb
CHANGED
|
@@ -7,8 +7,7 @@ require "postburner/tracked"
|
|
|
7
7
|
require "postburner/active_job/payload"
|
|
8
8
|
require "postburner/active_job/execution"
|
|
9
9
|
require "postburner/active_job/adapter"
|
|
10
|
-
require "postburner/
|
|
11
|
-
require "postburner/workers/worker"
|
|
10
|
+
require "postburner/worker"
|
|
12
11
|
require "postburner/runner"
|
|
13
12
|
require "postburner/engine"
|
|
14
13
|
require "postburner/strategies/queue"
|
|
@@ -17,7 +16,7 @@ require "postburner/strategies/test_queue"
|
|
|
17
16
|
require "postburner/strategies/immediate_test_queue"
|
|
18
17
|
require "postburner/strategies/null_queue"
|
|
19
18
|
|
|
20
|
-
# Postburner - PostgreSQL-backed job queue system built on
|
|
19
|
+
# Postburner - PostgreSQL-backed job queue system built on Beanstalkd.
|
|
21
20
|
#
|
|
22
21
|
# Postburner is a Ruby on Rails Engine that provides a database-backed job queue with
|
|
23
22
|
# full audit trails, inspection capabilities, and multiple execution strategies. Every
|
|
@@ -28,7 +27,7 @@ require "postburner/strategies/null_queue"
|
|
|
28
27
|
#
|
|
29
28
|
# - **Jobs:** Subclass {Postburner::Job} and implement `perform` method
|
|
30
29
|
# - **Queue Strategies:** Control how jobs are executed (async, inline, test modes)
|
|
31
|
-
# - **Beanstalkd Integration:** Production queuing via
|
|
30
|
+
# - **Beanstalkd Integration:** Production queuing via Beanstalkd
|
|
32
31
|
# - **Database Persistence:** Full audit trail with timestamps, logs, and errors
|
|
33
32
|
# - **Callbacks:** ActiveJob-style lifecycle hooks (enqueue, attempt, processing, processed)
|
|
34
33
|
#
|
|
@@ -385,7 +384,7 @@ module Postburner
|
|
|
385
384
|
# configuration. This prevents accidentally clearing tubes from other
|
|
386
385
|
# applications or environments sharing the same Beanstalkd server.
|
|
387
386
|
#
|
|
388
|
-
# @param tube_names [Array<String>, nil]
|
|
387
|
+
# @param tube_names [Array<String>, String, nil] Tube name(s) to clear, or nil to only show stats
|
|
389
388
|
# @param silent [Boolean] If true, suppress output to stdout (default: false)
|
|
390
389
|
#
|
|
391
390
|
# @return [Hash] Statistics and results (see Connection#clear_tubes!)
|
|
@@ -400,6 +399,10 @@ module Postburner
|
|
|
400
399
|
# Postburner.clear_jobs!(Postburner.watched_tube_names)
|
|
401
400
|
# # Only clears tubes defined in your config
|
|
402
401
|
#
|
|
402
|
+
# @example Clear scheduler tube (single string)
|
|
403
|
+
# Postburner.clear_jobs!(Postburner.scheduler_tube_name)
|
|
404
|
+
# # Clears the scheduler watchdog tube
|
|
405
|
+
#
|
|
403
406
|
# @example Trying to clear unconfigured tube - RAISES ERROR
|
|
404
407
|
# Postburner.clear_jobs!(['some-other-app-tube'])
|
|
405
408
|
# # => ArgumentError: Cannot clear tubes not in configuration
|
|
@@ -413,6 +416,7 @@ module Postburner
|
|
|
413
416
|
def self.clear_jobs!(tube_names = nil, silent: false)
|
|
414
417
|
require 'json'
|
|
415
418
|
|
|
419
|
+
tube_names = Array(tube_names) if tube_names
|
|
416
420
|
result = connection.clear_tubes!(tube_names)
|
|
417
421
|
|
|
418
422
|
unless silent
|
|
@@ -449,6 +453,21 @@ module Postburner
|
|
|
449
453
|
@__watched_tubes ||= watched_tube_names.map { |tube_name| connection.tubes[tube_name] }
|
|
450
454
|
end
|
|
451
455
|
|
|
456
|
+
# Returns the scheduler tube name with environment prefix.
|
|
457
|
+
#
|
|
458
|
+
# @return [String] Expanded scheduler tube name
|
|
459
|
+
#
|
|
460
|
+
# @example
|
|
461
|
+
# Postburner.scheduler_tube_name
|
|
462
|
+
# # => 'postburner.production.scheduler'
|
|
463
|
+
#
|
|
464
|
+
# @example Clear scheduler tube
|
|
465
|
+
# Postburner.clear_jobs!(Postburner.scheduler_tube_name)
|
|
466
|
+
#
|
|
467
|
+
def self.scheduler_tube_name
|
|
468
|
+
configuration.scheduler_tube_name
|
|
469
|
+
end
|
|
470
|
+
|
|
452
471
|
# Returns the Beanstalkd tube name prefix for the given environment.
|
|
453
472
|
#
|
|
454
473
|
# Delegates to {Configuration#tube_prefix}. Postburner automatically
|
|
@@ -519,14 +538,14 @@ module Postburner
|
|
|
519
538
|
|
|
520
539
|
tube_data = {
|
|
521
540
|
name: tube.name,
|
|
522
|
-
ready: stats_hash['
|
|
523
|
-
delayed: stats_hash['
|
|
524
|
-
buried: stats_hash['
|
|
525
|
-
reserved: stats_hash['
|
|
526
|
-
total: (stats_hash['
|
|
527
|
-
(stats_hash['
|
|
528
|
-
(stats_hash['
|
|
529
|
-
(stats_hash['
|
|
541
|
+
ready: stats_hash['current_jobs_ready'] || 0,
|
|
542
|
+
delayed: stats_hash['current_jobs_delayed'] || 0,
|
|
543
|
+
buried: stats_hash['current_jobs_buried'] || 0,
|
|
544
|
+
reserved: stats_hash['current_jobs_reserved'] || 0,
|
|
545
|
+
total: (stats_hash['current_jobs_ready'] || 0) +
|
|
546
|
+
(stats_hash['current_jobs_delayed'] || 0) +
|
|
547
|
+
(stats_hash['current_jobs_buried'] || 0) +
|
|
548
|
+
(stats_hash['current_jobs_reserved'] || 0)
|
|
530
549
|
}
|
|
531
550
|
rescue Beaneater::NotFoundError
|
|
532
551
|
# Tube doesn't exist yet, skip it
|
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.pre.
|
|
4
|
+
version: 1.0.0.pre.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matt Smith
|
|
@@ -124,6 +124,8 @@ files:
|
|
|
124
124
|
- app/models/postburner/application_record.rb
|
|
125
125
|
- app/models/postburner/job.rb
|
|
126
126
|
- app/models/postburner/mailer.rb
|
|
127
|
+
- app/models/postburner/schedule.rb
|
|
128
|
+
- app/models/postburner/schedule_execution.rb
|
|
127
129
|
- app/models/postburner/tracked_job.rb
|
|
128
130
|
- app/views/layouts/postburner/application.html.haml
|
|
129
131
|
- app/views/postburner/jobs/index.html.haml
|
|
@@ -138,15 +140,18 @@ files:
|
|
|
138
140
|
- lib/generators/postburner/install/install_generator.rb
|
|
139
141
|
- lib/generators/postburner/install/templates/config/postburner.yml
|
|
140
142
|
- lib/generators/postburner/install/templates/migrations/create_postburner_jobs.rb.erb
|
|
143
|
+
- lib/generators/postburner/install/templates/migrations/create_postburner_schedules.rb.erb
|
|
141
144
|
- lib/postburner.rb
|
|
142
145
|
- lib/postburner/active_job/adapter.rb
|
|
143
146
|
- lib/postburner/active_job/execution.rb
|
|
144
147
|
- lib/postburner/active_job/payload.rb
|
|
148
|
+
- lib/postburner/advisory_lock.rb
|
|
145
149
|
- lib/postburner/beanstalkd.rb
|
|
146
150
|
- lib/postburner/configuration.rb
|
|
147
151
|
- lib/postburner/connection.rb
|
|
148
152
|
- lib/postburner/engine.rb
|
|
149
153
|
- lib/postburner/runner.rb
|
|
154
|
+
- lib/postburner/scheduler.rb
|
|
150
155
|
- lib/postburner/strategies/immediate_test_queue.rb
|
|
151
156
|
- lib/postburner/strategies/nice_queue.rb
|
|
152
157
|
- lib/postburner/strategies/null_queue.rb
|
|
@@ -156,8 +161,7 @@ files:
|
|
|
156
161
|
- lib/postburner/tracked.rb
|
|
157
162
|
- lib/postburner/tube.rb
|
|
158
163
|
- lib/postburner/version.rb
|
|
159
|
-
- lib/postburner/
|
|
160
|
-
- lib/postburner/workers/worker.rb
|
|
164
|
+
- lib/postburner/worker.rb
|
|
161
165
|
- lib/tasks/postburner.rake
|
|
162
166
|
- lib/tasks/postburner_tasks.rake
|
|
163
167
|
homepage: https://gitlab.nearapogee.com/opensource/postburner
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Postburner
|
|
4
|
-
module Workers
|
|
5
|
-
# Base worker class with shared functionality for all worker types.
|
|
6
|
-
#
|
|
7
|
-
# Provides common methods for signal handling, job execution, error handling,
|
|
8
|
-
# and retry logic. Subclasses implement the specific execution strategy
|
|
9
|
-
# (simple, forking, threads_on_fork).
|
|
10
|
-
#
|
|
11
|
-
class Base
|
|
12
|
-
attr_reader :config, :logger
|
|
13
|
-
|
|
14
|
-
# @param config [Postburner::Configuration] Worker configuration
|
|
15
|
-
#
|
|
16
|
-
def initialize(config)
|
|
17
|
-
@config = config
|
|
18
|
-
@logger = config.logger
|
|
19
|
-
@shutdown = false
|
|
20
|
-
setup_signal_handlers
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Starts the worker loop.
|
|
24
|
-
#
|
|
25
|
-
# Subclasses must implement this method to define their execution strategy.
|
|
26
|
-
#
|
|
27
|
-
# @return [void]
|
|
28
|
-
#
|
|
29
|
-
# @raise [NotImplementedError] if not implemented by subclass
|
|
30
|
-
#
|
|
31
|
-
def start
|
|
32
|
-
raise NotImplementedError, "Subclasses must implement #start"
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Initiates graceful shutdown.
|
|
36
|
-
#
|
|
37
|
-
# Sets shutdown flag to stop processing new jobs. Current jobs
|
|
38
|
-
# are allowed to finish.
|
|
39
|
-
#
|
|
40
|
-
# @return [void]
|
|
41
|
-
#
|
|
42
|
-
def shutdown
|
|
43
|
-
@shutdown = true
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Checks if shutdown has been requested.
|
|
47
|
-
#
|
|
48
|
-
# @return [Boolean] true if shutdown requested, false otherwise
|
|
49
|
-
#
|
|
50
|
-
def shutdown?
|
|
51
|
-
@shutdown
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
protected
|
|
55
|
-
|
|
56
|
-
# Sets up signal handlers for graceful shutdown.
|
|
57
|
-
#
|
|
58
|
-
# TERM and INT signals trigger graceful shutdown.
|
|
59
|
-
#
|
|
60
|
-
# @return [void]
|
|
61
|
-
#
|
|
62
|
-
def setup_signal_handlers
|
|
63
|
-
Signal.trap('TERM') { shutdown }
|
|
64
|
-
Signal.trap('INT') { shutdown }
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Expands queue name to full tube name with environment prefix.
|
|
68
|
-
#
|
|
69
|
-
# Delegates to Postburner::Configuration#expand_tube_name.
|
|
70
|
-
#
|
|
71
|
-
# @param queue_name [String] Base queue name
|
|
72
|
-
#
|
|
73
|
-
# @return [String] Full tube name (e.g., 'postburner.production.critical')
|
|
74
|
-
#
|
|
75
|
-
def expand_tube_name(queue_name)
|
|
76
|
-
config.expand_tube_name(queue_name)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Executes a job from Beanstalkd.
|
|
80
|
-
#
|
|
81
|
-
# Delegates to Postburner::ActiveJob::Execution to handle default,
|
|
82
|
-
# tracked, and legacy job formats.
|
|
83
|
-
#
|
|
84
|
-
# @param beanstalk_job [Beaneater::Job] Job from Beanstalkd
|
|
85
|
-
#
|
|
86
|
-
# @return [void]
|
|
87
|
-
#
|
|
88
|
-
def execute_job(beanstalk_job)
|
|
89
|
-
logger.info "[Postburner] Executing #{beanstalk_job.class.name} #{beanstalk_job.id}"
|
|
90
|
-
Postburner::ActiveJob::Execution.execute(beanstalk_job.body)
|
|
91
|
-
logger.info "[Postburner] Deleting #{beanstalk_job.class.name} #{beanstalk_job.id} (success)"
|
|
92
|
-
beanstalk_job.delete
|
|
93
|
-
rescue => e
|
|
94
|
-
handle_error(beanstalk_job, e)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Handles job execution errors with retry logic.
|
|
98
|
-
#
|
|
99
|
-
# Implements retry strategy:
|
|
100
|
-
# - Parses payload to determine job type
|
|
101
|
-
# - For default jobs: manages retry count, re-queues with backoff
|
|
102
|
-
# - For tracked jobs: buries job (Postburner::Job handles retries)
|
|
103
|
-
# - For legacy jobs: buries job
|
|
104
|
-
#
|
|
105
|
-
# @param beanstalk_job [Beaneater::Job] Job from Beanstalkd
|
|
106
|
-
# @param error [Exception] The exception that was raised
|
|
107
|
-
#
|
|
108
|
-
# @return [void]
|
|
109
|
-
#
|
|
110
|
-
def handle_error(beanstalk_job, error)
|
|
111
|
-
logger.error "[Postburner] Job failed: #{error.class} - #{error.message}"
|
|
112
|
-
logger.error error.backtrace.join("\n")
|
|
113
|
-
|
|
114
|
-
begin
|
|
115
|
-
payload = JSON.parse(beanstalk_job.body)
|
|
116
|
-
|
|
117
|
-
if payload['tracked'] || Postburner::ActiveJob::Payload.legacy_format?(payload)
|
|
118
|
-
# Tracked and legacy jobs: bury for inspection
|
|
119
|
-
# (Postburner::Job has its own retry logic)
|
|
120
|
-
logger.info "[Postburner] Burying tracked/legacy job for inspection"
|
|
121
|
-
beanstalk_job.bury
|
|
122
|
-
else
|
|
123
|
-
# Default job: handle retry logic
|
|
124
|
-
handle_default_retry(beanstalk_job, payload, error)
|
|
125
|
-
end
|
|
126
|
-
rescue => retry_error
|
|
127
|
-
logger.error "[Postburner] Error handling failure: #{retry_error.message}"
|
|
128
|
-
beanstalk_job.bury rescue nil
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# Handles retry logic for default jobs.
|
|
133
|
-
#
|
|
134
|
-
# Checks ActiveJob's retry_on configuration, increments retry count,
|
|
135
|
-
# and re-queues with exponential backoff if retries remaining.
|
|
136
|
-
#
|
|
137
|
-
# @param beanstalk_job [Beaneater::Job] Job from Beanstalkd
|
|
138
|
-
# @param payload [Hash] Parsed job payload
|
|
139
|
-
# @param error [Exception] The exception that was raised
|
|
140
|
-
#
|
|
141
|
-
# @return [void]
|
|
142
|
-
#
|
|
143
|
-
def handle_default_retry(beanstalk_job, payload, error)
|
|
144
|
-
retry_count = payload['retry_count'] || 0
|
|
145
|
-
job_class = payload['job_class'].constantize
|
|
146
|
-
|
|
147
|
-
# Check if job class wants to retry this error
|
|
148
|
-
# (This is simplified - full implementation would check retry_on config)
|
|
149
|
-
max_retries = 5 # Default max retries
|
|
150
|
-
|
|
151
|
-
if retry_count < max_retries
|
|
152
|
-
# Increment retry count
|
|
153
|
-
payload['retry_count'] = retry_count + 1
|
|
154
|
-
payload['executions'] = (payload['executions'] || 0) + 1
|
|
155
|
-
|
|
156
|
-
# Calculate backoff delay (exponential: 1s, 2s, 4s, 8s, 16s...)
|
|
157
|
-
delay = calculate_backoff(retry_count)
|
|
158
|
-
|
|
159
|
-
# Delete old job and insert new one with updated payload
|
|
160
|
-
beanstalk_job.delete
|
|
161
|
-
|
|
162
|
-
Postburner.connected do |conn|
|
|
163
|
-
tube_name = expand_tube_name(payload['queue_name'])
|
|
164
|
-
conn.tubes[tube_name].put(
|
|
165
|
-
JSON.generate(payload),
|
|
166
|
-
pri: payload['priority'] || 0,
|
|
167
|
-
delay: delay,
|
|
168
|
-
ttr: 120
|
|
169
|
-
)
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
logger.info "[Postburner] Retrying default job #{payload['job_id']}, attempt #{retry_count + 1} in #{delay}s"
|
|
173
|
-
else
|
|
174
|
-
# Max retries exceeded
|
|
175
|
-
logger.error "[Postburner] Discarding default job #{payload['job_id']} after #{retry_count} retries"
|
|
176
|
-
beanstalk_job.delete
|
|
177
|
-
# TODO: Call after_discard callback if configured
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# Calculates exponential backoff delay for retries.
|
|
182
|
-
#
|
|
183
|
-
# @param retry_count [Integer] Number of retries so far
|
|
184
|
-
#
|
|
185
|
-
# @return [Integer] Delay in seconds (capped at 1 hour)
|
|
186
|
-
#
|
|
187
|
-
def calculate_backoff(retry_count)
|
|
188
|
-
# Exponential backoff: 2^retry_count, capped at 3600 seconds (1 hour)
|
|
189
|
-
[2 ** retry_count, 3600].min
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
# Watches all configured queues in Beanstalkd.
|
|
193
|
-
#
|
|
194
|
-
# @param connection [Postburner::Connection] Beanstalkd connection
|
|
195
|
-
#
|
|
196
|
-
# @return [void]
|
|
197
|
-
#
|
|
198
|
-
def watch_queues(connection, queue_names=nil)
|
|
199
|
-
tube_names = queue_names.map { |q| config.expand_tube_name(q) }
|
|
200
|
-
tube_names = config.expanded_tube_names if tube_names.empty?
|
|
201
|
-
connection.beanstalk.tubes.watch!(*tube_names)
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
end
|