postburner 0.9.0.rc.1 → 1.0.0.pre.2
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 +1082 -507
- data/app/models/postburner/job.rb +163 -39
- data/app/models/postburner/tracked_job.rb +83 -0
- data/bin/postburner +91 -0
- data/bin/rails +14 -0
- data/config/environment.rb +3 -0
- data/config/postburner.yml +22 -0
- data/config/postburner.yml.example +142 -0
- data/lib/generators/postburner/install/install_generator.rb +10 -0
- data/lib/generators/postburner/install/templates/config/postburner.yml +142 -0
- data/lib/postburner/active_job/adapter.rb +176 -0
- data/lib/postburner/active_job/execution.rb +109 -0
- data/lib/postburner/active_job/payload.rb +157 -0
- data/lib/postburner/beanstalkd.rb +97 -0
- data/lib/postburner/configuration.rb +202 -0
- data/lib/postburner/connection.rb +113 -0
- data/lib/postburner/engine.rb +1 -1
- data/lib/postburner/queue_config.rb +151 -0
- data/lib/postburner/strategies/queue.rb +20 -6
- data/lib/postburner/tracked.rb +171 -0
- data/lib/postburner/version.rb +1 -1
- data/lib/postburner/workers/base.rb +210 -0
- data/lib/postburner/workers/worker.rb +480 -0
- data/lib/postburner.rb +78 -7
- metadata +44 -11
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Postburner
|
|
4
|
+
# Configuration system for Postburner workers and connections.
|
|
5
|
+
#
|
|
6
|
+
# Supports both programmatic configuration and YAML file loading.
|
|
7
|
+
# Configuration can be set per-environment and controls worker behavior,
|
|
8
|
+
# Beanstalkd connection, and execution strategies.
|
|
9
|
+
#
|
|
10
|
+
# @example Programmatic configuration
|
|
11
|
+
# Postburner.configure do |config|
|
|
12
|
+
# config.beanstalk_url = 'beanstalk://localhost:11300'
|
|
13
|
+
# config.worker_type = :threads_on_fork
|
|
14
|
+
# config.logger = Rails.logger
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# @example Loading from YAML
|
|
18
|
+
# config = Postburner::Configuration.load_yaml('config/postburner.yml', 'production')
|
|
19
|
+
#
|
|
20
|
+
class Configuration
|
|
21
|
+
attr_accessor :beanstalk_url, :logger, :queues, :default_queue, :default_priority, :default_ttr, :default_threads, :default_forks, :default_gc_limit
|
|
22
|
+
|
|
23
|
+
# @param options [Hash] Configuration options
|
|
24
|
+
# @option options [String] :beanstalk_url Beanstalkd URL (default: ENV['BEANSTALK_URL'] or localhost)
|
|
25
|
+
# @option options [Logger] :logger Logger instance (default: Rails.logger)
|
|
26
|
+
# @option options [Hash] :queues Queue configurations
|
|
27
|
+
# @option options [String] :default_queue Default queue name (default: 'default')
|
|
28
|
+
# @option options [Integer] :default_priority Default job priority (default: 65536, lower = higher priority)
|
|
29
|
+
# @option options [Integer] :default_ttr Default time-to-run in seconds (default: 300)
|
|
30
|
+
# @option options [Integer] :default_threads Default thread count per queue (default: 1)
|
|
31
|
+
# @option options [Integer] :default_forks Default fork count per queue (default: 0, single process)
|
|
32
|
+
# @option options [Integer] :default_gc_limit Default GC limit for worker restarts (default: nil, no limit)
|
|
33
|
+
#
|
|
34
|
+
def initialize(options = {})
|
|
35
|
+
@beanstalk_url = options[:beanstalk_url] || ENV['BEANSTALK_URL'] || 'beanstalk://localhost:11300'
|
|
36
|
+
@logger = options[:logger] || (defined?(Rails) ? Rails.logger : Logger.new(STDOUT))
|
|
37
|
+
@queues = options[:queues] || { 'default' => {} }
|
|
38
|
+
@default_queue = options[:default_queue] || 'default'
|
|
39
|
+
@default_priority = options[:default_priority] || 65536
|
|
40
|
+
@default_ttr = options[:default_ttr] || 300
|
|
41
|
+
@default_threads = options[:default_threads] || 1
|
|
42
|
+
@default_forks = options[:default_forks] || 0
|
|
43
|
+
@default_gc_limit = options[:default_gc_limit]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Loads configuration from a YAML file.
|
|
47
|
+
#
|
|
48
|
+
# @param path [String] Path to YAML configuration file
|
|
49
|
+
# @param env [String] Environment name (e.g., 'production', 'development')
|
|
50
|
+
# @param worker_name [String, nil] Worker name to load (nil = auto-select if single worker)
|
|
51
|
+
#
|
|
52
|
+
# @return [Configuration] Configured instance
|
|
53
|
+
#
|
|
54
|
+
# @raise [Errno::ENOENT] if file doesn't exist
|
|
55
|
+
# @raise [ArgumentError] if environment not found in YAML
|
|
56
|
+
# @raise [ArgumentError] if multiple workers defined but worker_name not specified
|
|
57
|
+
# @raise [ArgumentError] if worker_name specified but not found
|
|
58
|
+
#
|
|
59
|
+
# @example Single worker (auto-selected)
|
|
60
|
+
# config = Postburner::Configuration.load_yaml('config/postburner.yml', 'production')
|
|
61
|
+
#
|
|
62
|
+
# @example Multiple workers (must specify)
|
|
63
|
+
# config = Postburner::Configuration.load_yaml('config/postburner.yml', 'production', 'imports')
|
|
64
|
+
#
|
|
65
|
+
def self.load_yaml(path, env = 'development', worker_name = nil)
|
|
66
|
+
yaml = YAML.load_file(path, aliases: true)
|
|
67
|
+
env_config = yaml[env.to_s] || yaml[env.to_sym]
|
|
68
|
+
|
|
69
|
+
raise ArgumentError, "Environment '#{env}' not found in #{path}" unless env_config
|
|
70
|
+
|
|
71
|
+
workers = env_config['workers']
|
|
72
|
+
raise ArgumentError, "No 'workers:' section found in #{path} for environment '#{env}'" unless workers
|
|
73
|
+
|
|
74
|
+
# Auto-select single worker or validate worker_name
|
|
75
|
+
if worker_name.nil?
|
|
76
|
+
if workers.size == 1
|
|
77
|
+
worker_name = workers.keys.first
|
|
78
|
+
else
|
|
79
|
+
raise ArgumentError, "Configuration has multiple workers, but --worker not specified\nAvailable workers: #{workers.keys.join(', ')}\nUsage: bin/postburner --worker <name>"
|
|
80
|
+
end
|
|
81
|
+
else
|
|
82
|
+
unless workers.key?(worker_name)
|
|
83
|
+
raise ArgumentError, "Worker '#{worker_name}' not found in #{path}\nAvailable workers: #{workers.keys.join(', ')}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
worker_config = workers[worker_name]
|
|
88
|
+
|
|
89
|
+
# Convert queue array to hash format expected by rest of system
|
|
90
|
+
queue_list = worker_config['queues'] || []
|
|
91
|
+
queues_hash = {}
|
|
92
|
+
queue_list.each do |queue_name|
|
|
93
|
+
queues_hash[queue_name] = {}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
options = {
|
|
97
|
+
beanstalk_url: env_config['beanstalk_url'],
|
|
98
|
+
queues: queues_hash,
|
|
99
|
+
default_queue: worker_config['default_queue'] || env_config['default_queue'],
|
|
100
|
+
default_priority: worker_config['default_priority'] || env_config['default_priority'],
|
|
101
|
+
default_ttr: worker_config['default_ttr'] || env_config['default_ttr'],
|
|
102
|
+
default_threads: worker_config['default_threads'] || env_config['default_threads'],
|
|
103
|
+
default_forks: worker_config['default_forks'] || env_config['default_forks'],
|
|
104
|
+
default_gc_limit: worker_config['default_gc_limit'] || env_config['default_gc_limit']
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
new(options)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Returns queue configuration for a specific queue name.
|
|
111
|
+
#
|
|
112
|
+
# @param queue_name [String, Symbol] Name of the queue
|
|
113
|
+
#
|
|
114
|
+
# @return [Hash] Queue configuration with threads, gc_limit, etc.
|
|
115
|
+
#
|
|
116
|
+
# @example
|
|
117
|
+
# config.queue_config('critical') # => { threads: 1, gc_limit: 100 }
|
|
118
|
+
#
|
|
119
|
+
def queue_config(queue_name)
|
|
120
|
+
@queues[queue_name.to_s] || @queues[queue_name.to_sym] || {}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Returns array of all configured queue names.
|
|
124
|
+
#
|
|
125
|
+
# @return [Array<String>] Queue names
|
|
126
|
+
#
|
|
127
|
+
# @example
|
|
128
|
+
# config.queue_names # => ['default', 'critical', 'mailers']
|
|
129
|
+
#
|
|
130
|
+
def queue_names
|
|
131
|
+
@queues.keys.map(&:to_s)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Expands queue name to full tube name with environment prefix.
|
|
135
|
+
#
|
|
136
|
+
# Converts a simple queue name (e.g., 'default', 'critical') to the full
|
|
137
|
+
# Beanstalkd tube name with environment namespace (e.g.,
|
|
138
|
+
# 'postburner.production.critical').
|
|
139
|
+
#
|
|
140
|
+
# @param queue_name [String, Symbol, nil] Base queue name (defaults to configured default_queue)
|
|
141
|
+
# @param env [String, Symbol, nil] Environment name (defaults to Rails.env or 'development')
|
|
142
|
+
#
|
|
143
|
+
# @return [String] Full tube name with environment prefix
|
|
144
|
+
#
|
|
145
|
+
# @example
|
|
146
|
+
# config.expand_tube_name('critical', 'production')
|
|
147
|
+
# # => "postburner.production.critical"
|
|
148
|
+
#
|
|
149
|
+
# @example With configured default
|
|
150
|
+
# config.default_queue = 'background'
|
|
151
|
+
# config.expand_tube_name
|
|
152
|
+
# # => "postburner.development.background"
|
|
153
|
+
#
|
|
154
|
+
def expand_tube_name(queue_name = nil, env = nil)
|
|
155
|
+
env ||= defined?(Rails) ? Rails.env : nil
|
|
156
|
+
queue_name ||= @default_queue
|
|
157
|
+
[
|
|
158
|
+
'postburner',
|
|
159
|
+
env,
|
|
160
|
+
queue_name,
|
|
161
|
+
].compact.join('.')
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# Returns array of expanded tube names with environment prefix.
|
|
166
|
+
#
|
|
167
|
+
# @param env [String, Symbol, nil] Environment name (defaults to Rails.env or 'development')
|
|
168
|
+
#
|
|
169
|
+
# @return [Array<String>] Array of expanded tube names
|
|
170
|
+
#
|
|
171
|
+
# @example
|
|
172
|
+
# config.expanded_tube_names('production') # => ['postburner.production.default', 'postburner.production.critical']
|
|
173
|
+
def expanded_tube_names(env = nil)
|
|
174
|
+
queue_names.map { |q| expand_tube_name(q, env) }
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Returns the global configuration instance.
|
|
179
|
+
#
|
|
180
|
+
# @return [Configuration] Global configuration
|
|
181
|
+
#
|
|
182
|
+
def self.configuration
|
|
183
|
+
@configuration ||= Configuration.new
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Configures Postburner via block.
|
|
187
|
+
#
|
|
188
|
+
# @yield [config] Configuration instance
|
|
189
|
+
# @yieldparam config [Configuration] The configuration to modify
|
|
190
|
+
#
|
|
191
|
+
# @return [void]
|
|
192
|
+
#
|
|
193
|
+
# @example
|
|
194
|
+
# Postburner.configure do |config|
|
|
195
|
+
# config.beanstalk_url = 'beanstalk://localhost:11300'
|
|
196
|
+
# config.worker_type = :threads_on_fork
|
|
197
|
+
# end
|
|
198
|
+
#
|
|
199
|
+
def self.configure
|
|
200
|
+
yield(configuration)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Postburner
|
|
4
|
+
# Wrapper around Beaneater connection with automatic reconnection.
|
|
5
|
+
#
|
|
6
|
+
# Provides a simplified interface to Beanstalkd via Beaneater, with
|
|
7
|
+
# automatic connection management and reconnection on failures.
|
|
8
|
+
#
|
|
9
|
+
# @example Direct usage
|
|
10
|
+
# conn = Postburner::Connection.new
|
|
11
|
+
# conn.tubes['default'].put('job data')
|
|
12
|
+
#
|
|
13
|
+
# @example With reconnection
|
|
14
|
+
# conn = Postburner::Connection.new
|
|
15
|
+
# conn.reconnect! # Force fresh connection
|
|
16
|
+
#
|
|
17
|
+
class Connection
|
|
18
|
+
attr_reader :url
|
|
19
|
+
|
|
20
|
+
# @param url [String] Beanstalkd URL (e.g., 'beanstalk://localhost:11300')
|
|
21
|
+
#
|
|
22
|
+
def initialize(url = nil)
|
|
23
|
+
@url = url || Postburner.configuration.beanstalk_url
|
|
24
|
+
@pool = nil
|
|
25
|
+
connect!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the tubes interface for Beanstalkd operations.
|
|
29
|
+
#
|
|
30
|
+
# Automatically ensures connection is active before returning.
|
|
31
|
+
#
|
|
32
|
+
# @return [Beaneater::Tubes] Tubes interface
|
|
33
|
+
#
|
|
34
|
+
# @raise [Beaneater::NotConnected] if connection fails
|
|
35
|
+
#
|
|
36
|
+
# @example
|
|
37
|
+
# conn.tubes['critical'].put('{"job": "data"}')
|
|
38
|
+
# conn.tubes.watch!('default', 'critical')
|
|
39
|
+
#
|
|
40
|
+
def tubes
|
|
41
|
+
ensure_connected!
|
|
42
|
+
@pool.tubes
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns the underlying Beaneater pool.
|
|
46
|
+
#
|
|
47
|
+
# @return [Beaneater::Pool] Beaneater connection pool
|
|
48
|
+
#
|
|
49
|
+
# @example
|
|
50
|
+
# conn.beanstalk.stats
|
|
51
|
+
#
|
|
52
|
+
def beanstalk
|
|
53
|
+
ensure_connected!
|
|
54
|
+
@pool
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Checks if currently connected to Beanstalkd.
|
|
58
|
+
#
|
|
59
|
+
# @return [Boolean] true if connected, false otherwise
|
|
60
|
+
#
|
|
61
|
+
def connected?
|
|
62
|
+
@pool && @pool.respond_to?(:connection) && @pool.connection
|
|
63
|
+
rescue
|
|
64
|
+
false
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Forces reconnection to Beanstalkd.
|
|
68
|
+
#
|
|
69
|
+
# Closes existing connection (if any) and establishes a fresh one.
|
|
70
|
+
# Use this when connection has gone stale or after network issues.
|
|
71
|
+
#
|
|
72
|
+
# @return [void]
|
|
73
|
+
#
|
|
74
|
+
# @example
|
|
75
|
+
# conn.reconnect!
|
|
76
|
+
#
|
|
77
|
+
def reconnect!
|
|
78
|
+
close
|
|
79
|
+
connect!
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Closes the Beanstalkd connection.
|
|
83
|
+
#
|
|
84
|
+
# @return [void]
|
|
85
|
+
#
|
|
86
|
+
def close
|
|
87
|
+
@pool&.close rescue nil
|
|
88
|
+
@pool = nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
# Establishes connection to Beanstalkd.
|
|
94
|
+
#
|
|
95
|
+
# @return [void]
|
|
96
|
+
#
|
|
97
|
+
# @raise [Beaneater::NotConnected] if connection fails
|
|
98
|
+
#
|
|
99
|
+
def connect!
|
|
100
|
+
# Beaneater expects 'host:port' format, not 'beanstalk://host:port'
|
|
101
|
+
clean_url = @url.sub(%r{^beanstalk://}, '')
|
|
102
|
+
@pool = Beaneater.new(clean_url)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Ensures connection is active, reconnecting if necessary.
|
|
106
|
+
#
|
|
107
|
+
# @return [void]
|
|
108
|
+
#
|
|
109
|
+
def ensure_connected!
|
|
110
|
+
reconnect! unless connected?
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
data/lib/postburner/engine.rb
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Postburner
|
|
4
|
+
# Queue configuration module for Postburner::Job classes.
|
|
5
|
+
#
|
|
6
|
+
# Provides DSL methods for configuring queue behavior (name, priority, TTR, retries).
|
|
7
|
+
# Replaces Backburner::Queue with cleaner implementation that doesn't interfere
|
|
8
|
+
# with ActiveSupport::Callbacks.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic usage
|
|
11
|
+
# class ProcessPayment < Postburner::Job
|
|
12
|
+
# queue 'critical'
|
|
13
|
+
# queue_priority 0
|
|
14
|
+
# queue_ttr 300
|
|
15
|
+
# queue_max_job_retries 3
|
|
16
|
+
#
|
|
17
|
+
# def perform(args)
|
|
18
|
+
# # ...
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
module QueueConfig
|
|
23
|
+
extend ActiveSupport::Concern
|
|
24
|
+
|
|
25
|
+
included do
|
|
26
|
+
class_attribute :postburner_queue_name, default: 'default'
|
|
27
|
+
class_attribute :postburner_priority, default: nil
|
|
28
|
+
class_attribute :postburner_ttr, default: nil
|
|
29
|
+
class_attribute :postburner_max_retries, default: nil
|
|
30
|
+
class_attribute :postburner_retry_delay, default: nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class_methods do
|
|
34
|
+
# Sets or returns the queue name.
|
|
35
|
+
#
|
|
36
|
+
# @param name [String, Symbol, nil] Queue name to set, or nil to get current value
|
|
37
|
+
#
|
|
38
|
+
# @return [String, nil] Current queue name when getting, nil when setting
|
|
39
|
+
#
|
|
40
|
+
# @example Set queue
|
|
41
|
+
# queue 'critical'
|
|
42
|
+
#
|
|
43
|
+
# @example Get queue
|
|
44
|
+
# ProcessPayment.queue # => 'critical'
|
|
45
|
+
#
|
|
46
|
+
def queue(name = nil)
|
|
47
|
+
if name
|
|
48
|
+
self.postburner_queue_name = name.to_s
|
|
49
|
+
nil # Return nil to avoid callback interference
|
|
50
|
+
else
|
|
51
|
+
postburner_queue_name
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Sets or returns the queue priority.
|
|
56
|
+
#
|
|
57
|
+
# Lower numbers = higher priority in Beanstalkd.
|
|
58
|
+
#
|
|
59
|
+
# @param pri [Integer, nil] Priority to set (0-4294967295), or nil to get current value
|
|
60
|
+
#
|
|
61
|
+
# @return [Integer, nil] Current priority when getting, nil when setting
|
|
62
|
+
#
|
|
63
|
+
# @example Set priority
|
|
64
|
+
# queue_priority 0 # Highest priority
|
|
65
|
+
#
|
|
66
|
+
# @example Get priority
|
|
67
|
+
# ProcessPayment.queue_priority # => 0
|
|
68
|
+
#
|
|
69
|
+
def queue_priority(pri = nil)
|
|
70
|
+
if pri
|
|
71
|
+
self.postburner_priority = pri
|
|
72
|
+
nil
|
|
73
|
+
else
|
|
74
|
+
postburner_priority
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Sets or returns the queue TTR (time-to-run).
|
|
79
|
+
#
|
|
80
|
+
# Number of seconds Beanstalkd will wait for job completion before
|
|
81
|
+
# making it available again.
|
|
82
|
+
#
|
|
83
|
+
# @param ttr [Integer, nil] Timeout in seconds, or nil to get current value
|
|
84
|
+
#
|
|
85
|
+
# @return [Integer, nil] Current TTR when getting, nil when setting
|
|
86
|
+
#
|
|
87
|
+
# @example Set TTR
|
|
88
|
+
# queue_ttr 300 # 5 minutes
|
|
89
|
+
#
|
|
90
|
+
# @example Get TTR
|
|
91
|
+
# ProcessPayment.queue_ttr # => 300
|
|
92
|
+
#
|
|
93
|
+
def queue_ttr(ttr = nil)
|
|
94
|
+
if ttr
|
|
95
|
+
self.postburner_ttr = ttr
|
|
96
|
+
nil
|
|
97
|
+
else
|
|
98
|
+
postburner_ttr
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Sets or returns maximum number of job retries.
|
|
103
|
+
#
|
|
104
|
+
# @param retries [Integer, nil] Max retries, or nil to get current value
|
|
105
|
+
#
|
|
106
|
+
# @return [Integer, nil] Current max retries when getting, nil when setting
|
|
107
|
+
#
|
|
108
|
+
# @example Set max retries
|
|
109
|
+
# queue_max_job_retries 3
|
|
110
|
+
#
|
|
111
|
+
# @example Get max retries
|
|
112
|
+
# ProcessPayment.queue_max_job_retries # => 3
|
|
113
|
+
#
|
|
114
|
+
def queue_max_job_retries(retries = nil)
|
|
115
|
+
if retries
|
|
116
|
+
self.postburner_max_retries = retries
|
|
117
|
+
nil
|
|
118
|
+
else
|
|
119
|
+
postburner_max_retries
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Sets or returns the retry delay.
|
|
124
|
+
#
|
|
125
|
+
# Can accept either a fixed delay (Integer) or a proc for dynamic calculation.
|
|
126
|
+
# The proc receives the retry count and returns delay in seconds.
|
|
127
|
+
#
|
|
128
|
+
# @param delay [Integer, Proc, nil] Delay in seconds, proc, or nil to get current value
|
|
129
|
+
#
|
|
130
|
+
# @return [Integer, Proc, nil] Current delay when getting, nil when setting
|
|
131
|
+
#
|
|
132
|
+
# @example Set fixed retry delay
|
|
133
|
+
# queue_retry_delay 10
|
|
134
|
+
#
|
|
135
|
+
# @example Set exponential backoff with proc
|
|
136
|
+
# queue_retry_delay ->(retries) { 2 ** retries }
|
|
137
|
+
#
|
|
138
|
+
# @example Get retry delay
|
|
139
|
+
# ProcessPayment.queue_retry_delay # => 10 or #<Proc>
|
|
140
|
+
#
|
|
141
|
+
def queue_retry_delay(delay = nil)
|
|
142
|
+
if delay
|
|
143
|
+
self.postburner_retry_delay = delay
|
|
144
|
+
nil
|
|
145
|
+
else
|
|
146
|
+
postburner_retry_delay
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -66,19 +66,33 @@ module Postburner
|
|
|
66
66
|
# @option options [Integer] :pri Priority (lower = higher priority)
|
|
67
67
|
# @option options [Integer] :ttr Time-to-run before job times out
|
|
68
68
|
#
|
|
69
|
-
# @return [Hash]
|
|
69
|
+
# @return [Hash] Response with :id (beanstalkd job id) and :status
|
|
70
70
|
#
|
|
71
71
|
# @raise [Beaneater::NotConnected] if Beanstalkd connection fails
|
|
72
72
|
#
|
|
73
73
|
# @api private
|
|
74
74
|
#
|
|
75
75
|
def insert(job, options = {})
|
|
76
|
+
#debugger
|
|
76
77
|
Postburner::Job.transaction do
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
job.id
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
Postburner.connected do |conn|
|
|
79
|
+
tube_name = job.tube_name
|
|
80
|
+
data = { class: job.class.name, args: [job.id] }
|
|
81
|
+
|
|
82
|
+
# Get priority, TTR from job instance (respects instance overrides) or options
|
|
83
|
+
pri = options[:pri] || job.queue_priority || Postburner.configuration.default_priority
|
|
84
|
+
delay = options[:delay] || 0
|
|
85
|
+
ttr = options[:ttr] || job.queue_ttr || Postburner.configuration.default_ttr
|
|
86
|
+
|
|
87
|
+
response = conn.tubes[tube_name].put(
|
|
88
|
+
JSON.generate(data),
|
|
89
|
+
pri: pri,
|
|
90
|
+
delay: delay,
|
|
91
|
+
ttr: ttr
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
response
|
|
95
|
+
end
|
|
82
96
|
end
|
|
83
97
|
end
|
|
84
98
|
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Postburner
|
|
4
|
+
# Concern for ActiveJob classes to opt-in to PostgreSQL tracking.
|
|
5
|
+
#
|
|
6
|
+
# Simply include this module in your ActiveJob class to enable full audit
|
|
7
|
+
# trail persistence in PostgreSQL. Without this, jobs execute as "default" jobs
|
|
8
|
+
# (Beanstalkd only, no PostgreSQL overhead).
|
|
9
|
+
#
|
|
10
|
+
# @example Opt-in to tracking
|
|
11
|
+
# class ProcessPayment < ApplicationJob
|
|
12
|
+
# include Postburner::Tracked # ← Enables PostgreSQL audit trail
|
|
13
|
+
#
|
|
14
|
+
# def perform(payment_id)
|
|
15
|
+
# log "Processing payment #{payment_id}"
|
|
16
|
+
# # ... work ...
|
|
17
|
+
# log! "Payment processed successfully"
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# @example Without tracking (default mode)
|
|
22
|
+
# class SendEmail < ApplicationJob
|
|
23
|
+
# # No Postburner::Tracked - executes as default job
|
|
24
|
+
#
|
|
25
|
+
# def perform(email)
|
|
26
|
+
# # Fast execution, no PostgreSQL overhead
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
module Tracked
|
|
31
|
+
extend ActiveSupport::Concern
|
|
32
|
+
|
|
33
|
+
included do
|
|
34
|
+
# Include Beanstalkd configuration DSL automatically
|
|
35
|
+
include Postburner::Beanstalkd
|
|
36
|
+
|
|
37
|
+
# Reference to Postburner::Job during execution (set by worker)
|
|
38
|
+
attr_accessor :postburner_job
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Appends a log message to the job's audit trail.
|
|
42
|
+
#
|
|
43
|
+
# Only available for tracked jobs. Logs are stored in the
|
|
44
|
+
# Postburner::Job record's `logs` JSONB array.
|
|
45
|
+
#
|
|
46
|
+
# @param message [String] Log message
|
|
47
|
+
# @param level [Symbol] Log level (:debug, :info, :warning, :error)
|
|
48
|
+
#
|
|
49
|
+
# @return [void]
|
|
50
|
+
#
|
|
51
|
+
# @example
|
|
52
|
+
# def perform(user_id)
|
|
53
|
+
# log "Starting user processing"
|
|
54
|
+
# # ... work ...
|
|
55
|
+
# log "Completed successfully", level: :info
|
|
56
|
+
# end
|
|
57
|
+
#
|
|
58
|
+
def log(message, level: :info)
|
|
59
|
+
postburner_job&.log(message, level: level)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Appends a log message and immediately persists to database.
|
|
63
|
+
#
|
|
64
|
+
# Use this for important log messages that should be saved immediately
|
|
65
|
+
# rather than batched with other updates.
|
|
66
|
+
#
|
|
67
|
+
# @param message [String] Log message
|
|
68
|
+
# @param level [Symbol] Log level (:debug, :info, :warning, :error)
|
|
69
|
+
#
|
|
70
|
+
# @return [void]
|
|
71
|
+
#
|
|
72
|
+
# @example
|
|
73
|
+
# def perform(payment_id)
|
|
74
|
+
# log! "Critical: Processing high-value payment #{payment_id}"
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
def log!(message, level: :info)
|
|
78
|
+
postburner_job&.log!(message, level: level)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Tracks an exception in the job's errata array.
|
|
82
|
+
#
|
|
83
|
+
# Appends exception details (class, message, backtrace) to the
|
|
84
|
+
# in-memory errata array. Does NOT persist immediately.
|
|
85
|
+
#
|
|
86
|
+
# @param exception [Exception] The exception to track
|
|
87
|
+
#
|
|
88
|
+
# @return [void]
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# def perform(user_id)
|
|
92
|
+
# process_user(user_id)
|
|
93
|
+
# rescue => e
|
|
94
|
+
# log_exception(e)
|
|
95
|
+
# raise
|
|
96
|
+
# end
|
|
97
|
+
#
|
|
98
|
+
def log_exception(exception)
|
|
99
|
+
postburner_job&.log_exception(exception)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Tracks an exception and immediately persists to database.
|
|
103
|
+
#
|
|
104
|
+
# @param exception [Exception] The exception to track
|
|
105
|
+
#
|
|
106
|
+
# @return [void]
|
|
107
|
+
#
|
|
108
|
+
# @example
|
|
109
|
+
# def perform(user_id)
|
|
110
|
+
# process_user(user_id)
|
|
111
|
+
# rescue CriticalError => e
|
|
112
|
+
# log_exception!(e)
|
|
113
|
+
# raise
|
|
114
|
+
# end
|
|
115
|
+
#
|
|
116
|
+
def log_exception!(exception)
|
|
117
|
+
postburner_job&.log_exception!(exception)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Returns the Beanstalkd job object for direct queue operations.
|
|
121
|
+
#
|
|
122
|
+
# Provides access to the underlying Beaneater job object through the
|
|
123
|
+
# TrackedJob wrapper. Use this to perform Beanstalkd operations like
|
|
124
|
+
# touch, bury, release, etc.
|
|
125
|
+
#
|
|
126
|
+
# @return [Beaneater::Job, nil] Beanstalkd job object or nil if not available
|
|
127
|
+
#
|
|
128
|
+
# @example Extend TTR during long operation
|
|
129
|
+
# def perform(file_id)
|
|
130
|
+
# file = File.find(file_id)
|
|
131
|
+
# file.each_line do |line|
|
|
132
|
+
# # ... process line ...
|
|
133
|
+
# bk&.touch # Extend TTR
|
|
134
|
+
# end
|
|
135
|
+
# end
|
|
136
|
+
#
|
|
137
|
+
# @example Other Beanstalkd operations
|
|
138
|
+
# bk&.bury # Bury the job
|
|
139
|
+
# bk&.release(pri: 0) # Release with priority
|
|
140
|
+
# bk&.stats # Get job statistics
|
|
141
|
+
#
|
|
142
|
+
# @see #extend!
|
|
143
|
+
#
|
|
144
|
+
def bk
|
|
145
|
+
postburner_job&.bk
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Extends the job's time-to-run (TTR) in Beanstalkd.
|
|
149
|
+
#
|
|
150
|
+
# Convenience method that calls touch on the Beanstalkd job, extending
|
|
151
|
+
# the TTR by the original TTR value. Use this during long-running operations
|
|
152
|
+
# to prevent the job from timing out.
|
|
153
|
+
#
|
|
154
|
+
# @return [void]
|
|
155
|
+
#
|
|
156
|
+
# @example Process large file line by line
|
|
157
|
+
# def perform(file_id)
|
|
158
|
+
# file = File.find(file_id)
|
|
159
|
+
# file.each_line do |line|
|
|
160
|
+
# # ... process line ...
|
|
161
|
+
# extend! # Extend TTR to prevent timeout
|
|
162
|
+
# end
|
|
163
|
+
# end
|
|
164
|
+
#
|
|
165
|
+
# @see #bk
|
|
166
|
+
#
|
|
167
|
+
def extend!
|
|
168
|
+
postburner_job&.extend!
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
data/lib/postburner/version.rb
CHANGED