sneakers 0.0.1 → 0.0.2
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.
- data/.gitignore +6 -17
- data/Gemfile +0 -1
- data/Gemfile.lock +152 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +2 -2
- data/README.md +140 -9
- data/Rakefile +9 -0
- data/bin/sneakers +5 -0
- data/examples/benchmark_worker.rb +21 -0
- data/examples/metrics_worker.rb +28 -0
- data/examples/profiling_worker.rb +55 -0
- data/examples/sneakers.conf.rb.example +10 -0
- data/examples/title_scraper.rb +20 -0
- data/examples/workflow_worker.rb +24 -0
- data/lib/sneakers.rb +80 -1
- data/lib/sneakers/cli.rb +107 -0
- data/lib/sneakers/concerns/logging.rb +34 -0
- data/lib/sneakers/concerns/metrics.rb +34 -0
- data/lib/sneakers/handlers/oneshot.rb +25 -0
- data/lib/sneakers/metrics/logging_metrics.rb +16 -0
- data/lib/sneakers/metrics/null_metrics.rb +13 -0
- data/lib/sneakers/metrics/statsd_metrics.rb +21 -0
- data/lib/sneakers/publisher.rb +35 -0
- data/lib/sneakers/queue.rb +42 -0
- data/lib/sneakers/runner.rb +20 -0
- data/lib/sneakers/runner_config.rb +55 -0
- data/lib/sneakers/support/production_formatter.rb +11 -0
- data/lib/sneakers/support/queue_name.rb +14 -0
- data/lib/sneakers/support/utils.rb +18 -0
- data/lib/sneakers/tasks.rb +34 -0
- data/lib/sneakers/version.rb +1 -1
- data/lib/sneakers/worker.rb +120 -0
- data/lib/sneakers/workergroup.rb +47 -0
- data/sneakers.gemspec +26 -16
- data/spec/fixtures/require_worker.rb +17 -0
- data/spec/sneakers/cli_spec.rb +53 -0
- data/spec/sneakers/concerns/logging.rb +39 -0
- data/spec/sneakers/concerns/metrics.rb +38 -0
- data/spec/sneakers/publisher_spec.rb +37 -0
- data/spec/sneakers/queue_spec.rb +42 -0
- data/spec/sneakers/worker_spec.rb +348 -0
- data/spec/spec_helper.rb +10 -0
- metadata +216 -14
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sneakers'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
|
6
|
+
class Foobar
|
7
|
+
include Sneakers::Concerns::Logging
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Sneakers::Concerns::Logging do
|
11
|
+
describe ".configure" do
|
12
|
+
before do
|
13
|
+
Foobar.logger = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should configure a default logger when included" do
|
17
|
+
Foobar.logger.must_be_nil
|
18
|
+
Foobar.configure_logger
|
19
|
+
Foobar.logger.wont_be_nil
|
20
|
+
Foobar.logger.formatter.must_equal Sneakers::Concerns::Logging::ProductionFormatter
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should supply accessible instance logger" do
|
24
|
+
Foobar.logger.must_be_nil
|
25
|
+
Foobar.configure_logger
|
26
|
+
f = Foobar.new
|
27
|
+
f.logger.must_equal Foobar.logger
|
28
|
+
f.logger.wont_be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should configure a given logger when specified" do
|
32
|
+
Foobar.logger.must_be_nil
|
33
|
+
log = Logger.new(STDOUT)
|
34
|
+
Foobar.configure_logger(log)
|
35
|
+
Foobar.logger.must_equal log
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sneakers'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
|
6
|
+
class Foometrics
|
7
|
+
include Sneakers::Concerns::Metrics
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Sneakers::Concerns::Metrics do
|
11
|
+
describe ".configure" do
|
12
|
+
before do
|
13
|
+
Foometrics.metrics = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should configure a default logger when included" do
|
17
|
+
Foometrics.metrics.must_be_nil
|
18
|
+
Foometrics.configure_metrics
|
19
|
+
Foometrics.metrics.wont_be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should supply accessible instance logger" do
|
23
|
+
Foometrics.metrics.must_be_nil
|
24
|
+
Foometrics.configure_metrics
|
25
|
+
f = Foometrics.new
|
26
|
+
f.metrics.must_equal Foometrics.metrics
|
27
|
+
f.metrics.wont_be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should configure a given metrics when specified" do
|
31
|
+
Foometrics.metrics.must_be_nil
|
32
|
+
o = Object.new
|
33
|
+
Foometrics.configure_metrics(o)
|
34
|
+
Foometrics.metrics.must_equal o
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sneakers'
|
3
|
+
|
4
|
+
|
5
|
+
describe Sneakers::Publisher do
|
6
|
+
before do
|
7
|
+
Sneakers.configure(:env => 'test')
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#publish" do
|
11
|
+
it "should publish a message to an exchange" do
|
12
|
+
xchg = Object.new
|
13
|
+
mock(xchg).publish("test msg", :routing_key => "downloads_test")
|
14
|
+
|
15
|
+
p = Sneakers::Publisher.new
|
16
|
+
p.exchange = xchg
|
17
|
+
|
18
|
+
mock(p).ensure_connection!{}
|
19
|
+
p.publish("test msg", :to_queue => 'downloads')
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should not reconnect if already connected" do
|
23
|
+
xchg = Object.new
|
24
|
+
mock(xchg).publish("test msg", :routing_key => "downloads_test")
|
25
|
+
|
26
|
+
p = Sneakers::Publisher.new
|
27
|
+
p.exchange = xchg
|
28
|
+
mock(p).connected?{true}
|
29
|
+
mock(p).ensure_connection!.times(0)
|
30
|
+
|
31
|
+
p.publish("test msg", :to_queue => 'downloads')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sneakers'
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
describe Sneakers::Queue do
|
7
|
+
before do
|
8
|
+
Sneakers.configure(:env => 'test')
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#subscribe" do
|
12
|
+
it "should setup a bunny queue according to configuration values" do
|
13
|
+
q = Sneakers::Queue.new("downloads",
|
14
|
+
:prefetch => 25,
|
15
|
+
:durable => true,
|
16
|
+
:ack => true,
|
17
|
+
:heartbeat_interval => 2,
|
18
|
+
:exchange => "sneakers"
|
19
|
+
)
|
20
|
+
mkbunny = Object.new
|
21
|
+
mkchan = Object.new
|
22
|
+
mkex = Object.new
|
23
|
+
mkqueue = Object.new
|
24
|
+
|
25
|
+
mock(mkbunny).start {}
|
26
|
+
mock(mkbunny).create_channel{ mkchan }
|
27
|
+
mock(Bunny).new(:heartbeat_interval => 2){ mkbunny }
|
28
|
+
|
29
|
+
mock(mkchan).prefetch(25)
|
30
|
+
mock(mkchan).exchange("sneakers", :type => :direct, :durable => true){ mkex }
|
31
|
+
mock(mkchan).queue("downloads", :durable => true){ mkqueue }
|
32
|
+
|
33
|
+
mock(mkqueue).bind(mkex, :routing_key => "downloads")
|
34
|
+
mock(mkqueue).subscribe(:block => false, :ack => true)
|
35
|
+
|
36
|
+
q.subscribe(Object.new)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,348 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sneakers'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
|
6
|
+
class DummyWorker
|
7
|
+
include Sneakers::Worker
|
8
|
+
from_queue 'downloads',
|
9
|
+
:env => 'test',
|
10
|
+
:durable => false,
|
11
|
+
:ack => false,
|
12
|
+
:threads => 50,
|
13
|
+
:prefetch => 40,
|
14
|
+
:timeout_job_after => 1,
|
15
|
+
:exchange => 'dummy',
|
16
|
+
:heartbeat_interval => 5
|
17
|
+
|
18
|
+
def work(msg)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class DefaultsWorker
|
23
|
+
include Sneakers::Worker
|
24
|
+
from_queue 'defaults'
|
25
|
+
|
26
|
+
def work(msg)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class TimeoutWorker
|
31
|
+
include Sneakers::Worker
|
32
|
+
from_queue 'defaults',
|
33
|
+
:timeout_job_after => 0.5,
|
34
|
+
:ack => true
|
35
|
+
|
36
|
+
def work(msg)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class AcksWorker
|
41
|
+
include Sneakers::Worker
|
42
|
+
from_queue 'defaults',
|
43
|
+
:ack => true
|
44
|
+
|
45
|
+
def work(msg)
|
46
|
+
if msg == :ack
|
47
|
+
ack!
|
48
|
+
elsif msg == :nack
|
49
|
+
nack!
|
50
|
+
elsif msg == :reject
|
51
|
+
reject!
|
52
|
+
else
|
53
|
+
msg
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class PublishingWorker
|
59
|
+
include Sneakers::Worker
|
60
|
+
from_queue 'defaults',
|
61
|
+
:ack => false,
|
62
|
+
:exchange => 'foochange'
|
63
|
+
|
64
|
+
def work(msg)
|
65
|
+
publish msg, :to_queue => 'target'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
class LoggingWorker
|
72
|
+
include Sneakers::Worker
|
73
|
+
from_queue 'defaults',
|
74
|
+
:ack => false
|
75
|
+
|
76
|
+
def work(msg)
|
77
|
+
logger.info "hello"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
class MetricsWorker
|
83
|
+
include Sneakers::Worker
|
84
|
+
from_queue 'defaults',
|
85
|
+
:ack => true,
|
86
|
+
:timeout_job_after => 0.5
|
87
|
+
|
88
|
+
def work(msg)
|
89
|
+
metrics.increment "foobar"
|
90
|
+
msg
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
class TestPool
|
97
|
+
def process(*args,&block)
|
98
|
+
block.call
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class TestHandler
|
103
|
+
def acknowledge(tag); end
|
104
|
+
def reject(tag); end
|
105
|
+
def error(tag, err); end
|
106
|
+
def timeout(tag); end
|
107
|
+
end
|
108
|
+
|
109
|
+
def with_test_queuefactory(ctx, ack=true, msg=nil, nowork=false)
|
110
|
+
qf = Object.new
|
111
|
+
q = Object.new
|
112
|
+
s = Object.new
|
113
|
+
hdr = Object.new
|
114
|
+
mock(qf).build_queue(anything, anything, anything) { q }
|
115
|
+
mock(q).subscribe(anything){ s }
|
116
|
+
|
117
|
+
mock(s).each(anything) { |h,b| b.call(hdr, msg) unless nowork }
|
118
|
+
mock(hdr).ack{true} if !nowork && ack
|
119
|
+
mock(hdr).reject{true} if !nowork && !ack
|
120
|
+
|
121
|
+
mock(ctx).queue_factory { qf } # should return our own
|
122
|
+
end
|
123
|
+
|
124
|
+
describe Sneakers::Worker do
|
125
|
+
before do
|
126
|
+
@queue = Object.new
|
127
|
+
stub(@queue).name { 'test-queue' }
|
128
|
+
stub(@queue).opts { {} }
|
129
|
+
|
130
|
+
Sneakers.configure(:env => 'test')
|
131
|
+
Sneakers::Worker.configure_logger(Logger.new('/dev/null'))
|
132
|
+
Sneakers::Worker.configure_metrics
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#initialize" do
|
136
|
+
describe "builds an internal queue" do
|
137
|
+
before do
|
138
|
+
@dummy_q = DummyWorker.new.queue
|
139
|
+
@defaults_q = DefaultsWorker.new.queue
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should build a queue with correct configuration given defaults" do
|
143
|
+
@defaults_q.name.must_equal('defaults_test')
|
144
|
+
@defaults_q.opts.must_equal(
|
145
|
+
:durable => true,
|
146
|
+
:ack => true,
|
147
|
+
:prefetch => 10,
|
148
|
+
:heartbeat_interval => 2,
|
149
|
+
:exchange => 'sneakers'
|
150
|
+
)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should build a queue with given configuration" do
|
154
|
+
@dummy_q.name.must_equal('downloads_test')
|
155
|
+
@dummy_q.opts.must_equal(
|
156
|
+
:durable => false,
|
157
|
+
:ack => false,
|
158
|
+
:prefetch => 40,
|
159
|
+
:heartbeat_interval => 5,
|
160
|
+
:exchange => 'dummy'
|
161
|
+
)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "initializes worker" do
|
166
|
+
it "should generate a worker id" do
|
167
|
+
DummyWorker.new.id.must_match(/^worker-/)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
describe "#run" do
|
174
|
+
it "should subscribe on internal queue" do
|
175
|
+
q = Object.new
|
176
|
+
w = DummyWorker.new(q)
|
177
|
+
mock(q).subscribe(w).once #XXX once?
|
178
|
+
stub(q).name{ "test" }
|
179
|
+
stub(q).opts { nil }
|
180
|
+
w.run
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe "#stop" do
|
185
|
+
it "should unsubscribe from internal queue" do
|
186
|
+
q = Object.new
|
187
|
+
mock(q).unsubscribe.once #XXX once?
|
188
|
+
stub(q).name { 'test-queue' }
|
189
|
+
stub(q).opts {nil}
|
190
|
+
w = DummyWorker.new(q)
|
191
|
+
w.stop
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
describe "#do_work" do
|
197
|
+
it "should perform worker's work" do
|
198
|
+
w = DummyWorker.new(@queue, TestPool.new)
|
199
|
+
mock(w).work("msg").once
|
200
|
+
w.do_work(nil, nil, "msg", nil)
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should catch runtime exceptions from a bad work" do
|
204
|
+
w = AcksWorker.new(@queue, TestPool.new)
|
205
|
+
mock(w).work("msg").once{ raise "foo" }
|
206
|
+
handler = Object.new
|
207
|
+
mock(handler).error("tag", anything)
|
208
|
+
header = Object.new
|
209
|
+
stub(header).delivery_tag { "tag" }
|
210
|
+
w.do_work(header, nil, "msg", handler)
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should timeout if a work takes too long" do
|
214
|
+
w = TimeoutWorker.new(@queue, TestPool.new)
|
215
|
+
stub(w).work("msg"){ sleep 10 }
|
216
|
+
|
217
|
+
handler = Object.new
|
218
|
+
mock(handler).timeout("tag")
|
219
|
+
|
220
|
+
header = Object.new
|
221
|
+
stub(header).delivery_tag { "tag" }
|
222
|
+
|
223
|
+
w.do_work(header, nil, "msg", handler)
|
224
|
+
end
|
225
|
+
|
226
|
+
describe "with ack" do
|
227
|
+
before do
|
228
|
+
@header = Object.new
|
229
|
+
stub(@header).delivery_tag{ "tag" }
|
230
|
+
|
231
|
+
@worker = AcksWorker.new(@queue, TestPool.new)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "should work and handle acks" do
|
235
|
+
handler = Object.new
|
236
|
+
mock(handler).acknowledge("tag")
|
237
|
+
|
238
|
+
@worker.do_work(@header, nil, :ack, handler)
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should work and handle nacks" do
|
242
|
+
handler = Object.new
|
243
|
+
mock(handler).reject("tag")
|
244
|
+
|
245
|
+
@worker.do_work(@header, nil, :nack, handler)
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should work and handle rejects" do
|
249
|
+
handler = Object.new
|
250
|
+
mock(handler).reject("tag")
|
251
|
+
|
252
|
+
@worker.do_work(@header, nil, :reject, handler)
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should work and handle user-land timeouts" do
|
256
|
+
handler = Object.new
|
257
|
+
mock(handler).timeout("tag")
|
258
|
+
|
259
|
+
@worker.do_work(@header, nil, :timeout, handler)
|
260
|
+
end
|
261
|
+
|
262
|
+
it "should work and handle user-land error" do
|
263
|
+
handler = Object.new
|
264
|
+
mock(handler).error("tag",anything)
|
265
|
+
|
266
|
+
@worker.do_work(@header, nil, :error, handler)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe "without ack" do
|
271
|
+
it "should work and not care about acking if not ack" do
|
272
|
+
handler = Object.new
|
273
|
+
mock(handler).reject(anything).never
|
274
|
+
mock(handler).acknowledge(anything).never
|
275
|
+
|
276
|
+
w = DummyWorker.new(@queue, TestPool.new)
|
277
|
+
w.do_work(nil, nil, 'msg', handler)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
describe 'publish' do
|
284
|
+
it 'should be able to publish a message from working context' do
|
285
|
+
w = PublishingWorker.new(@queue, TestPool.new)
|
286
|
+
mock(w).publish('msg', :to_queue => 'target').once
|
287
|
+
w.do_work(nil, nil, 'msg', nil)
|
288
|
+
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
describe 'Logging' do
|
294
|
+
it 'should be able to use the logging facilities' do
|
295
|
+
log = Logger.new('/dev/null')
|
296
|
+
mock(log).debug(anything).once
|
297
|
+
mock(log).info("hello").once
|
298
|
+
Sneakers::Worker.configure_logger(log)
|
299
|
+
|
300
|
+
w = LoggingWorker.new(@queue, TestPool.new)
|
301
|
+
w.do_work(nil,nil,'msg',nil)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
describe 'Metrics' do
|
307
|
+
before do
|
308
|
+
@handler = Object.new
|
309
|
+
stub(@handler).acknowledge("tag")
|
310
|
+
stub(@handler).reject("tag")
|
311
|
+
stub(@handler).timeout("tag")
|
312
|
+
stub(@handler).error("tag", anything)
|
313
|
+
|
314
|
+
@header = Object.new
|
315
|
+
stub(@header).delivery_tag { "tag" }
|
316
|
+
|
317
|
+
@w = MetricsWorker.new(@queue, TestPool.new)
|
318
|
+
mock(@w.metrics).increment("work.MetricsWorker.started").once
|
319
|
+
mock(@w.metrics).increment("work.MetricsWorker.ended").once
|
320
|
+
mock(@w.metrics).timing("work.MetricsWorker.time").yields.once
|
321
|
+
end
|
322
|
+
|
323
|
+
it 'should be able to meter acks' do
|
324
|
+
mock(@w.metrics).increment("foobar").once
|
325
|
+
mock(@w.metrics).increment("work.MetricsWorker.handled.ack").once
|
326
|
+
@w.do_work(@header, nil, :ack, @handler)
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'should be able to meter rejects' do
|
330
|
+
mock(@w.metrics).increment("foobar").once
|
331
|
+
mock(@w.metrics).increment("work.MetricsWorker.handled.reject").once
|
332
|
+
@w.do_work(@header, nil, nil, @handler)
|
333
|
+
end
|
334
|
+
|
335
|
+
it 'should be able to meter errors' do
|
336
|
+
mock(@w.metrics).increment("work.MetricsWorker.handled.error").once
|
337
|
+
mock(@w).work('msg'){ raise :error }
|
338
|
+
@w.do_work(@header, nil, 'msg', @handler)
|
339
|
+
end
|
340
|
+
|
341
|
+
it 'should be able to meter timeouts' do
|
342
|
+
mock(@w.metrics).increment("work.MetricsWorker.handled.timeout").once
|
343
|
+
mock(@w).work('msg'){ sleep 10 }
|
344
|
+
@w.do_work(@header, nil, 'msg', @handler)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
end
|