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 +4 -4
- data/.ruby-version +1 -0
- data/README.md +138 -55
- data/lib/quebert/backend/beanstalk.rb +56 -27
- data/lib/quebert/command_line_runner.rb +5 -2
- data/lib/quebert/controller/beanstalk.rb +37 -33
- data/lib/quebert/job.rb +9 -10
- data/lib/quebert/serializer.rb +10 -9
- data/lib/quebert/version.rb +1 -1
- data/lib/quebert/worker.rb +7 -3
- data/quebert.gemspec +3 -3
- data/spec/backend_spec.rb +12 -0
- data/spec/configuration_spec.rb +9 -8
- data/spec/consumer_spec.rb +15 -15
- data/spec/job_spec.rb +17 -14
- data/spec/support/jobs.rb +2 -2
- data/spec/worker_spec.rb +2 -2
- metadata +8 -8
- data/.rbenv-version +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2bda9cb5709ea354e0ccb9822af9866fc9074795
|
4
|
+
data.tar.gz: 45923c3de44f7d15397732d30448931540847f50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
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
|
-
|
54
|
+
```ruby
|
55
|
+
WackyMathWizard.new(4, 5, 6).enqueue
|
56
|
+
```
|
51
57
|
|
52
58
|
Then perform the jobs!
|
53
59
|
|
54
|
-
|
55
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
91
|
+
```ruby
|
92
|
+
Quebert.config.after_job do
|
93
|
+
ActiveRecord::Base.clear_active_connections!
|
94
|
+
end
|
65
95
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
120
|
-
|
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
|
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
|
-
|
9
|
-
|
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
|
13
|
-
|
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
|
-
|
31
|
+
watch_tubes
|
32
|
+
beanstalkd_tubes.reserve(timeout)
|
23
33
|
end
|
24
34
|
|
25
35
|
def reserve(timeout=nil)
|
26
|
-
Controller::Beanstalk.new
|
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
|
-
|
48
|
+
kick
|
43
49
|
reserve_without_controller.delete
|
44
50
|
end
|
45
51
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
54
|
-
|
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
|
58
|
-
@
|
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
|
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, :
|
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
|
16
|
-
@beanstalk_job
|
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
|
-
|
20
|
+
job_log "Deleted on initialization", :error
|
21
21
|
rescue Job::Release
|
22
|
-
beanstalk_job.release
|
23
|
-
|
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
|
-
|
27
|
-
rescue
|
26
|
+
job_log "Buried on initialization", :error
|
27
|
+
rescue => e
|
28
28
|
beanstalk_job.bury
|
29
|
-
|
29
|
+
job_log "Error caught on initialization. #{e.inspect}", :error
|
30
30
|
raise
|
31
31
|
end
|
32
32
|
|
33
33
|
def perform
|
34
|
-
|
35
|
-
|
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
|
-
|
43
|
+
job_log "Completed in #{(time*1000*1000).to_i/1000.to_f} ms\n"
|
44
44
|
result
|
45
45
|
rescue Job::Delete
|
46
|
-
|
46
|
+
job_log "Deleting job", :error
|
47
47
|
beanstalk_job.delete
|
48
|
-
|
48
|
+
job_log "Job deleted", :error
|
49
49
|
rescue Job::Release
|
50
|
-
|
51
|
-
beanstalk_job.release :pri =>
|
52
|
-
|
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
|
-
|
54
|
+
job_log "Burrying job", :error
|
55
55
|
beanstalk_job.bury
|
56
|
-
|
56
|
+
job_log "Job burried", :error
|
57
57
|
rescue Job::Timeout => e
|
58
|
-
|
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
|
64
|
-
|
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
|
67
|
-
|
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
|
-
|
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
|
-
|
78
|
+
job_log "Max retry delay exceeded. Burrying job"
|
79
79
|
beanstalk_job.bury
|
80
|
-
|
80
|
+
job_log "Job burried"
|
81
81
|
else
|
82
|
-
|
83
|
-
beanstalk_job.release :pri =>
|
84
|
-
|
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
|
-
|
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
|
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
|
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(
|
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(
|
70
|
-
|
71
|
-
|
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
|
data/lib/quebert/serializer.rb
CHANGED
@@ -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
|
data/lib/quebert/version.rb
CHANGED
data/lib/quebert/worker.rb
CHANGED
@@ -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
|
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
|
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
|
data/spec/configuration_spec.rb
CHANGED
@@ -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(
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
data/spec/consumer_spec.rb
CHANGED
@@ -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 =>
|
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
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
-
|
29
|
+
job = Adder.new(*args)
|
30
|
+
job.queue = "foo"
|
31
|
+
serialized = job.to_json
|
30
32
|
unserialized = Adder.from_json(serialized)
|
31
|
-
unserialized.should
|
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 +
|
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 +
|
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 +
|
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 +
|
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
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
|
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
|
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:
|
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:
|
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.
|
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
|