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 +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
|