postburner 1.0.0.pre.4 → 1.0.0.pre.5

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: a279e41bee615d867b4392c53c9cfe9535c1652842f166f2f6cd33b3fc6215e2
4
- data.tar.gz: f11ad9028e92679bc5c5fc3d62b75d8a3a163394bb130dac4bb9a1bcd33c7cfb
3
+ metadata.gz: 38e2982c4478f99aa0d19c58ac7455cdf82ff1c3707a154d1dbd0c3a1546ceb8
4
+ data.tar.gz: 0d17bb9da882d8e1cf2cce7c1cddd508ddbc7531c037dd56b7e0c6d34bd40f5b
5
5
  SHA512:
6
- metadata.gz: 70eeebf1007b699b58713d3a9fc107aabf118fe6c13c39d519e4b800416d9ea185bb96012be1e37a617ef7b4e52896e1387aa2f11e9b45077912bb154071f62f
7
- data.tar.gz: 1e34ecbf3dfe03401f7280deca6939e056bceb21ed6d2303e87b4f5c53d5ea23f0d712814acc48a356b471dbd1754505a155e7830c304051bfb1faae2fe4fde1
6
+ metadata.gz: 28421eb19f5e288e43e9d455364906dabe0ee3ab024a230efb9aca19842ac3677c4b602788d8f0f7c67253f64e1b382d12a4f23756ff01ef2e43a5b51d478d55
7
+ data.tar.gz: f13d5452e5394c3e6d8b9468d8863344f35049972fbe5eac6c20cbede12ddc7e9eb2a17538bccb626a7a00a744c567d33e59558f5e7309ab3ecf775bea3db5b9
data/README.md CHANGED
@@ -48,8 +48,11 @@ class ProcessPayment < ApplicationJob
48
48
  end
49
49
  end
50
50
 
51
- # Run worker
51
+ # Run worker (bin/postburner)
52
52
  bundle exec postburner --worker default
53
+
54
+ # Or with rake task
55
+ bundle exec rake postburner:work WORKER=default
53
56
  ```
54
57
 
55
58
  ## Why
@@ -124,7 +127,8 @@ beanstalkd -l 127.0.0.1 -p 11300 # Start beanstalkd
124
127
  bundle exec rails generate postburner:install
125
128
  bundle exec rails db:migrate
126
129
 
127
- bundle exec postburner # start
130
+ bundle exec postburner # start with bin/postburner
131
+ bundle exec rake postburner:work # or with rake task
128
132
  ```
129
133
 
130
134
  ## Table of Contents
@@ -849,6 +853,52 @@ bin/postburner --worker general # Run the 'general' worker
849
853
  bin/postburner --worker general --queues default,mailers # Only process specific queues
850
854
  ```
851
855
 
856
+ **Rake task:**
857
+ ```bash
858
+ bundle exec rake postburner:work # Auto-select worker
859
+ bundle exec rake postburner:work WORKER=general # Specific worker
860
+ bundle exec rake postburner:work WORKER=general QUEUES=default,mailers # Filter queues
861
+ bundle exec rake postburner:work CONFIG=config/custom.yml # Custom config
862
+ ```
863
+
864
+ ### Programmatic Worker Control
865
+
866
+ Use `Postburner::Runner` to start workers programmatically from Ruby code:
867
+
868
+ ```ruby
869
+ # Basic usage
870
+ runner = Postburner::Runner.new(
871
+ config: 'config/postburner.yml',
872
+ env: 'production',
873
+ worker: 'default'
874
+ )
875
+ runner.run
876
+
877
+ # With queue filtering
878
+ runner = Postburner::Runner.new(
879
+ worker: 'general',
880
+ queues: ['default', 'mailers']
881
+ )
882
+ runner.run
883
+
884
+ # From environment variables (Rake task pattern)
885
+ runner = Postburner::Runner.new(
886
+ config: ENV['CONFIG'] || 'config/postburner.yml',
887
+ env: Rails.env,
888
+ worker: ENV['WORKER'],
889
+ queues: ENV['QUEUES']&.split(',')
890
+ )
891
+ runner.run
892
+ ```
893
+
894
+ **Options:**
895
+ - `:config` - Path to YAML configuration file (default: `'config/postburner.yml'`)
896
+ - `:env` - Environment name (default: `RAILS_ENV` or `'development'`)
897
+ - `:worker` - Worker name from config (required if multiple workers defined)
898
+ - `:queues` - Array of queue names to filter (overrides config queues)
899
+
900
+ This provides a unified interface used by both `bin/postburner` and `rake postburner:work`, making it easy to integrate Postburner workers into custom deployment scripts or process managers.
901
+
852
902
  ### Running Workers in Separate Processes
853
903
 
854
904
  For production deployments, run different workers in separate OS processes for isolation and resource allocation:
@@ -24,6 +24,10 @@ module Postburner
24
24
  module Insertion
25
25
  extend ActiveSupport::Concern
26
26
 
27
+ included do
28
+ after_save_commit :insert_if_queued!
29
+ end
30
+
27
31
  # Enqueues the job to Beanstalkd for processing.
28
32
  #
29
33
  # Sets queued_at timestamp and optionally run_at for scheduled execution.
@@ -170,6 +170,20 @@ module Postburner
170
170
  self.class.queue
171
171
  end
172
172
 
173
+ # Returns the full tube name with environment prefix.
174
+ #
175
+ # Expands the queue name to include the environment prefix
176
+ # (e.g., 'critical' becomes 'postburner.development.critical').
177
+ #
178
+ # @return [String] Full tube name with environment prefix
179
+ #
180
+ # @example
181
+ # job.expanded_tube_name # => 'postburner.development.critical'
182
+ #
183
+ def expanded_tube_name
184
+ Postburner.configuration.expand_tube_name(queue_name)
185
+ end
186
+
173
187
  # Returns the priority for this job instance.
174
188
  #
175
189
  # Checks instance-level override first, then falls back to class-level configuration.
@@ -184,6 +198,11 @@ module Postburner
184
198
  @priority || self.class.priority
185
199
  end
186
200
 
201
+ # Alias for {#priority}.
202
+ #
203
+ # @see #priority
204
+ alias_method :pri, :priority
205
+
187
206
  # Returns the TTR (time-to-run) for this job instance.
188
207
  #
189
208
  # Checks instance-level override first, then falls back to class-level configuration.
@@ -198,18 +217,5 @@ module Postburner
198
217
  @ttr || self.class.ttr
199
218
  end
200
219
 
201
- # Returns the full tube name with environment prefix.
202
- #
203
- # Expands the queue name to include the environment prefix
204
- # (e.g., 'critical' becomes 'postburner.development.critical').
205
- #
206
- # @return [String] Full tube name with environment prefix
207
- #
208
- # @example
209
- # job.expanded_tube_name # => 'postburner.development.critical'
210
- #
211
- def expanded_tube_name
212
- Postburner.configuration.expand_tube_name(queue_name)
213
- end
214
220
  end
215
221
  end
@@ -81,7 +81,6 @@ module Postburner
81
81
 
82
82
  before_validation :ensure_sid!
83
83
  before_destroy :delete!
84
- after_save_commit :insert_if_queued!
85
84
 
86
85
  validates :sid, presence: {strict: true}
87
86
 
@@ -237,6 +236,40 @@ module Postburner
237
236
  #
238
237
  class MalformedResponse < StandardError; end
239
238
 
239
+ # Exception raised when Beanstalkd rejects a job with BAD_FORMAT error.
240
+ #
241
+ # Beanstalkd returns BAD_FORMAT when job parameters are invalid, such as:
242
+ # - Negative delay values
243
+ # - Invalid priority (outside 0-4294967295 range)
244
+ # - Invalid TTR (time-to-run)
245
+ # - Malformed job data
246
+ #
247
+ # This exception wraps the original Beaneater::BadFormatError and includes
248
+ # detailed debugging information about the parameters that caused the error.
249
+ # The original exception is preserved via the `cause` chain.
250
+ #
251
+ # @example Common causes
252
+ # # Negative delay (now prevented by fix in queue.rb)
253
+ # job.queue!(at: 1.hour.ago) # Previously caused BAD_FORMAT
254
+ #
255
+ # # Invalid priority
256
+ # job.queue!(pri: -1) # Outside valid range
257
+ #
258
+ # @example Accessing detailed error information
259
+ # begin
260
+ # job.queue!(at: past_time)
261
+ # rescue Postburner::Job::BadFormat => e
262
+ # puts e.message # Includes tube, data, pri, delay, ttr
263
+ # puts e.cause # Original Beaneater::BadFormatError
264
+ # end
265
+ #
266
+ # @note The error message includes full job parameters for debugging
267
+ # @note Access original Beanstalkd error via exception.cause
268
+ #
269
+ # @see Postburner::Queue#insert
270
+ #
271
+ class BadFormat < StandardError; end
272
+
240
273
  private
241
274
 
242
275
 
data/bin/postburner CHANGED
@@ -55,37 +55,6 @@ require File.expand_path('config/environment', Dir.pwd)
55
55
 
56
56
  # Postburner is loaded automatically via the Rails engine when the gem is in your Gemfile
57
57
 
58
- # Load configuration
59
- config_path = File.expand_path(options[:config], Dir.pwd)
60
-
61
- begin
62
- config = Postburner::Configuration.load_yaml(config_path, options[:env], options[:worker])
63
- rescue ArgumentError => e
64
- Rails.logger.error "[Postburner] ERROR: #{e.message}"
65
- exit 1
66
- end
67
-
68
- # Filter queues if --queues option provided
69
- if options[:queues]
70
- # Validate that all specified queues exist in config
71
- invalid_queues = options[:queues] - config.queue_names
72
- unless invalid_queues.empty?
73
- config.logger.error "[Postburner] ERROR: Unknown queue(s): #{invalid_queues.join(', ')}"
74
- config.logger.error "[Postburner] Available queues: #{config.queue_names.join(', ')}"
75
- exit 1
76
- end
77
-
78
- # Filter config to only include specified queues
79
- filtered_queues = config.queues.select { |name, _| options[:queues].include?(name.to_s) }
80
- config.queues = filtered_queues
81
- end
82
-
83
- config.logger.info "[Postburner] Configuration: #{config_path}"
84
- config.logger.info "[Postburner] Environment: #{options[:env]}"
85
- config.logger.info "[Postburner] Worker: #{options[:worker] || '(auto-selected)'}" if options[:worker] || options[:queues].nil?
86
- config.logger.info "[Postburner] Queues: #{config.queue_names.join(', ')}"
87
- config.logger.info "[Postburner] Defaults: forks=#{config.default_forks}, threads=#{config.default_threads}, gc_limit=#{config.default_gc_limit || 'none'}"
88
-
89
- # Create and start worker
90
- worker = Postburner::Workers::Worker.new(config)
91
- worker.start
58
+ # Create and run worker
59
+ runner = Postburner::Runner.new(options)
60
+ runner.run
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Postburner
4
+ # Shared runner logic for bin/postburner and rake tasks.
5
+ #
6
+ # Provides a unified interface for starting Postburner workers from
7
+ # both the command-line executable and Rake tasks. Handles configuration
8
+ # loading, queue filtering, logging, and worker startup.
9
+ #
10
+ # @example Using from bin/postburner
11
+ # options = { config: 'config/postburner.yml', env: 'production', worker: 'default' }
12
+ # runner = Postburner::Runner.new(options)
13
+ # runner.run
14
+ #
15
+ # @example Using from Rake task
16
+ # options = { worker: ENV['WORKER'], queues: ENV['QUEUES']&.split(',') }
17
+ # runner = Postburner::Runner.new(options)
18
+ # runner.run
19
+ #
20
+ class Runner
21
+ attr_reader :options
22
+
23
+ # Initialize runner with options.
24
+ #
25
+ # @param options [Hash] Runner options
26
+ # @option options [String] :config Path to YAML configuration file (default: 'config/postburner.yml')
27
+ # @option options [String] :env Environment name (default: RAILS_ENV or 'development')
28
+ # @option options [String, nil] :worker Worker name from config (required if multiple workers defined)
29
+ # @option options [Array<String>, nil] :queues List of queue names to filter (overrides config)
30
+ def initialize(options = {})
31
+ @options = {
32
+ config: 'config/postburner.yml',
33
+ env: ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development',
34
+ worker: nil,
35
+ queues: nil
36
+ }.merge(options)
37
+ end
38
+
39
+ # Load configuration, filter queues, and start worker.
40
+ #
41
+ # @return [void]
42
+ def run
43
+ config = load_configuration
44
+ filter_queues(config) if options[:queues]
45
+ log_configuration(config)
46
+ start_worker(config)
47
+ end
48
+
49
+ private
50
+
51
+ # Load configuration from YAML file.
52
+ #
53
+ # @return [Postburner::Configuration]
54
+ def load_configuration
55
+ config_path = File.expand_path(options[:config], root_directory)
56
+ Postburner::Configuration.load_yaml(config_path, options[:env], options[:worker])
57
+ rescue ArgumentError => e
58
+ logger.error "[Postburner] ERROR: #{e.message}"
59
+ exit 1
60
+ end
61
+
62
+ # Filter configuration to only include specified queues.
63
+ #
64
+ # @param config [Postburner::Configuration]
65
+ # @return [void]
66
+ def filter_queues(config)
67
+ # Validate that all specified queues exist in config
68
+ invalid_queues = options[:queues] - config.queue_names
69
+ unless invalid_queues.empty?
70
+ config.logger.error "[Postburner] ERROR: Unknown queue(s): #{invalid_queues.join(', ')}"
71
+ config.logger.error "[Postburner] Available queues: #{config.queue_names.join(', ')}"
72
+ exit 1
73
+ end
74
+
75
+ # Filter config to only include specified queues
76
+ filtered_queues = config.queues.select { |name, _| options[:queues].include?(name.to_s) }
77
+ config.queues = filtered_queues
78
+ end
79
+
80
+ # Log configuration details.
81
+ #
82
+ # @param config [Postburner::Configuration]
83
+ # @return [void]
84
+ def log_configuration(config)
85
+ config_path = File.expand_path(options[:config], root_directory)
86
+ config.logger.info "[Postburner] Configuration: #{config_path}"
87
+ config.logger.info "[Postburner] Environment: #{options[:env]}"
88
+ config.logger.info "[Postburner] Worker: #{options[:worker] || '(auto-selected)'}" if options[:worker] || options[:queues].nil?
89
+ config.logger.info "[Postburner] Queues: #{config.queue_names.join(', ')}"
90
+ config.logger.info "[Postburner] Defaults: forks=#{config.default_forks}, threads=#{config.default_threads}, gc_limit=#{config.default_gc_limit || 'none'}"
91
+ end
92
+
93
+ # Returns root directory for config file resolution.
94
+ #
95
+ # Uses Rails.root when Rails is defined, otherwise falls back to current directory.
96
+ #
97
+ # @return [String] Root directory path
98
+ def root_directory
99
+ if defined?(Rails) && Rails.respond_to?(:root)
100
+ Rails.root.to_s
101
+ else
102
+ Dir.pwd
103
+ end
104
+ end
105
+
106
+ # Create and start worker.
107
+ #
108
+ # @param config [Postburner::Configuration]
109
+ # @return [void]
110
+ def start_worker(config)
111
+ worker = Postburner::Workers::Worker.new(config)
112
+ worker.start
113
+ end
114
+
115
+ # Returns logger for error reporting during initialization.
116
+ #
117
+ # @return [Logger]
118
+ def logger
119
+ if defined?(Rails)
120
+ Rails.logger
121
+ else
122
+ Logger.new($stderr)
123
+ end
124
+ end
125
+ end
126
+ end
@@ -81,24 +81,21 @@ 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] || 0
84
+ delay = options[:delay].present? ? [0, options[:delay].to_i].max : 0
85
85
  ttr = options[:ttr] || job.ttr || Postburner.configuration.default_ttr
86
+ payload = JSON.generate(data)
86
87
 
87
88
  begin
88
89
  response = conn.tubes[tube_name].put(
89
- JSON.generate(data),
90
+ payload,
90
91
  pri: pri,
91
92
  delay: delay,
92
93
  ttr: ttr
93
94
  )
94
95
  rescue Beaneater::BadFormatError => e
95
- Rails.logger.error "Beanstalkd BAD_FORMAT error from:"
96
- Rails.logger.error " tube: #{tube_name}"
97
- Rails.logger.error " data: #{JSON.generate(data)}"
98
- Rails.logger.error " pri: #{pri.inspect} (#{pri.class})"
99
- Rails.logger.error " delay: #{delay.inspect} (#{delay.class})"
100
- Rails.logger.error " ttr: #{ttr.inspect} (#{ttr.class})"
101
- raise
96
+ raise Postburner::Job::BadFormat.new(
97
+ "Beanstalkd BAD_FORMAT: tube=#{tube_name} payload=#{payload} pri=#{pri.inspect} delay=#{delay.inspect} ttr=#{ttr.inspect}"
98
+ ), cause: e
102
99
  end
103
100
 
104
101
  response
@@ -1,3 +1,3 @@
1
1
  module Postburner
2
- VERSION = '1.0.0.pre.4'
2
+ VERSION = '1.0.0.pre.5'
3
3
  end
data/lib/postburner.rb CHANGED
@@ -8,6 +8,7 @@ require "postburner/active_job/execution"
8
8
  require "postburner/active_job/adapter"
9
9
  require "postburner/workers/base"
10
10
  require "postburner/workers/worker"
11
+ require "postburner/runner"
11
12
  require "postburner/engine"
12
13
  require "postburner/strategies/queue"
13
14
  require "postburner/strategies/nice_queue"
@@ -449,7 +450,6 @@ module Postburner
449
450
  #
450
451
  def self.stats
451
452
  connected do |conn|
452
-
453
453
  {
454
454
  watched_tubes: self.watched_tubes,
455
455
  # Get all tube instances that exist on Beanstalkd
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :postburner do
4
+ desc "Start Postburner worker (ENV: WORKER=name, QUEUES=queue1,queue2, CONFIG=path)"
5
+ task work: :environment do
6
+ # Parse options from environment variables
7
+ options = {
8
+ config: ENV['CONFIG'] || 'config/postburner.yml',
9
+ env: Rails.env,
10
+ worker: ENV['WORKER'],
11
+ queues: ENV['QUEUES']&.split(',')&.map(&:strip)
12
+ }
13
+
14
+ # Create and run worker
15
+ runner = Postburner::Runner.new(options)
16
+ runner.run
17
+ end
18
+ 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.pre.4
4
+ version: 1.0.0.pre.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Smith
@@ -104,7 +104,6 @@ extensions: []
104
104
  extra_rdoc_files: []
105
105
  files:
106
106
  - CHANGELOG.md
107
- - MIT-LICENSE
108
107
  - README.md
109
108
  - Rakefile
110
109
  - app/assets/config/postburner_manifest.js
@@ -147,6 +146,7 @@ files:
147
146
  - lib/postburner/configuration.rb
148
147
  - lib/postburner/connection.rb
149
148
  - lib/postburner/engine.rb
149
+ - lib/postburner/runner.rb
150
150
  - lib/postburner/strategies/immediate_test_queue.rb
151
151
  - lib/postburner/strategies/nice_queue.rb
152
152
  - lib/postburner/strategies/null_queue.rb
@@ -157,6 +157,7 @@ files:
157
157
  - lib/postburner/version.rb
158
158
  - lib/postburner/workers/base.rb
159
159
  - lib/postburner/workers/worker.rb
160
+ - lib/tasks/postburner.rake
160
161
  - lib/tasks/postburner_tasks.rake
161
162
  homepage: https://gitlab.nearapogee.com/opensource/postburner
162
163
  licenses:
data/MIT-LICENSE DELETED
@@ -1,20 +0,0 @@
1
- Copyright 2021 Matt Smith
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.