stooge 0.1.0 → 0.1.1
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/HISTORY.md +1 -1
- data/README.md +2 -2
- data/lib/stooge/handler.rb +33 -13
- data/lib/stooge/version.rb +1 -1
- data/lib/stooge/worker.rb +1 -1
- data/lib/stooge.rb +15 -9
- data/spec/stooge_spec.rb +138 -0
- metadata +10 -10
data/HISTORY.md
CHANGED
data/README.md
CHANGED
@@ -44,10 +44,10 @@ end
|
|
44
44
|
Error handling
|
45
45
|
--------------
|
46
46
|
|
47
|
-
When an error is thrown in a job handler, the job is
|
47
|
+
When an error is thrown in a job handler, the job is re-queued to be done later and the Stooge process exits. If you define an error handler, however, the error handler is run and the job is removed from the queue.
|
48
48
|
|
49
49
|
```ruby
|
50
|
-
Stooge.error do |
|
50
|
+
Stooge.error do |exception, handler, payload, headers|
|
51
51
|
puts "got an error! #{e}"
|
52
52
|
end
|
53
53
|
```
|
data/lib/stooge/handler.rb
CHANGED
@@ -30,23 +30,43 @@ module Stooge
|
|
30
30
|
channel.queue(@queue_name, @queue_options) do |queue|
|
31
31
|
queue.subscribe(:ack => true) do |metadata, payload|
|
32
32
|
Stooge.log "recv: #{@queue_name}"
|
33
|
-
|
34
|
-
case metadata.content_type
|
35
|
-
when 'application/json'
|
36
|
-
args = MultiJson.decode(payload)
|
37
|
-
else
|
38
|
-
args = payload
|
39
|
-
end
|
40
|
-
@block.call(args, metadata.headers)
|
41
|
-
rescue Object => e
|
42
|
-
if Stooge.error_handler
|
43
|
-
Stooge.error_handler.call(e,self,payload,metadata)
|
44
|
-
end
|
45
|
-
end
|
33
|
+
run(payload, metadata.content_type, metadata.headers)
|
46
34
|
metadata.ack
|
47
35
|
end
|
48
36
|
end
|
49
37
|
end
|
50
38
|
|
39
|
+
#
|
40
|
+
# Call the handler block. This method will rescue any exceptions the
|
41
|
+
# handler block raises and pass them on to the global error handler.
|
42
|
+
#
|
43
|
+
# @param [String] payload the message payload
|
44
|
+
# @param [String] content_type the MIME content type of the message
|
45
|
+
# payload
|
46
|
+
# @param [Hash] headers the message headers
|
47
|
+
#
|
48
|
+
# @return [Object] the return value of the handler block
|
49
|
+
#
|
50
|
+
def run(payload, content_type, headers)
|
51
|
+
@block.call(
|
52
|
+
decode_payload(payload, content_type),
|
53
|
+
headers)
|
54
|
+
rescue Object => e
|
55
|
+
if Stooge.error_handler
|
56
|
+
Stooge.error_handler.call(e,self,payload,headers)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def decode_payload(payload, content_type)
|
63
|
+
case content_type
|
64
|
+
when 'application/json'
|
65
|
+
MultiJson.decode(payload)
|
66
|
+
else
|
67
|
+
payload.to_s
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
51
71
|
end
|
52
72
|
end
|
data/lib/stooge/version.rb
CHANGED
data/lib/stooge/worker.rb
CHANGED
data/lib/stooge.rb
CHANGED
@@ -14,7 +14,7 @@ module Stooge
|
|
14
14
|
@@connection = nil
|
15
15
|
@@channel = nil
|
16
16
|
@@handlers = []
|
17
|
-
@@error_handler = Proc.new do |exception, handler, payload,
|
17
|
+
@@error_handler = Proc.new do |exception, handler, payload, headers|
|
18
18
|
Stooge.log "#{handler.queue_name} failed: #{exception.inspect}"
|
19
19
|
raise exception
|
20
20
|
end
|
@@ -71,12 +71,10 @@ module Stooge
|
|
71
71
|
#
|
72
72
|
# If you don't raise an exception in the error handler the job will be
|
73
73
|
# acked with the broker and the broker will consider the job done and remove
|
74
|
-
# it from the queue.
|
75
|
-
#
|
76
|
-
# raise the error, like this:
|
74
|
+
# it from the queue. To make sure the job is not lost you can simply
|
75
|
+
# re-raise the same exception in your custom handler:
|
77
76
|
#
|
78
|
-
# Stooge.error do |exception, handler, payload,
|
79
|
-
# metadata.ack
|
77
|
+
# Stooge.error do |exception, handler, payload, headers|
|
80
78
|
# raise exception
|
81
79
|
# end
|
82
80
|
#
|
@@ -85,7 +83,7 @@ module Stooge
|
|
85
83
|
# @yieldparam [Stooge::Handler] handler the handler that failed.
|
86
84
|
# @yieldparam [Object] payload the message payload that was processed when
|
87
85
|
# the handler failed.
|
88
|
-
# @yieldparam [Hash]
|
86
|
+
# @yieldparam [Hash] headers the message headers
|
89
87
|
#
|
90
88
|
def error(&blk)
|
91
89
|
@@error_handler = blk
|
@@ -124,10 +122,18 @@ module Stooge
|
|
124
122
|
#
|
125
123
|
# @return [Boolean] true or false
|
126
124
|
#
|
127
|
-
def
|
125
|
+
def has_handlers?
|
128
126
|
@@handlers.empty? == false
|
129
127
|
end
|
130
128
|
|
129
|
+
#
|
130
|
+
# Remove all job handlers. Mainly useful in tests to create a clean slate
|
131
|
+
# for each test case.
|
132
|
+
#
|
133
|
+
def clear_handlers
|
134
|
+
@@handlers = []
|
135
|
+
end
|
136
|
+
|
131
137
|
#
|
132
138
|
# Execute a handler block without going through AMQP at all. This is a
|
133
139
|
# helper method for use in tests. It allows you to test the business logic
|
@@ -148,7 +154,7 @@ module Stooge
|
|
148
154
|
def run_handler(queue_name, data, headers = {})
|
149
155
|
@@handlers.each do |handler|
|
150
156
|
if handler.queue_name == queue_name
|
151
|
-
return handler.
|
157
|
+
return handler.run(MultiJson.encode(data), 'application/json', headers)
|
152
158
|
end
|
153
159
|
end
|
154
160
|
end
|
data/spec/stooge_spec.rb
CHANGED
@@ -6,4 +6,142 @@ require 'rspec'
|
|
6
6
|
|
7
7
|
describe Stooge do
|
8
8
|
|
9
|
+
before do
|
10
|
+
Stooge.amqp_url = 'amqp://guest:guest@localhost/'
|
11
|
+
Stooge.clear_handlers
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should use a default URL to the AMQP broker' do
|
15
|
+
Stooge.amqp_url.should == 'amqp://guest:guest@localhost/'
|
16
|
+
Stooge.send(:amqp_config).should == {
|
17
|
+
:vhost => '/',
|
18
|
+
:host => 'localhost',
|
19
|
+
:user => 'guest',
|
20
|
+
:port => 5672,
|
21
|
+
:pass => 'guest'
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should be possible to configure the URL to the AMQP broker' do
|
26
|
+
Stooge.amqp_url = 'amqp://john:doe@example.com:5000/vhost'
|
27
|
+
Stooge.amqp_url.should == 'amqp://john:doe@example.com:5000/vhost'
|
28
|
+
Stooge.send(:amqp_config).should == {
|
29
|
+
:vhost => '/vhost',
|
30
|
+
:host => 'example.com',
|
31
|
+
:user => 'john',
|
32
|
+
:port => 5000,
|
33
|
+
:pass => 'doe'
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'can add a job handler (ruby block) for a specified work queue' do
|
38
|
+
Stooge.job('test.work') {}
|
39
|
+
Stooge.should have_handlers
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'can enqueue jobs on a named queue with data and headers if inside an EM synchrony block' do
|
43
|
+
EM.synchrony do
|
44
|
+
# Here we stub the call to #publish on the default exchange and
|
45
|
+
# intercept the published message instead of sending the message to the
|
46
|
+
# broker and then testing if the broker receieved a new message, which
|
47
|
+
# is very hard to do reliably because of timing issues and because the
|
48
|
+
# message is silently dropped by the broker if no queue is bound to the
|
49
|
+
# exchange. It also give us the ability to easily test that the message
|
50
|
+
# is published with the correct options.
|
51
|
+
Stooge.with_channel do |channel|
|
52
|
+
channel.default_exchange.should_receive(:publish).with(
|
53
|
+
'{"test":"test"}',
|
54
|
+
{
|
55
|
+
:routing_key => 'test.work',
|
56
|
+
:mandatory => true,
|
57
|
+
:content_type => 'application/json',
|
58
|
+
:headers => { :foo => 'bar' }
|
59
|
+
}).and_yield
|
60
|
+
end
|
61
|
+
Stooge.enqueue('test.work', { :test => 'test' }, :foo => 'bar')
|
62
|
+
Stooge.stop!
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'can put a job on a queue and that job is the later picked up by a handler and processed' do
|
67
|
+
# This test is a bit tricky and might require some explanation. To
|
68
|
+
# actually test that this works we need to first create a job handler that
|
69
|
+
# sets a global ($work_performed) variable that we can later check to see
|
70
|
+
# wether the job has actually been performed. We then open an EM synchrony
|
71
|
+
# block in which we start the job handler (i.e. makes it listen to its
|
72
|
+
# queue for job messages). We then enqeue a job and later check if the job
|
73
|
+
# was performed as expected.
|
74
|
+
#
|
75
|
+
# To make all this work we need to deal with some nasty timing issues:
|
76
|
+
#
|
77
|
+
# 1. We need make sure the handler has had enough time to create a queue
|
78
|
+
# and binding before we can publish a message, or the message will
|
79
|
+
# just be silently dropped. This needs to happen in different EM tick.
|
80
|
+
# We therefor add an EM timer to publish the message 1 second later.
|
81
|
+
# The timer requires us to open up yet another synchrony block, because
|
82
|
+
# the timer block does not execute inside the fiber created by
|
83
|
+
# synchrony which it needs to do for Stooge#enqueue to work (because of
|
84
|
+
# the deferable magic happening behind the scenes).
|
85
|
+
#
|
86
|
+
# 2. When we have published the message we then need to wait a while for
|
87
|
+
# the job handler to pick it up and process it. We need to keep the
|
88
|
+
# EM loop alive for this to happen, so we simply sleep for 1 second
|
89
|
+
# before we shut the EM loop down and check if the job was processed.
|
90
|
+
#
|
91
|
+
# NOTE: This all depend on the communication with the broker taking less
|
92
|
+
# than 1 second, which should be more than fine in most cases. If this
|
93
|
+
# test stops working for some reason the first thing to try should be to
|
94
|
+
# increase the wait time and see if it works then.
|
95
|
+
#
|
96
|
+
$work_performed = false
|
97
|
+
Stooge.job('test.work') { $work_performed = true }
|
98
|
+
EM.synchrony do
|
99
|
+
Stooge.with_channel do |channel|
|
100
|
+
Stooge.start_handlers(channel)
|
101
|
+
end
|
102
|
+
EM.add_timer(1) do
|
103
|
+
EM.synchrony do
|
104
|
+
Stooge.enqueue('test.work', { :test => 'test' }, :foo => 'bar')
|
105
|
+
end
|
106
|
+
sleep(1)
|
107
|
+
Stooge.stop!
|
108
|
+
end
|
109
|
+
end
|
110
|
+
$work_performed.should == true
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should be possible to specify a custom global error handler' do
|
114
|
+
$error = false
|
115
|
+
old_handler = Stooge.error_handler
|
116
|
+
Stooge.error do |exception, handler, payload, metadata|
|
117
|
+
$error = true
|
118
|
+
end
|
119
|
+
Stooge.job('test.work') { raise }
|
120
|
+
Stooge.run_handler('test.work', '')
|
121
|
+
Stooge.error(&old_handler)
|
122
|
+
$error.should == true
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should be possible to specify a custom logger' do
|
126
|
+
$logged = false
|
127
|
+
old_logger = Stooge.logger
|
128
|
+
Stooge.logger do |msg|
|
129
|
+
$logged = true
|
130
|
+
end
|
131
|
+
Stooge.log('test')
|
132
|
+
Stooge.logger(&old_logger)
|
133
|
+
$logged.should == true
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should be possible to test a handler' do
|
137
|
+
$work_performed = false
|
138
|
+
Stooge.job('test.work') do |args, headers|
|
139
|
+
$work_performed = true
|
140
|
+
args.should == { 'test' => 'test' }
|
141
|
+
headers.should == { :foo => 'bar' }
|
142
|
+
end
|
143
|
+
Stooge.run_handler('test.work', { :test => 'test' }, :foo => 'bar')
|
144
|
+
$work_performed.should == true
|
145
|
+
end
|
146
|
+
|
9
147
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stooge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-03-
|
12
|
+
date: 2012-03-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: amqp
|
16
|
-
requirement: &
|
16
|
+
requirement: &70114352031440 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70114352031440
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: em-synchrony
|
27
|
-
requirement: &
|
27
|
+
requirement: &70114352031000 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70114352031000
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: multi_json
|
38
|
-
requirement: &
|
38
|
+
requirement: &70114352030540 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70114352030540
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rspec
|
49
|
-
requirement: &
|
49
|
+
requirement: &70114352030080 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70114352030080
|
58
58
|
description: Super advanced job queue over AMQP
|
59
59
|
email:
|
60
60
|
- niklas@sutajio.se
|