quebert 2.0.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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