quebert 2.0.4 → 3.0.0

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
  SHA1:
3
- metadata.gz: 13a07e8336155e0c777ed1295201b2d7d23e9bee
4
- data.tar.gz: 6e3929be1a44ee006809f3b24a1ef605aed7605d
3
+ metadata.gz: 2bda9cb5709ea354e0ccb9822af9866fc9074795
4
+ data.tar.gz: 45923c3de44f7d15397732d30448931540847f50
5
5
  SHA512:
6
- metadata.gz: f56a3426063aa8353e6e52f8c61bb7a76e73bdbced8df6a919872979fb33102ce0e9cbd536046ccc30fa21d1e57b38b994d275abcfd560366d6b451cf7507257
7
- data.tar.gz: 2f464763bb13a6eefd45ebc46f8a2dbc3a326dff7e5968960319f37a52bcf10c3d5d5c3abf64a55d6ca009a30b12d705b65367f47d0e0ab9572969aeb5651427
6
+ metadata.gz: 41ad3f71924915e1ce6341b45cd8f079925dc2cfba0859bd2282d6eabf448a192841065663d0871c83830553f7b2eeb5029d6f1f5ec0f0be5c8a0a4d830dfea1
7
+ data.tar.gz: 466dd771292f13cae5fc7f876c23dea8bb9935dcc396a4d0fbe0e5e537f13024e6e68af71864f61e154a68f6145ee31d8e9364b90463f2dd46bcbfbb2a65e398
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.6
data/README.md CHANGED
@@ -32,91 +32,174 @@ There are two ways to enqueue jobs with Quebert: through the Job itself, provide
32
32
  ## Jobs
33
33
 
34
34
  Quebert includes a Job class so you can implement how you want certain types of Jobs performed.
35
-
36
- Quebert.backend = Quebert::Backend::InProcess.new
37
-
38
- class WackyMathWizard < Quebert::Job
39
- def perform(*nums)
40
- nums.inject(0){|sum, n| sum = sum + n}
41
- end
42
- end
35
+
36
+ ```ruby
37
+ Quebert.backend = Quebert::Backend::InProcess.new
38
+
39
+ class WackyMathWizard < Quebert::Job
40
+ def perform(*nums)
41
+ nums.inject(0){|sum, n| sum = sum + n}
42
+ end
43
+ end
44
+ ```
43
45
 
44
46
  You can either drop a job in a queue:
45
47
 
46
- Quebert.backend.put WackyMathWizard.new(1, 2, 3)
48
+ ```ruby
49
+ Quebert.backend.put WackyMathWizard.new(1, 2, 3)
50
+ ```
47
51
 
48
52
  Or drop it in right from the job:
49
53
 
50
- WackyMathWizard.new(4, 5, 6).enqueue
54
+ ```ruby
55
+ WackyMathWizard.new(4, 5, 6).enqueue
56
+ ```
51
57
 
52
58
  Then perform the jobs!
53
59
 
54
- Quebert.backend.reserve.perform # => 6
55
- Quebert.backend.reserve.perform # => 15
60
+ ```ruby
61
+ Quebert.backend.reserve.perform # => 6
62
+ Quebert.backend.reserve.perform # => 15
63
+ ```
64
+
65
+ ## Rails integration
66
+
67
+ config/quebert.yml:
68
+
69
+ ```yaml
70
+ development:
71
+ backend: beanstalk
72
+ host: localhost:11300
73
+ queue: myapp-development
74
+ test:
75
+ backend: sync
76
+ # etc.
77
+ ```
78
+
79
+ config/initializers/quebert.rb:
80
+
81
+ ```ruby
82
+ Quebert.config.from_hash(Rails.application.config.quebert)
83
+ Quebert.config.logger = Rails.logger
84
+ ```
56
85
 
57
86
  ## Before/After/Around Hooks
58
87
 
59
88
  Quebert has support for providing custom hooks to be called before, after & around your jobs are being run.
60
89
  A common example is making sure that any active ActiveRecord database connections are put back on the connection pool after a job is done:
61
90
 
62
- Quebert.config.after_job do
63
- ActiveRecord::Base.clear_active_connections!
64
- end
91
+ ```ruby
92
+ Quebert.config.after_job do
93
+ ActiveRecord::Base.clear_active_connections!
94
+ end
65
95
 
66
- Quebert.config.before_job do |job|
67
- # all hooks take an optional job argument
68
- # in case you want to do something with that job
69
- end
96
+ Quebert.config.before_job do |job|
97
+ # all hooks take an optional job argument
98
+ # in case you want to do something with that job
99
+ end
70
100
 
71
- Quebert.config.around_job do |job|
72
- # this hook gets called twice
73
- # once before & once after a job is performed
74
- end
101
+ Quebert.config.around_job do |job|
102
+ # this hook gets called twice
103
+ # once before & once after a job is performed
104
+ end
105
+ ```
75
106
 
76
107
  ## Async Sender
77
108
 
78
109
  Take any ol' class and include the Quebert::AsyncSender.
79
110
 
80
- Quebert.backend = Quebert::Backend::InProcess.new
81
-
82
- class Greeter
83
- include Quebert::AsyncSender::Class
84
-
85
- def initialize(name)
86
- @name = name
87
- end
88
-
89
- def sleep_and_greet(time_of_day)
90
- sleep 10000 # Sleeping, get it?
91
- "Oh! Hi #{name}! Good #{time_of_day}."
92
- end
93
-
94
- def self.budweiser_greeting(name)
95
- "waaazup #{name}!"
96
- end
97
- end
98
-
99
- walmart_greeter = Greeter.new("Brad")
111
+ ```ruby
112
+ Quebert.backend = Quebert::Backend::InProcess.new
113
+
114
+ class Greeter
115
+ include Quebert::AsyncSender::Class
116
+
117
+ def initialize(name)
118
+ @name = name
119
+ end
120
+
121
+ def sleep_and_greet(time_of_day)
122
+ sleep 10000 # Sleeping, get it?
123
+ "Oh! Hi #{name}! Good #{time_of_day}."
124
+ end
125
+
126
+ def self.budweiser_greeting(name)
127
+ "waaazup #{name}!"
128
+ end
129
+ end
130
+
131
+ walmart_greeter = Greeter.new("Brad")
132
+ ```
100
133
 
101
134
  Remember the send method in ruby?
102
135
 
103
- walmart_greeter.send(:sleep_and_greet, "morning")
104
- # ... time passes, you wait as greeter snores obnoxiously ...
105
- # => "Oh! Hi Brad! Good morning."
136
+ ```ruby
137
+ walmart_greeter.send(:sleep_and_greet, "morning")
138
+ # ... time passes, you wait as greeter snores obnoxiously ...
139
+ # => "Oh! Hi Brad! Good morning."
140
+ ```
106
141
 
107
142
  What if the method takes a long time to run and you want to queue it? async.send it!
108
143
 
109
- walmart_greeter.async.sleep_and_greet("morning")
110
- # ... do some shopping and come back later when the dude wakes up
111
-
112
- Quebert figures out how to *serialize the class, throw it on a worker queue, re-instantiate it on the other side, and finish up the work.
144
+ ```ruby
145
+ walmart_greeter.async.sleep_and_greet("morning")
146
+ # ... do some shopping and come back later when the dude wakes up
147
+ ```
148
+
149
+ Quebert figures out how to serialize the class, throw it on a worker queue, re-instantiate it on the other side, and finish up the work.
150
+
151
+ ```ruby
152
+ Quebert.backend.reserve.perform # => "Oh! Hi Brad! Good morning."
153
+ # ... Sorry dude! I'm shopping already
154
+ ```
113
155
 
114
- Quebert.backend.reserve.perform # => "Oh! Hi Brad! Good morning."
115
- # ... Sorry dude! I'm shopping already
116
-
117
156
  Does it work on Class methods? Yeah, that was easier than making instance methods work:
118
157
 
119
- Quebert.async.budweiser_greeting("Corey")
120
- Quebert.backend.reserve.perform # => "waazup Corey!"
158
+ ```ruby
159
+ Quebert.async.budweiser_greeting("Coraline")
160
+ Quebert.backend.reserve.perform # => "waazup Coraline!"
161
+ ```
121
162
 
122
163
  * Only basic data types are included for serialization. Serializers may be customized to include support for different types.
164
+
165
+ ## Backends
166
+
167
+ * Beanstalk: Enqueue jobs in a beanstalkd service. The workers run in a separate process. Typically used in production environments.
168
+ * Sync: Perform jobs immediately upon enqueuing. Typically used in testing environments.
169
+ * InProcess: Enqueue jobs in an in-memory array. A worker will need to reserve a job to perform.
170
+
171
+ ## Using multiple queues
172
+
173
+ To start a worker pointed at a non-default queue (e.g., a Quebert "tube"), start the process with `-q`:
174
+
175
+ ```sh
176
+ bundle exec quebert -q other-tube
177
+ ```
178
+
179
+ Then specify the queue name in your job:
180
+
181
+ ```ruby
182
+ class FooJob < Quebert::Job
183
+ def queue
184
+ "other-tube"
185
+ end
186
+
187
+ def perform(args)
188
+ # ...
189
+ end
190
+ end
191
+ ```
192
+
193
+ ## Beanstalk: Changing a job's TTR
194
+
195
+ ```ruby
196
+ class FooJob < Quebert::Job
197
+ def ttr
198
+ 5.minutes
199
+ end
200
+
201
+ def perform(args)
202
+ # ...
203
+ end
204
+ end
205
+ ```
@@ -1,33 +1,39 @@
1
- require 'beaneater'
1
+ require "beaneater"
2
+ require "forwardable"
2
3
 
3
4
  module Quebert
4
5
  module Backend
5
-
6
6
  # Manage jobs on a Beanstalk queue out of process
7
7
  class Beanstalk
8
- def initialize(host, tube_name)
9
- @host, @tube_name = host, tube_name
8
+ extend Forwardable
9
+ include Logging
10
+
11
+ # A buffer time in seconds added to the Beanstalk TTR for Quebert to do
12
+ # its own job cleanup The job will perform based on the Beanstalk TTR,
13
+ # but Beanstalk hangs on to the job just a little longer so that Quebert
14
+ # can bury the job or schedule a retry with the appropriate delay
15
+ TTR_BUFFER = 5
16
+
17
+ attr_reader :host, :queue
18
+ attr_writer :queues
19
+
20
+ def initialize(host, queue)
21
+ @host = host
22
+ @queue = queue
23
+ @queues = []
10
24
  end
11
25
 
12
- def put(job, *args)
13
- priority, delay, ttr = args
14
- opts = {}
15
- opts[:pri] = priority unless priority.nil?
16
- opts[:delay] = delay unless delay.nil?
17
- opts[:ttr] = ttr unless ttr.nil?
18
- tube.put job.to_json, opts
26
+ def self.configure(opts = {})
27
+ new(opts.fetch(:host, "127.0.0.1:11300"), opts.fetch(:queue))
19
28
  end
20
29
 
21
30
  def reserve_without_controller(timeout=nil)
22
- tube.reserve timeout
31
+ watch_tubes
32
+ beanstalkd_tubes.reserve(timeout)
23
33
  end
24
34
 
25
35
  def reserve(timeout=nil)
26
- Controller::Beanstalk.new reserve_without_controller(timeout), self
27
- end
28
-
29
- def peek(state)
30
- tube.peek state
36
+ Controller::Beanstalk.new(reserve_without_controller(timeout))
31
37
  end
32
38
 
33
39
  # For testing purposes... I think there's a better way to do this though.
@@ -39,24 +45,47 @@ module Quebert
39
45
  reserve_without_controller.delete
40
46
  end
41
47
  while peek(:buried) do
42
- tube.kick
48
+ kick
43
49
  reserve_without_controller.delete
44
50
  end
45
51
  end
46
-
47
- def self.configure(opts={})
48
- opts[:host] ||= ['127.0.0.1:11300']
49
- new(opts[:host], opts[:tube])
52
+
53
+ # TODO add a queue param?
54
+ def_delegators :default_tube, :peek, :kick
55
+
56
+ def put(job)
57
+ tube = beanstalkd_tubes[job.queue || queue]
58
+ tube.put(job.to_json,
59
+ :pri => job.priority,
60
+ :delay => job.delay,
61
+ :ttr => job.ttr + TTR_BUFFER)
50
62
  end
51
63
 
52
64
  private
53
- def pool
54
- @pool ||= Beaneater::Pool.new Array(@host)
65
+
66
+ def default_tube
67
+ @default_tube ||= beanstalkd_tubes[queue]
68
+ end
69
+
70
+ def beanstalkd_connection
71
+ @beanstalkd_connection ||= Beaneater.new(host)
72
+ end
73
+
74
+ def beanstalkd_tubes
75
+ beanstalkd_connection.tubes
76
+ end
77
+
78
+ def watch_tubes
79
+ if queues != @watched_tube_names
80
+ @watched_tube_names = queues
81
+ logger.info "Watching beanstalkd queues #{@watched_tube_names.inspect}"
82
+ beanstalkd_tubes.watch!(*@watched_tube_names)
83
+ end
55
84
  end
56
85
 
57
- def tube
58
- @tube ||= pool.tubes[@tube_name]
86
+ def queues
87
+ @queues.empty? ? [queue] : @queues
59
88
  end
60
89
  end
61
90
  end
62
- end
91
+ end
@@ -28,6 +28,7 @@ module Quebert
28
28
  "(default: #{@options[:pid]})") { |file| @options[:pid] = file }
29
29
  opts.on("-C", "--config FILE", "Load options from config file") { |file| @options[:config] = file }
30
30
  opts.on("-c", "--chdir DIR", "Change to dir before starting") { |dir| @options[:chdir] = File.expand_path(dir) }
31
+ opts.on("-q", "--queues LIST", "Specify queue name(s)") { |list| @options[:queues] = list.split(",") }
31
32
  end
32
33
  end
33
34
 
@@ -58,7 +59,9 @@ module Quebert
58
59
  require config
59
60
  end
60
61
 
61
- Worker.new.start
62
+ worker = Worker.new
63
+ worker.queues = params[:queues] if params[:queues]
64
+ worker.start
62
65
  end
63
66
 
64
67
  private
@@ -70,4 +73,4 @@ module Quebert
70
73
  end
71
74
  end
72
75
  end
73
- end
76
+ end
@@ -6,33 +6,33 @@ module Quebert
6
6
  class Beanstalk < Base
7
7
  include Logging
8
8
 
9
- attr_reader :beanstalk_job, :queue, :job
9
+ attr_reader :beanstalk_job, :job
10
10
 
11
11
  MAX_TIMEOUT_RETRY_DELAY = 300
12
12
  TIMEOUT_RETRY_DELAY_SEED = 2
13
13
  TIMEOUT_RETRY_GROWTH_RATE = 3
14
14
 
15
- def initialize(beanstalk_job, queue)
16
- @beanstalk_job, @queue = beanstalk_job, queue
15
+ def initialize(beanstalk_job)
16
+ @beanstalk_job = beanstalk_job
17
17
  @job = Job.from_json(beanstalk_job.body)
18
18
  rescue Job::Delete
19
19
  beanstalk_job.delete
20
- log "Deleted on initialization", :error
20
+ job_log "Deleted on initialization", :error
21
21
  rescue Job::Release
22
- beanstalk_job.release @job.priority, @job.delay
23
- log "Released on initialization with priority: #{@job.priority} and delay: #{@job.delay}", :error
22
+ beanstalk_job.release job.priority, job.delay
23
+ job_log "Released on initialization with priority: #{job.priority} and delay: #{job.delay}", :error
24
24
  rescue Job::Bury
25
25
  beanstalk_job.bury
26
- log "Buried on initialization", :error
27
- rescue Exception => e
26
+ job_log "Buried on initialization", :error
27
+ rescue => e
28
28
  beanstalk_job.bury
29
- log "Exception caught on initialization. #{e.inspect}", :error
29
+ job_log "Error caught on initialization. #{e.inspect}", :error
30
30
  raise
31
31
  end
32
32
 
33
33
  def perform
34
- log "Performing with args #{job.args.inspect}"
35
- log "Beanstalk Job Stats: #{beanstalk_job.stats.inspect}"
34
+ job_log "Performing with args #{job.args.inspect}"
35
+ job_log "Beanstalk Job Stats: #{beanstalk_job.stats.inspect}"
36
36
 
37
37
  result = false
38
38
  time = Benchmark.realtime do
@@ -40,33 +40,33 @@ module Quebert
40
40
  beanstalk_job.delete
41
41
  end
42
42
 
43
- log "Completed in #{(time*1000*1000).to_i/1000.to_f} ms\n"
43
+ job_log "Completed in #{(time*1000*1000).to_i/1000.to_f} ms\n"
44
44
  result
45
45
  rescue Job::Delete
46
- log "Deleting job", :error
46
+ job_log "Deleting job", :error
47
47
  beanstalk_job.delete
48
- log "Job deleted", :error
48
+ job_log "Job deleted", :error
49
49
  rescue Job::Release
50
- log "Releasing with priority: #{@job.priority} and delay: #{@job.delay}", :error
51
- beanstalk_job.release :pri => @job.priority, :delay => @job.delay
52
- log "Job released", :error
50
+ job_log "Releasing with priority: #{job.priority} and delay: #{job.delay}", :error
51
+ beanstalk_job.release :pri => job.priority, :delay => job.delay
52
+ job_log "Job released", :error
53
53
  rescue Job::Bury
54
- log "Burrying job", :error
54
+ job_log "Burrying job", :error
55
55
  beanstalk_job.bury
56
- log "Job burried", :error
56
+ job_log "Job burried", :error
57
57
  rescue Job::Timeout => e
58
- log "Job timed out. Retrying with delay. #{e.inspect} #{e.backtrace.join("\n")}", :error
58
+ job_log "Job timed out. Retrying with delay. #{e.inspect} #{e.backtrace.join("\n")}", :error
59
59
  retry_with_delay
60
60
  raise
61
61
  rescue Job::Retry
62
62
  # The difference between the Retry and Timeout class is that
63
- # Retry does not log an exception where as Timeout does
64
- log "Manually retrying with delay"
63
+ # Retry does not job_log an exception where as Timeout does
64
+ job_log "Manually retrying with delay"
65
65
  retry_with_delay
66
- rescue Exception => e
67
- log "Exception caught on perform. Burying job. #{e.inspect} #{e.backtrace.join("\n")}", :error
66
+ rescue => e
67
+ job_log "Error caught on perform. Burying job. #{e.inspect} #{e.backtrace.join("\n")}", :error
68
68
  beanstalk_job.bury
69
- log "Job buried", :error
69
+ job_log "Job buried", :error
70
70
  raise
71
71
  end
72
72
 
@@ -75,23 +75,27 @@ module Quebert
75
75
  delay = TIMEOUT_RETRY_DELAY_SEED + TIMEOUT_RETRY_GROWTH_RATE**beanstalk_job.stats["releases"].to_i
76
76
 
77
77
  if delay > MAX_TIMEOUT_RETRY_DELAY
78
- log "Max retry delay exceeded. Burrying job"
78
+ job_log "Max retry delay exceeded. Burrying job"
79
79
  beanstalk_job.bury
80
- log "Job burried"
80
+ job_log "Job burried"
81
81
  else
82
- log "TTR exceeded. Releasing with priority: #{@job.priority} and delay: #{delay}"
83
- beanstalk_job.release :pri => @job.priority, :delay => delay
84
- log "Job released"
82
+ job_log "TTR exceeded. Releasing with priority: #{job.priority} and delay: #{delay}"
83
+ beanstalk_job.release :pri => job.priority, :delay => delay
84
+ job_log "Job released"
85
85
  end
86
86
  rescue ::Beaneater::NotFoundError
87
- log "Job ran longer than allowed. Beanstalk already deleted it!!!!", :error
87
+ job_log "Job ran longer than allowed. Beanstalk already deleted it!!!!", :error
88
88
  # Sometimes the timer doesn't behave correctly and this job actually runs longer than
89
89
  # allowed. At that point the beanstalk job no longer exists anymore. Lets let it go and don't blow up.
90
90
  end
91
91
 
92
- def log(message, level=:info)
92
+ def job_log(message, level=:info)
93
93
  # Have the job write to the log file so that we catch the details of the job
94
- job.send(:log, message, level)
94
+ if job
95
+ job.send(:log, message, level)
96
+ else
97
+ Quebert.logger.send(level, message)
98
+ end
95
99
  end
96
100
  end
97
101
  end
data/lib/quebert/job.rb CHANGED
@@ -6,7 +6,7 @@ module Quebert
6
6
  include Logging
7
7
 
8
8
  attr_reader :args
9
- attr_accessor :priority, :delay, :ttr
9
+ attr_accessor :priority, :delay, :ttr, :queue
10
10
 
11
11
  # Prioritize Quebert jobs as specified in https://github.com/kr/beanstalkd/blob/master/doc/protocol.txt.
12
12
  class Priority
@@ -21,11 +21,6 @@ module Quebert
21
21
  # By default, the job should live for 10 seconds tops.
22
22
  DEFAULT_JOB_TTR = 10
23
23
 
24
- # A buffer time in seconds added to the Beanstalk TTR for Quebert to do its own job cleanup
25
- # The job will perform based on the Beanstalk TTR, but Beanstalk hangs on to the job just a
26
- # little longer so that Quebert can bury the job or schedule a retry with the appropriate delay
27
- QUEBERT_TTR_BUFFER = 5
28
-
29
24
  # Exceptions are used for signaling job status... ewww. Yank this out and
30
25
  # replace with a more well thought out controller.
31
26
  NotImplemented = Class.new(StandardError)
@@ -57,7 +52,7 @@ module Quebert
57
52
  # Honor the timeout and kill the job in ruby-space. Beanstalk
58
53
  # should be cleaning up this job and returning it to the queue
59
54
  # as well.
60
- val = ::Timeout.timeout(@ttr, Job::Timeout){ perform(*args) }
55
+ val = ::Timeout.timeout(ttr, Job::Timeout){ perform(*args) }
61
56
 
62
57
  Quebert.config.around_job(self)
63
58
  Quebert.config.after_job(self)
@@ -66,9 +61,9 @@ module Quebert
66
61
  end
67
62
 
68
63
  # Accepts arguments that override the job options and enqueu this stuff.
69
- def enqueue(opts={})
70
- opts.each { |opt, val| self.send("#{opt}=", val) }
71
- self.class.backend.put self, @priority, @delay, @ttr + QUEBERT_TTR_BUFFER
64
+ def enqueue(override_opts={})
65
+ override_opts.each { |opt, val| self.send("#{opt}=", val) }
66
+ backend.put(self)
72
67
  end
73
68
 
74
69
  # Serialize the job into a JSON string that we can put on the beandstalkd queue.
@@ -87,6 +82,10 @@ module Quebert
87
82
  @backend = backend
88
83
  end
89
84
 
85
+ def backend
86
+ self.class.backend
87
+ end
88
+
90
89
  def self.backend
91
90
  @backend || Quebert.configuration.backend
92
91
  end
@@ -1,6 +1,6 @@
1
1
  module Quebert
2
2
  module Serializer
3
-
3
+
4
4
  # Does this mean you could queue a job that could queue a job? Whoa!
5
5
  class Job
6
6
  def self.serialize(job)
@@ -9,21 +9,23 @@ module Quebert
9
9
  'args' => serialize_args(job.args),
10
10
  'priority' => job.priority,
11
11
  'delay' => job.delay,
12
- 'ttr' => job.ttr
12
+ 'ttr' => job.ttr,
13
+ 'queue' => job.queue
13
14
  }
14
15
  end
15
-
16
+
16
17
  def self.deserialize(hash)
17
18
  hash = Support.stringify_keys(hash)
18
19
  job = Support.constantize(hash['job']).new(*deserialize_args(hash['args']))
19
20
  job.priority = hash['priority']
20
21
  job.delay = hash['delay']
21
22
  job.ttr = hash['ttr']
23
+ job.queue = hash['queue']
22
24
  job
23
25
  end
24
-
26
+
25
27
  private
26
-
28
+
27
29
  # Reflect on each arg and see if it has a seralizer
28
30
  def self.serialize_args(args)
29
31
  args.map do |arg|
@@ -37,7 +39,7 @@ module Quebert
37
39
  hash
38
40
  end
39
41
  end
40
-
42
+
41
43
  # Find a serializer and/or push out a value
42
44
  def self.deserialize_args(args)
43
45
  args.map do |arg|
@@ -50,7 +52,7 @@ module Quebert
50
52
  end
51
53
  end
52
54
  end
53
-
55
+
54
56
  # Deal with converting an AR to/from a hash that we can send over the wire.
55
57
  class ActiveRecord
56
58
  def self.serialize(record)
@@ -60,7 +62,7 @@ module Quebert
60
62
  end
61
63
  { 'model' => record.class.model_name.to_s, 'attributes' => attrs }
62
64
  end
63
-
65
+
64
66
  def self.deserialize(hash)
65
67
  hash = Support.stringify_keys(hash)
66
68
  model = Support.constantize(hash.delete('model'))
@@ -81,6 +83,5 @@ module Quebert
81
83
  end
82
84
  end
83
85
  end
84
-
85
86
  end
86
87
  end
@@ -1,3 +1,3 @@
1
1
  module Quebert
2
- VERSION = "2.0.4"
2
+ VERSION = "3.0.0"
3
3
  end
@@ -2,9 +2,10 @@ module Quebert
2
2
  class Worker
3
3
  include Logging
4
4
 
5
- attr_accessor :exception_handler, :backend
5
+ attr_accessor :exception_handler, :backend, :queues
6
6
 
7
7
  def initialize
8
+ @queues = []
8
9
  yield self if block_given?
9
10
  end
10
11
 
@@ -14,10 +15,13 @@ module Quebert
14
15
  Signal.trap('INT') { safe_stop }
15
16
 
16
17
  logger.info "Worker started with #{backend.class.name} backend\n"
18
+
19
+ backend.queues = queues if backend.respond_to?(:queues=)
20
+
17
21
  while @controller = backend.reserve do
18
22
  begin
19
23
  @controller.perform
20
- rescue Exception => error
24
+ rescue => error
21
25
  if exception_handler
22
26
  exception_handler.call(
23
27
  error,
@@ -26,7 +30,7 @@ module Quebert
26
30
  :worker => self
27
31
  )
28
32
  else
29
- raise error
33
+ raise
30
34
  end
31
35
  end
32
36
  @controller = nil
data/quebert.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  # specify any dependencies here; for example:
22
22
  # s.add_development_dependency "rspec"
23
23
  s.add_runtime_dependency "json"
24
- s.add_runtime_dependency "beaneater"
25
-
24
+ s.add_runtime_dependency "beaneater", "~> 1.0"
25
+
26
26
  s.add_development_dependency 'rspec', '2.7.0'
27
- end
27
+ end
data/spec/backend_spec.rb CHANGED
@@ -51,6 +51,18 @@ describe Backend::Beanstalk do
51
51
  @q.reserve.perform.should eql(num)
52
52
  end
53
53
  end
54
+
55
+ it "should consume from multiple queues" do
56
+ @q.queues = ["a", "b"]
57
+ job1 = Adder.new(1)
58
+ job1.queue = "a"
59
+ @q.put(job1)
60
+ job2 = Adder.new(2)
61
+ job2.queue = "b"
62
+ @q.put(job2)
63
+ @q.reserve.perform.should eql(1)
64
+ @q.reserve.perform.should eql(2)
65
+ end
54
66
  end
55
67
 
56
68
  describe Backend::Sync do
@@ -3,18 +3,19 @@ require 'spec_helper'
3
3
  describe Configuration do
4
4
  context "from hash" do
5
5
  before(:all) do
6
- @config = Configuration.new.from_hash('backend' => 'beanstalk', 'host' => 'localhost:11300', 'tube' => 'quebert-config-test')
6
+ @config = Configuration.new.from_hash(
7
+ "backend" => "beanstalk",
8
+ "host" => "localhost:11300",
9
+ "queue" => "quebert-config-test")
7
10
  end
8
-
11
+
9
12
  it "should configure backend" do
10
13
  backend = @config.backend
11
14
  backend.should be_instance_of(Quebert::Backend::Beanstalk)
12
15
  # Blech, gross nastiness in their lib, but we need to look in to see if this stuff as configed
13
- pool = backend.send(:pool)
14
- pool.connections.first.address.should eql('localhost:11300')
15
- tube = backend.send(:tube)
16
- tube.name.should eql('quebert-config-test')
16
+ backend.send(:beanstalkd_connection).connection.host.should eql("localhost")
17
+ backend.send(:beanstalkd_connection).connection.port.should eql(11300)
18
+ backend.send(:default_tube).name.should eql("quebert-config-test")
17
19
  end
18
-
19
20
  end
20
- end
21
+ end
@@ -4,7 +4,7 @@ describe Controller::Base do
4
4
  it "should perform job" do
5
5
  Controller::Base.new(Adder.new(1,2)).perform.should eql(3)
6
6
  end
7
-
7
+
8
8
  it "should rescue all raised job actions" do
9
9
  [ReleaseJob, DeleteJob, BuryJob].each do |job|
10
10
  lambda{
@@ -16,34 +16,34 @@ end
16
16
 
17
17
  describe Controller::Beanstalk do
18
18
  before(:all) do
19
- @q = Backend::Beanstalk.configure(:host => 'localhost:11300', :tube => 'quebert-test-jobs-actions')
19
+ @q = Backend::Beanstalk.configure(:host => "localhost:11300",
20
+ :queue => "quebert-test-jobs-actions")
20
21
  end
21
-
22
+
22
23
  before(:each) do
23
24
  @q.drain!
24
25
  end
25
-
26
+
26
27
  it "should delete job off queue after succesful run" do
27
28
  @q.put Adder.new(1, 2)
28
29
  @q.peek(:ready).should_not be_nil
29
30
  @q.reserve.perform.should eql(3)
30
31
  @q.peek(:ready).should be_nil
31
32
  end
32
-
33
+
33
34
  it "should bury job if an exception occurs in job" do
34
35
  @q.put Exceptional.new
35
36
  @q.peek(:ready).should_not be_nil
36
37
  lambda{ @q.reserve.perform }.should raise_exception
37
38
  @q.peek(:buried).should_not be_nil
38
39
  end
39
-
40
+
40
41
  it "should bury an AR job if an exception occurs deserializing it" do
41
- @user = User.new(:first_name => "John", :last_name => "Doe", :email => "jdoe@gmail.com")
42
- @user.id = 1
43
- @q.put Serializer::ActiveRecord.serialize(@user)
44
- @q.peek(:ready).should_not be_nil
42
+ tube = @q.send(:default_tube)
43
+ tube.put({:foo => "bar"}.to_json)
44
+ tube.peek(:ready).should_not be_nil
45
45
  lambda{ @q.reserve.perform }.should raise_exception
46
- @q.peek(:buried).should_not be_nil
46
+ tube.peek(:buried).should_not be_nil
47
47
  end
48
48
 
49
49
  context "job actions" do
@@ -53,14 +53,14 @@ describe Controller::Beanstalk do
53
53
  @q.reserve.perform
54
54
  @q.peek(:ready).should be_nil
55
55
  end
56
-
56
+
57
57
  it "should release job" do
58
58
  @q.put ReleaseJob.new
59
59
  @q.peek(:ready).should_not be_nil
60
60
  @q.reserve.perform
61
61
  @q.peek(:ready).should_not be_nil
62
62
  end
63
-
63
+
64
64
  it "should bury job" do
65
65
  @q.put BuryJob.new
66
66
  @q.peek(:ready).should_not be_nil
@@ -79,7 +79,7 @@ describe Controller::Beanstalk do
79
79
  job.beanstalk_job.stats["releases"].should eql(0)
80
80
  job.beanstalk_job.stats["delay"].should eql(0)
81
81
  lambda{job.perform}.should raise_exception(Quebert::Job::Timeout)
82
-
82
+
83
83
  @q.peek(:ready).should be_nil
84
84
  beanstalk_job = @q.peek(:delayed)
85
85
  beanstalk_job.should_not be_nil
@@ -91,7 +91,7 @@ describe Controller::Beanstalk do
91
91
  # lets set the max retry delay so it should bury instead of delay
92
92
  redefine_constant Quebert::Controller::Beanstalk, :MAX_TIMEOUT_RETRY_DELAY, 1
93
93
  lambda{@q.reserve.perform}.should raise_exception(Quebert::Job::Timeout)
94
-
94
+
95
95
  @q.peek(:ready).should be_nil
96
96
  @q.peek(:delayed).should be_nil
97
97
  @q.peek(:buried).should_not be_nil
data/spec/job_spec.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Quebert::Job do
4
-
4
+
5
5
  before(:all) do
6
6
  Adder.backend = @q = Quebert::Backend::InProcess.new
7
7
  end
@@ -13,23 +13,26 @@ describe Quebert::Job do
13
13
  it "should perform!" do
14
14
  Adder.new(1,2,3).perform!.should eql(6)
15
15
  end
16
-
16
+
17
17
  it "should perform 0 arg jobs" do
18
18
  Adder.new.perform!.should eql(0)
19
19
  end
20
-
20
+
21
21
  it "should raise not implemented on base job" do
22
22
  lambda {
23
23
  Job.new.perform
24
24
  }.should raise_exception(Quebert::Job::NotImplemented)
25
25
  end
26
-
26
+
27
27
  it "should convert job to and from JSON" do
28
28
  args = [1,2,3]
29
- serialized = Adder.new(*args).to_json
29
+ job = Adder.new(*args)
30
+ job.queue = "foo"
31
+ serialized = job.to_json
30
32
  unserialized = Adder.from_json(serialized)
31
- unserialized.should be_instance_of(Adder)
33
+ unserialized.should be_a(Adder)
32
34
  unserialized.args.should eql(args)
35
+ unserialized.queue.should eql("foo")
33
36
  end
34
37
 
35
38
  it "should have default MEDIUM priority" do
@@ -54,20 +57,20 @@ describe Quebert::Job do
54
57
  ReleaseJob.new.perform
55
58
  }.should raise_exception(Job::Release)
56
59
  end
57
-
60
+
58
61
  it "should raise delete" do
59
62
  lambda{
60
63
  DeleteJob.new.perform
61
64
  }.should raise_exception(Job::Delete)
62
65
  end
63
-
66
+
64
67
  it "should raise bury" do
65
68
  lambda{
66
69
  BuryJob.new.perform
67
70
  }.should raise_exception(Job::Bury)
68
71
  end
69
72
  end
70
-
73
+
71
74
  context "job queue" do
72
75
  it "should enqueue" do
73
76
  lambda{
@@ -113,7 +116,7 @@ describe Quebert::Job do
113
116
  job = @q.reserve
114
117
  job.beanstalk_job.pri.should eql(1)
115
118
  job.beanstalk_job.delay.should eql(2)
116
- job.beanstalk_job.ttr.should eql(300 + Job::QUEBERT_TTR_BUFFER)
119
+ job.beanstalk_job.ttr.should eql(300 + Quebert::Backend::Beanstalk::TTR_BUFFER)
117
120
  end
118
121
 
119
122
  it "should enqueue and honor beanstalk options" do
@@ -121,7 +124,7 @@ describe Quebert::Job do
121
124
  job = @q.reserve
122
125
  job.beanstalk_job.pri.should eql(1)
123
126
  job.beanstalk_job.delay.should eql(2)
124
- job.beanstalk_job.ttr.should eql(300 + Job::QUEBERT_TTR_BUFFER)
127
+ job.beanstalk_job.ttr.should eql(300 + Quebert::Backend::Beanstalk::TTR_BUFFER)
125
128
  end
126
129
  end
127
130
 
@@ -132,7 +135,7 @@ describe Quebert::Job do
132
135
  job = @q.reserve
133
136
  job.beanstalk_job.pri.should eql(1)
134
137
  job.beanstalk_job.delay.should eql(2)
135
- job.beanstalk_job.ttr.should eql(300 + Job::QUEBERT_TTR_BUFFER)
138
+ job.beanstalk_job.ttr.should eql(300 + Quebert::Backend::Beanstalk::TTR_BUFFER)
136
139
  end
137
140
 
138
141
  it "should enqueue and honor beanstalk options" do
@@ -140,12 +143,12 @@ describe Quebert::Job do
140
143
  job = @q.reserve
141
144
  job.beanstalk_job.pri.should eql(1)
142
145
  job.beanstalk_job.delay.should eql(2)
143
- job.beanstalk_job.ttr.should eql(300 + Job::QUEBERT_TTR_BUFFER)
146
+ job.beanstalk_job.ttr.should eql(300 + Quebert::Backend::Beanstalk::TTR_BUFFER)
144
147
  end
145
148
  end
146
149
  end
147
150
  end
148
-
151
+
149
152
  context "Timeout" do
150
153
  it "should respect TTR option" do
151
154
  lambda {
data/spec/support/jobs.rb CHANGED
@@ -36,6 +36,6 @@ end
36
36
 
37
37
  class Exceptional < Quebert::Job
38
38
  def perform
39
- raise Exception
39
+ fail
40
40
  end
41
- end
41
+ end
data/spec/worker_spec.rb CHANGED
@@ -20,13 +20,13 @@ describe Worker do
20
20
 
21
21
  it "should default to Quebert.config.worker.exception_handler handler" do
22
22
  @q.put Exceptional.new
23
- Quebert.config.worker.exception_handler = Proc.new{|e, opts| e.should be_instance_of(Exception) }
23
+ Quebert.config.worker.exception_handler = Proc.new{|e, opts| e.should be_a(StandardError) }
24
24
  lambda{ @w.start }.should_not raise_exception
25
25
  end
26
26
 
27
27
  it "should intercept exceptions" do
28
28
  @q.put Exceptional.new
29
- @w.exception_handler = Proc.new{|e, opts| e.should be_instance_of(Exception) }
29
+ @w.exception_handler = Proc.new{|e, opts| e.should be_a(StandardError) }
30
30
  lambda{ @w.start }.should_not raise_exception
31
31
  end
32
32
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quebert
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.4
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brad Gessler
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-10-28 00:00:00.000000000 Z
13
+ date: 2015-05-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json
@@ -30,16 +30,16 @@ dependencies:
30
30
  name: beaneater
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
- - - ">="
33
+ - - "~>"
34
34
  - !ruby/object:Gem::Version
35
- version: '0'
35
+ version: '1.0'
36
36
  type: :runtime
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
- - - ">="
40
+ - - "~>"
41
41
  - !ruby/object:Gem::Version
42
- version: '0'
42
+ version: '1.0'
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: rspec
45
45
  requirement: !ruby/object:Gem::Requirement
@@ -64,8 +64,8 @@ extra_rdoc_files: []
64
64
  files:
65
65
  - ".document"
66
66
  - ".gitignore"
67
- - ".rbenv-version"
68
67
  - ".rspec"
68
+ - ".ruby-version"
69
69
  - ".travis.yml"
70
70
  - Gemfile
71
71
  - Guardfile
@@ -131,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
131
  version: '0'
132
132
  requirements: []
133
133
  rubyforge_project: quebert
134
- rubygems_version: 2.2.2
134
+ rubygems_version: 2.2.3
135
135
  signing_key:
136
136
  specification_version: 4
137
137
  summary: A worker queue framework built around beanstalkd
data/.rbenv-version DELETED
@@ -1 +0,0 @@
1
- 2.1.2