postburner 0.8.0 → 1.0.0.pre.1
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 +0 -22
- data/README.md +1219 -238
- data/Rakefile +1 -1
- data/app/concerns/postburner/callbacks.rb +286 -0
- data/app/models/postburner/job.rb +735 -46
- 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/postburner/active_job/adapter.rb +163 -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/immediate_test_queue.rb +133 -0
- data/lib/postburner/strategies/nice_queue.rb +85 -0
- data/lib/postburner/strategies/null_queue.rb +132 -0
- data/lib/postburner/strategies/queue.rb +119 -0
- data/lib/postburner/strategies/test_queue.rb +128 -0
- data/lib/postburner/time_helpers.rb +75 -0
- 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 +433 -4
- metadata +66 -17
- data/lib/postburner/tube.rb +0 -53
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Postburner
|
|
4
|
+
module ActiveJob
|
|
5
|
+
# Handles serialization/deserialization of ActiveJob payloads for Beanstalkd.
|
|
6
|
+
#
|
|
7
|
+
# Provides consistent payload format for both default and tracked jobs, with
|
|
8
|
+
# versioning support for future format changes.
|
|
9
|
+
#
|
|
10
|
+
# ## Payload Format (V1)
|
|
11
|
+
#
|
|
12
|
+
# Both default and tracked jobs store the same ActiveJob data in Beanstalkd.
|
|
13
|
+
# The only difference is tracked jobs also persist to PostgreSQL and include
|
|
14
|
+
# a `postburner_job_id` reference.
|
|
15
|
+
#
|
|
16
|
+
# @example Default job payload
|
|
17
|
+
# {
|
|
18
|
+
# v: 1,
|
|
19
|
+
# tracked: false,
|
|
20
|
+
# postburner_job_id: nil,
|
|
21
|
+
# job_class: "SendEmail",
|
|
22
|
+
# job_id: "abc-123",
|
|
23
|
+
# queue_name: "mailers",
|
|
24
|
+
# arguments: [[1, 2, 3]],
|
|
25
|
+
# retry_count: 0,
|
|
26
|
+
# ...
|
|
27
|
+
# }
|
|
28
|
+
#
|
|
29
|
+
# @example Tracked job payload
|
|
30
|
+
# {
|
|
31
|
+
# v: 1,
|
|
32
|
+
# tracked: true,
|
|
33
|
+
# postburner_job_id: 456, # References postburner_jobs.id
|
|
34
|
+
# job_class: "ProcessPayment",
|
|
35
|
+
# job_id: "def-456",
|
|
36
|
+
# queue_name: "critical",
|
|
37
|
+
# arguments: [[789]],
|
|
38
|
+
# retry_count: 0,
|
|
39
|
+
# ...
|
|
40
|
+
# }
|
|
41
|
+
#
|
|
42
|
+
class Payload
|
|
43
|
+
VERSION = 1
|
|
44
|
+
|
|
45
|
+
class << self
|
|
46
|
+
# Generates payload structure for an ActiveJob.
|
|
47
|
+
#
|
|
48
|
+
# @param job [ActiveJob::Base] The ActiveJob instance
|
|
49
|
+
# @param tracked [Boolean] Whether this is a tracked job
|
|
50
|
+
# @param postburner_job_id [Integer, nil] Postburner::Job ID for tracked jobs
|
|
51
|
+
#
|
|
52
|
+
# @return [Hash] Payload hash
|
|
53
|
+
#
|
|
54
|
+
# @api private
|
|
55
|
+
#
|
|
56
|
+
def for_job(job, tracked: false, postburner_job_id: nil)
|
|
57
|
+
{
|
|
58
|
+
v: VERSION,
|
|
59
|
+
tracked: tracked,
|
|
60
|
+
postburner_job_id: postburner_job_id,
|
|
61
|
+
job_class: job.class.name,
|
|
62
|
+
job_id: job.job_id,
|
|
63
|
+
queue_name: job.queue_name,
|
|
64
|
+
priority: job.priority,
|
|
65
|
+
arguments: ::ActiveJob::Arguments.serialize(job.arguments),
|
|
66
|
+
executions: job.executions,
|
|
67
|
+
exception_executions: job.exception_executions || {},
|
|
68
|
+
locale: job.locale,
|
|
69
|
+
timezone: job.timezone,
|
|
70
|
+
enqueued_at: job.enqueued_at&.iso8601,
|
|
71
|
+
retry_count: 0 # For default job retry tracking
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Generates JSON payload for a default job.
|
|
76
|
+
#
|
|
77
|
+
# @param job [ActiveJob::Base] The ActiveJob instance
|
|
78
|
+
#
|
|
79
|
+
# @return [String] JSON-encoded payload
|
|
80
|
+
#
|
|
81
|
+
def default_payload(job)
|
|
82
|
+
JSON.generate(for_job(job, tracked: false))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Generates JSON payload for a tracked job.
|
|
86
|
+
#
|
|
87
|
+
# @param job [ActiveJob::Base] The ActiveJob instance
|
|
88
|
+
# @param postburner_job_id [Integer] Postburner::Job ID
|
|
89
|
+
#
|
|
90
|
+
# @return [String] JSON-encoded payload
|
|
91
|
+
#
|
|
92
|
+
def tracked_payload(job, postburner_job_id)
|
|
93
|
+
JSON.generate(for_job(job, tracked: true, postburner_job_id: postburner_job_id))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Serializes ActiveJob data for PostgreSQL storage (tracked jobs).
|
|
97
|
+
#
|
|
98
|
+
# Returns the same structure as for_job but as a plain Hash
|
|
99
|
+
# (not JSON string) for storage in Postburner::Job args column.
|
|
100
|
+
#
|
|
101
|
+
# @param job [ActiveJob::Base] The ActiveJob instance
|
|
102
|
+
#
|
|
103
|
+
# @return [Hash] Payload hash for PostgreSQL storage
|
|
104
|
+
#
|
|
105
|
+
def serialize_for_tracked(job)
|
|
106
|
+
for_job(job, tracked: true).stringify_keys
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Parses and validates a JSON payload from Beanstalkd.
|
|
110
|
+
#
|
|
111
|
+
# Handles version checking and returns the parsed data.
|
|
112
|
+
#
|
|
113
|
+
# @param json_string [String] JSON payload from Beanstalkd
|
|
114
|
+
#
|
|
115
|
+
# @return [Hash] Parsed payload with string keys
|
|
116
|
+
#
|
|
117
|
+
# @raise [ArgumentError] if payload version is unknown
|
|
118
|
+
#
|
|
119
|
+
def parse(json_string)
|
|
120
|
+
data = JSON.parse(json_string)
|
|
121
|
+
|
|
122
|
+
# Handle version
|
|
123
|
+
version = data['v'] || data[:v] || 1
|
|
124
|
+
|
|
125
|
+
case version
|
|
126
|
+
when 1
|
|
127
|
+
data.is_a?(Hash) ? data.stringify_keys : data
|
|
128
|
+
else
|
|
129
|
+
raise ArgumentError, "Unknown payload version: #{version}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Detects if a parsed payload is for a tracked job.
|
|
134
|
+
#
|
|
135
|
+
# @param payload [Hash] Parsed payload
|
|
136
|
+
#
|
|
137
|
+
# @return [Boolean] true if tracked, false otherwise
|
|
138
|
+
#
|
|
139
|
+
def tracked?(payload)
|
|
140
|
+
payload['tracked'] == true || payload[:tracked] == true
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Detects if a parsed payload is for a legacy Postburner::Job.
|
|
144
|
+
#
|
|
145
|
+
# Legacy format: { "class" => "JobClassName", "args" => [job_id] }
|
|
146
|
+
#
|
|
147
|
+
# @param payload [Hash] Parsed payload
|
|
148
|
+
#
|
|
149
|
+
# @return [Boolean] true if legacy format
|
|
150
|
+
#
|
|
151
|
+
def legacy_format?(payload)
|
|
152
|
+
payload.key?('class') && payload.key?('args') && !payload.key?('v')
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Postburner
|
|
4
|
+
# Beanstalkd-specific configuration DSL for ActiveJob classes using Postburner.
|
|
5
|
+
#
|
|
6
|
+
# Provides consistent API with Postburner::Job for setting Beanstalkd queue
|
|
7
|
+
# priority and TTR (time-to-run). Include this module in your ActiveJob classes
|
|
8
|
+
# to use Beanstalkd-specific configuration.
|
|
9
|
+
#
|
|
10
|
+
# @note Automatically included when you include Postburner::Tracked
|
|
11
|
+
#
|
|
12
|
+
# @example Default job with Beanstalkd config
|
|
13
|
+
# class ProcessPayment < ApplicationJob
|
|
14
|
+
# include Postburner::Beanstalkd
|
|
15
|
+
#
|
|
16
|
+
# queue_as :critical
|
|
17
|
+
# queue_priority 0 # Highest priority
|
|
18
|
+
# queue_ttr 300 # 5 minutes to complete
|
|
19
|
+
#
|
|
20
|
+
# def perform(payment_id)
|
|
21
|
+
# # ...
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# @example With tracking (Beanstalkd automatically included)
|
|
26
|
+
# class ProcessPayment < ApplicationJob
|
|
27
|
+
# include Postburner::Tracked # Includes Beanstalkd automatically
|
|
28
|
+
#
|
|
29
|
+
# queue_priority 0
|
|
30
|
+
# queue_ttr 600
|
|
31
|
+
#
|
|
32
|
+
# def perform(payment_id)
|
|
33
|
+
# log "Processing payment"
|
|
34
|
+
# # ...
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
module Beanstalkd
|
|
39
|
+
extend ActiveSupport::Concern
|
|
40
|
+
|
|
41
|
+
included do
|
|
42
|
+
class_attribute :postburner_priority, default: nil
|
|
43
|
+
class_attribute :postburner_ttr, default: nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class_methods do
|
|
47
|
+
# Sets or returns the queue priority.
|
|
48
|
+
#
|
|
49
|
+
# Lower numbers = higher priority in Beanstalkd (0 is highest).
|
|
50
|
+
# Falls back to Postburner.configuration.default_priority if not set.
|
|
51
|
+
#
|
|
52
|
+
# @param pri [Integer, nil] Priority to set (0-4294967295), or nil to get current value
|
|
53
|
+
#
|
|
54
|
+
# @return [Integer, nil] Current priority when getting, nil when setting
|
|
55
|
+
#
|
|
56
|
+
# @example Set priority
|
|
57
|
+
# queue_priority 0 # Highest priority
|
|
58
|
+
#
|
|
59
|
+
# @example Get priority
|
|
60
|
+
# ProcessPayment.queue_priority # => 0
|
|
61
|
+
#
|
|
62
|
+
def queue_priority(pri = nil)
|
|
63
|
+
if pri
|
|
64
|
+
self.postburner_priority = pri
|
|
65
|
+
nil
|
|
66
|
+
else
|
|
67
|
+
postburner_priority
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Sets or returns the queue TTR (time to run).
|
|
72
|
+
#
|
|
73
|
+
# Number of seconds Beanstalkd will wait for job completion before
|
|
74
|
+
# making it available again. Falls back to Postburner.configuration.default_ttr
|
|
75
|
+
# if not set.
|
|
76
|
+
#
|
|
77
|
+
# @param ttr [Integer, nil] Timeout in seconds, or nil to get current value
|
|
78
|
+
#
|
|
79
|
+
# @return [Integer, nil] Current TTR when getting, nil when setting
|
|
80
|
+
#
|
|
81
|
+
# @example Set TTR
|
|
82
|
+
# queue_ttr 300 # 5 minutes
|
|
83
|
+
#
|
|
84
|
+
# @example Get TTR
|
|
85
|
+
# ProcessPayment.queue_ttr # => 300
|
|
86
|
+
#
|
|
87
|
+
def queue_ttr(ttr = nil)
|
|
88
|
+
if ttr
|
|
89
|
+
self.postburner_ttr = ttr
|
|
90
|
+
nil
|
|
91
|
+
else
|
|
92
|
+
postburner_ttr
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -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)
|
|
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