ayl-beanstalk 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/Gemfile.lock +2 -2
- data/VERSION +1 -1
- data/ayl-beanstalk.gemspec +8 -5
- data/lib/ayl-beanstalk/exceptions.rb +28 -0
- data/lib/ayl-beanstalk/job.rb +73 -0
- data/lib/ayl-beanstalk/worker.rb +81 -50
- data/lib/ayl-beanstalk.rb +2 -0
- data/spec/engine_spec.rb +1 -1
- data/spec/job_spec.rb +167 -0
- data/spec/worker_spec.rb +150 -145
- metadata +8 -5
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -30,7 +30,7 @@ GEM
|
|
30
30
|
activesupport (3.1.2)
|
31
31
|
multi_json (~> 1.0)
|
32
32
|
arel (2.2.1)
|
33
|
-
ayl (0.
|
33
|
+
ayl (0.3.0)
|
34
34
|
beanstalk-client (1.1.1)
|
35
35
|
builder (3.0.0)
|
36
36
|
coderay (0.9.8)
|
@@ -114,7 +114,7 @@ PLATFORMS
|
|
114
114
|
ruby
|
115
115
|
|
116
116
|
DEPENDENCIES
|
117
|
-
ayl (>= 0.
|
117
|
+
ayl (>= 0.3.0)
|
118
118
|
beanstalk-client (>= 1.1.0)
|
119
119
|
bundler (>= 1.0.0)
|
120
120
|
daemons (>= 1.1.0)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/ayl-beanstalk.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "ayl-beanstalk"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["j0hnds@gmail.com"]
|
12
|
-
s.date = "2013-07
|
12
|
+
s.date = "2013-08-07"
|
13
13
|
s.description = "Ayl extension to provide beanstalk support."
|
14
14
|
s.email = "j0hnds@gmail.com"
|
15
15
|
s.executables = ["ayl_beanstalk", "ayl_worker", "ayl_worker_control"]
|
@@ -33,12 +33,15 @@ Gem::Specification.new do |s|
|
|
33
33
|
"lib/ayl-beanstalk.rb",
|
34
34
|
"lib/ayl-beanstalk/command_line.rb",
|
35
35
|
"lib/ayl-beanstalk/engine.rb",
|
36
|
+
"lib/ayl-beanstalk/exceptions.rb",
|
37
|
+
"lib/ayl-beanstalk/job.rb",
|
36
38
|
"lib/ayl-beanstalk/pool.rb",
|
37
39
|
"lib/ayl-beanstalk/worker.rb",
|
38
40
|
"spec/ayl_worker_control_spec.rb",
|
39
41
|
"spec/ayl_worker_spec.rb",
|
40
42
|
"spec/command_line_spec.rb",
|
41
43
|
"spec/engine_spec.rb",
|
44
|
+
"spec/job_spec.rb",
|
42
45
|
"spec/spec_helper.rb",
|
43
46
|
"spec/support/config/environment.rb",
|
44
47
|
"spec/support/exit_code_matchers.rb",
|
@@ -54,7 +57,7 @@ Gem::Specification.new do |s|
|
|
54
57
|
s.specification_version = 3
|
55
58
|
|
56
59
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
57
|
-
s.add_runtime_dependency(%q<ayl>, [">= 0.
|
60
|
+
s.add_runtime_dependency(%q<ayl>, [">= 0.3.0"])
|
58
61
|
s.add_runtime_dependency(%q<beanstalk-client>, [">= 1.1.0"])
|
59
62
|
s.add_runtime_dependency(%q<daemons>, [">= 1.1.0"])
|
60
63
|
s.add_development_dependency(%q<rspec>, [">= 2.3.0"])
|
@@ -63,7 +66,7 @@ Gem::Specification.new do |s|
|
|
63
66
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
64
67
|
s.add_development_dependency(%q<pry>, [">= 0"])
|
65
68
|
else
|
66
|
-
s.add_dependency(%q<ayl>, [">= 0.
|
69
|
+
s.add_dependency(%q<ayl>, [">= 0.3.0"])
|
67
70
|
s.add_dependency(%q<beanstalk-client>, [">= 1.1.0"])
|
68
71
|
s.add_dependency(%q<daemons>, [">= 1.1.0"])
|
69
72
|
s.add_dependency(%q<rspec>, [">= 2.3.0"])
|
@@ -73,7 +76,7 @@ Gem::Specification.new do |s|
|
|
73
76
|
s.add_dependency(%q<pry>, [">= 0"])
|
74
77
|
end
|
75
78
|
else
|
76
|
-
s.add_dependency(%q<ayl>, [">= 0.
|
79
|
+
s.add_dependency(%q<ayl>, [">= 0.3.0"])
|
77
80
|
s.add_dependency(%q<beanstalk-client>, [">= 1.1.0"])
|
78
81
|
s.add_dependency(%q<daemons>, [">= 1.1.0"])
|
79
82
|
s.add_dependency(%q<rspec>, [">= 2.3.0"])
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ayl
|
2
|
+
|
3
|
+
module Beanstalk
|
4
|
+
|
5
|
+
#
|
6
|
+
# Raise this exception to have ayl release the current job and place it in
|
7
|
+
# the delay slot for the tube.
|
8
|
+
#
|
9
|
+
class RequiresJobDecay < StandardError
|
10
|
+
|
11
|
+
attr_reader :delay
|
12
|
+
|
13
|
+
def initialize(delay=nil)
|
14
|
+
@delay = delay
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Raise this exception to have ayl release the current job and place it in
|
20
|
+
# the buried slot for the tube.
|
21
|
+
#
|
22
|
+
class RequiresJobBury < StandardError
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#
|
2
|
+
# I don't like to call this a monkey-patch. I simply want to extend the
|
3
|
+
# Beanstalk::Job to take care of some things that are best left to it.
|
4
|
+
# So let's just call it 'ayl Duck-punching'.
|
5
|
+
#
|
6
|
+
# I promise that no methods overridden here; only new methods added.
|
7
|
+
#
|
8
|
+
class Beanstalk::Job
|
9
|
+
include Ayl::Logging
|
10
|
+
|
11
|
+
#
|
12
|
+
# Return the body of the job as an Ayl::Message. If the message is improperly
|
13
|
+
# formatted, then nil is returned.
|
14
|
+
#
|
15
|
+
def ayl_message
|
16
|
+
@msg ||= Ayl::Message.from_hash(ybody)
|
17
|
+
rescue Ayl::UnrecoverableMessageException => ex
|
18
|
+
logger.error "Error extracting message from beanstalk job: #{ex}"
|
19
|
+
Ayl::Mailer.instance.deliver_message "Error extracting message from beanstalk job", ex
|
20
|
+
@msg = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Delete the job handling any exceptions that occur during the job deletion
|
25
|
+
# (like the job not existing).
|
26
|
+
#
|
27
|
+
def ayl_delete
|
28
|
+
delete
|
29
|
+
rescue Exception => ex
|
30
|
+
logger.error "Error deleting job: #{ex}\n#{ex.backtrace.join("\n")}"
|
31
|
+
Ayl::Mailer.instance.deliver_message("Error deleting job", ex)
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Decay the job handling any exceptions that occur during the job decay
|
36
|
+
# process.
|
37
|
+
#
|
38
|
+
def ayl_decay(delay=nil)
|
39
|
+
decay(*[ delay ].compact)
|
40
|
+
rescue Exception => ex
|
41
|
+
logger.error "Error decaying job: #{ex}\n#{ex.backtrace.join("\n")}"
|
42
|
+
Ayl::Mailer.instance.deliver_message("Error decaying job", ex)
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Bury the job handling any exceptions that occur during the burying
|
47
|
+
# process.
|
48
|
+
#
|
49
|
+
def ayl_bury
|
50
|
+
bury
|
51
|
+
rescue Exception => ex
|
52
|
+
logger.error "Error burying job: #{ex}\n#{ex.backtrace.join("\n")}"
|
53
|
+
Ayl::Mailer.instance.deliver_message("Error decaying job", ex)
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Handle the decay process by deleting the job if its age is more than
|
58
|
+
# 60 seconds, but delaying it if it is younger than 60 seconds. Obviously
|
59
|
+
# we want to handle any exceptions that occur here.
|
60
|
+
#
|
61
|
+
def handle_decay(ex)
|
62
|
+
logger.debug "Age of job: #{age}"
|
63
|
+
if age > 60
|
64
|
+
Ayl::Mailer.instance.deliver_message("Deleting decayed job; it just took too long.", ex)
|
65
|
+
logger.debug "Deleting job"
|
66
|
+
ayl_delete
|
67
|
+
else
|
68
|
+
logger.debug "Decaying job"
|
69
|
+
ayl_decay
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
data/lib/ayl-beanstalk/worker.rb
CHANGED
@@ -14,71 +14,102 @@ module Ayl
|
|
14
14
|
|
15
15
|
def process_messages
|
16
16
|
logger.debug "#{self.class.name} entering process_messages loop watching: #{Ayl::MessageOptions.default_queue_name}"
|
17
|
+
|
17
18
|
# Set the queue that we will be watching
|
18
19
|
pool.watch(Ayl::MessageOptions.default_queue_name)
|
19
|
-
|
20
|
-
|
21
|
-
job = nil
|
22
|
-
begin
|
23
|
-
job = pool.reserve
|
24
|
-
rescue Exception => ex
|
25
|
-
logger.error "#{self.class.name} Exception in process_messages: #{ex}\n#{ex.backtrace.join("\n")}"
|
26
|
-
Ayl::Mailer.instance.deliver_message("#{self.class.name} Exception in process_messages.", ex)
|
27
|
-
delete_job(job) unless job.nil?
|
28
|
-
next
|
29
|
-
end
|
30
|
-
break if job.nil?
|
31
|
-
msg = nil
|
20
|
+
|
21
|
+
reserve_job do | job |
|
32
22
|
begin
|
33
|
-
|
34
|
-
process_message(
|
35
|
-
|
36
|
-
|
37
|
-
logger.error "#{self.class.name} Unrecoverable exception in process_messages: #{ex}"
|
38
|
-
Ayl::Mailer.instance.deliver_message("#{self.class.name} Unrecoverable exception in process_messages", ex)
|
39
|
-
delete_job(job)
|
23
|
+
|
24
|
+
process_message(job.ayl_message) unless job.ayl_message.nil?
|
25
|
+
job.ayl_delete
|
26
|
+
|
40
27
|
rescue SystemExit
|
28
|
+
|
41
29
|
# This exception is raised when 'Kernel.exit' is called. In this case
|
42
30
|
# we want to make sure the job is deleted, then we simply re-raise
|
43
31
|
# the exception and we go bye-bye.
|
44
|
-
|
32
|
+
job.ayl_delete
|
45
33
|
raise
|
34
|
+
|
35
|
+
rescue Ayl::Beanstalk::RequiresJobDecay => ex
|
36
|
+
|
37
|
+
# The code in the message body has requested that we throw this job back
|
38
|
+
# in the queue with a delay.
|
39
|
+
job.ayl_decay(ex.delay)
|
40
|
+
|
41
|
+
rescue Ayl::Beanstalk::RequiresJobBury => ex
|
42
|
+
|
43
|
+
# The code in the message body has requested that we throw this job
|
44
|
+
# into the 'buried' state. This will allow a human to look the job
|
45
|
+
# over and determine if it can be processed
|
46
|
+
job.ayl_bury
|
47
|
+
|
46
48
|
rescue Exception => ex
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
else
|
51
|
-
delete_job(job)
|
52
|
-
Ayl::Mailer.instance.deliver_message("#{self.class.name} Exception in process_messages.", ex)
|
53
|
-
end
|
49
|
+
|
50
|
+
deal_with_unexpected_exception(job, ex)
|
51
|
+
|
54
52
|
end
|
55
|
-
end
|
56
|
-
end
|
57
53
|
|
58
|
-
|
59
|
-
|
60
|
-
if job.age > 60
|
61
|
-
Ayl::Mailer.instance.deliver_message("#{self.class.name} Deleting decayed job; it just took too long.", ex)
|
62
|
-
logger.debug "Deleting job"
|
63
|
-
delete_job(job)
|
64
|
-
else
|
65
|
-
logger.debug "Decaying job"
|
66
|
-
decay_job(job)
|
67
|
-
end
|
54
|
+
end # reserve_job...
|
55
|
+
|
68
56
|
end
|
69
57
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
58
|
+
private
|
59
|
+
|
60
|
+
#
|
61
|
+
# The main loop that gets job from the beanstalk queue to process. When a job is
|
62
|
+
# received it will be passed to the block for this method.
|
63
|
+
#
|
64
|
+
def reserve_job
|
65
|
+
while true
|
66
|
+
job = nil
|
67
|
+
|
68
|
+
begin
|
69
|
+
|
70
|
+
# Sit around and wait for a job to become available
|
71
|
+
job = pool.reserve
|
72
|
+
|
73
|
+
rescue Exception => ex
|
74
|
+
|
75
|
+
logger.error "Unexpected exception in reserve_job: #{ex}\n#{ex.backtrace.join("\n")}"
|
76
|
+
Ayl::Mailer.instance.deliver_message("Unexpected exception in process_messages.", ex)
|
77
|
+
job.ayl_delete unless job.nil?
|
78
|
+
|
79
|
+
# Notice that we are just breaking out of the loop here. Why?
|
80
|
+
# Think about the kinds of exceptions that occur here. They will be
|
81
|
+
# things like Beanstalk::OUT_OF_MEMORY errors, beanstalkd connection
|
82
|
+
# errors, etc. At this point, the worker is finished, kaput. Might as
|
83
|
+
# well report and die. That's exactly what will happen.
|
84
|
+
break
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
break if job.nil?
|
89
|
+
|
90
|
+
yield job
|
91
|
+
|
92
|
+
end
|
75
93
|
end
|
76
94
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
95
|
+
#
|
96
|
+
# Deals with the decision to decay or delete job when an unexpected
|
97
|
+
# exception is encountered.
|
98
|
+
#
|
99
|
+
def deal_with_unexpected_exception(job, ex)
|
100
|
+
logger.error "Unexpected exception in process_messages: #{ex}\n#{ex.backtrace.join("\n")}"
|
101
|
+
case job.ayl_message.options.failed_job_handler
|
102
|
+
when 'decay'
|
103
|
+
job.handle_decay(ex)
|
104
|
+
when 'delete'
|
105
|
+
job.ayl_delete
|
106
|
+
Ayl::Mailer.instance.deliver_message("Exception in process_messages. Job deleted.", ex)
|
107
|
+
when 'bury'
|
108
|
+
job.ayl_bury
|
109
|
+
Ayl::Mailer.instance.deliver_message("Exception in process_messages. Job buried", ex)
|
110
|
+
else
|
111
|
+
Ayl::Mailer.instance.deliver_message("Invalid failed job handler specified: #{job.ayl_message.options.failed_job_handler}", ex)
|
112
|
+
end
|
82
113
|
end
|
83
114
|
|
84
115
|
end
|
data/lib/ayl-beanstalk.rb
CHANGED
data/spec/engine_spec.rb
CHANGED
@@ -47,7 +47,7 @@ describe Ayl::Beanstalk::Engine do
|
|
47
47
|
it "should submit the specified message to beanstalk" do
|
48
48
|
mock_pool = mock("Beanstalk::Pool")
|
49
49
|
mock_pool.should_receive(:use).with("default")
|
50
|
-
mock_pool.should_receive(:yput).with( { :type => :ayl, :
|
50
|
+
mock_pool.should_receive(:yput).with( { :type => :ayl, :failed_job_handler => 'delete', :code => "23.to_s(2)" }, 512, 0, 120)
|
51
51
|
|
52
52
|
::Beanstalk::Pool.should_receive(:new).with([ "localhost:11300" ]).and_return(mock_pool)
|
53
53
|
|
data/spec/job_spec.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'beanstalk-client'
|
3
|
+
|
4
|
+
describe Beanstalk::Job do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@job = Beanstalk::Job.new(nil,nil,nil)
|
8
|
+
@job.stub_chain(:logger, :error)
|
9
|
+
@job.stub_chain(:logger, :debug)
|
10
|
+
end
|
11
|
+
|
12
|
+
context '#ayl_message' do
|
13
|
+
|
14
|
+
it "should return the message constructed from the body of the job" do
|
15
|
+
@job.stub(:ybody).and_return('the body')
|
16
|
+
msg = Ayl::Message.new(nil,nil,nil)
|
17
|
+
Ayl::Message.should_receive(:from_hash).with('the body').and_return(msg)
|
18
|
+
|
19
|
+
@job.ayl_message.should == msg
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should return the same message constructed from the body of the job on subsequent calls" do
|
23
|
+
@job.stub(:ybody).and_return('the body')
|
24
|
+
msg = Ayl::Message.new(nil,nil,nil)
|
25
|
+
Ayl::Message.should_receive(:from_hash).with('the body').and_return(msg)
|
26
|
+
|
27
|
+
@job.ayl_message.should == msg
|
28
|
+
|
29
|
+
@job.ayl_message.should == msg
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return nil and send an email if the message body was bad" do
|
33
|
+
@job.stub(:ybody).and_return('the body')
|
34
|
+
msg = Ayl::Message.new(nil,nil,nil)
|
35
|
+
ex = Ayl::UnrecoverableMessageException.new
|
36
|
+
Ayl::Message.should_receive(:from_hash).with('the body').and_raise(ex)
|
37
|
+
|
38
|
+
Ayl::Mailer.should_receive(:instance).and_return do
|
39
|
+
mock("Mailer").tap do | mock_mailer |
|
40
|
+
mock_mailer.should_receive(:deliver_message).with(anything(), ex)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@job.ayl_message.should == nil
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
context '#ayl_delete' do
|
50
|
+
|
51
|
+
it "should call the delete method on the job" do
|
52
|
+
@job.should_receive(:delete)
|
53
|
+
|
54
|
+
@job.ayl_delete
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should send an email if the delete method raises an exception" do
|
58
|
+
ex = Exception.new
|
59
|
+
@job.should_receive(:delete).and_raise(ex)
|
60
|
+
|
61
|
+
Ayl::Mailer.should_receive(:instance).and_return do
|
62
|
+
mock("Mailer").tap do | mock_mailer |
|
63
|
+
mock_mailer.should_receive(:deliver_message).with(anything(), ex)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
@job.ayl_delete
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
context '#ayl_decay' do
|
73
|
+
|
74
|
+
it "should call the decay with no arguments when nil delay is specified" do
|
75
|
+
@job.should_receive(:decay).with()
|
76
|
+
|
77
|
+
@job.ayl_decay(nil)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should call the decay with no arguments when no delay is specified" do
|
81
|
+
@job.should_receive(:decay).with()
|
82
|
+
|
83
|
+
@job.ayl_decay
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should call the decay with no arguments when no delay is specified" do
|
87
|
+
@job.should_receive(:decay).with(10)
|
88
|
+
|
89
|
+
@job.ayl_decay(10)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should send an email if the decay method raises an exception" do
|
93
|
+
ex = Exception.new
|
94
|
+
@job.should_receive(:decay).and_raise(ex)
|
95
|
+
|
96
|
+
Ayl::Mailer.should_receive(:instance).and_return do
|
97
|
+
mock("Mailer").tap do | mock_mailer |
|
98
|
+
mock_mailer.should_receive(:deliver_message).with(anything(), ex)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
@job.ayl_decay
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
context '#ayl_bury' do
|
108
|
+
|
109
|
+
it "should call the bury method on the job" do
|
110
|
+
@job.should_receive(:bury)
|
111
|
+
|
112
|
+
@job.ayl_bury
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should send an email if the bury method raises an exception" do
|
116
|
+
ex = Exception.new
|
117
|
+
@job.should_receive(:bury).and_raise(ex)
|
118
|
+
|
119
|
+
Ayl::Mailer.should_receive(:instance).and_return do
|
120
|
+
mock("Mailer").tap do | mock_mailer |
|
121
|
+
mock_mailer.should_receive(:deliver_message).with(anything(), ex)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
@job.ayl_bury
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
context '#handle_decay' do
|
131
|
+
|
132
|
+
it "should decay the job if the age of the job is less than 60 seconds" do
|
133
|
+
@job.should_receive(:age).at_least(1).times.and_return(2)
|
134
|
+
@job.should_receive(:ayl_decay)
|
135
|
+
|
136
|
+
ex = Exception.new
|
137
|
+
|
138
|
+
@job.handle_decay(ex)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should decay the job if the age of the job is exactly 60 seconds" do
|
142
|
+
@job.should_receive(:age).at_least(1).times.and_return(60)
|
143
|
+
@job.should_receive(:ayl_decay)
|
144
|
+
|
145
|
+
ex = Exception.new
|
146
|
+
|
147
|
+
@job.handle_decay(ex)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should delete the job and send an email if the job was older than 60 seconds" do
|
151
|
+
@job.should_receive(:age).at_least(1).times.and_return(61)
|
152
|
+
@job.should_receive(:ayl_delete)
|
153
|
+
|
154
|
+
ex = Exception.new
|
155
|
+
|
156
|
+
Ayl::Mailer.should_receive(:instance).and_return do
|
157
|
+
mock("Mailer").tap do | mock_mailer |
|
158
|
+
mock_mailer.should_receive(:deliver_message).with(anything(), ex)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
@job.handle_decay(ex)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
data/spec/worker_spec.rb
CHANGED
@@ -5,7 +5,7 @@ require 'active_record/errors'
|
|
5
5
|
|
6
6
|
describe Ayl::Beanstalk::Worker do
|
7
7
|
|
8
|
-
context
|
8
|
+
context '#reserve_job' do
|
9
9
|
|
10
10
|
before(:each) do
|
11
11
|
Kernel.stub(:puts)
|
@@ -13,217 +13,222 @@ describe Ayl::Beanstalk::Worker do
|
|
13
13
|
@worker.stub_chain(:logger, :info)
|
14
14
|
@worker.stub_chain(:logger, :error)
|
15
15
|
@worker.stub_chain(:logger, :debug)
|
16
|
-
Ayl::MessageOptions.default_queue_name = 'default'
|
17
|
-
end
|
18
|
-
|
19
|
-
it "should wait for a message to be received from beanstalk and process it" do
|
20
|
-
Ayl::MessageOptions.default_queue_name = 'the_queue_name'
|
21
|
-
mock_job = mock("Beanstalk::Job")
|
22
|
-
mock_job.should_receive(:delete)
|
23
|
-
mock_job.should_receive(:ybody).and_return({ :type => :ayl, :code => "23.to_s(2)" })
|
24
16
|
|
25
|
-
mock_pool = mock("Beanstalk::Pool")
|
26
|
-
mock_pool.should_receive(:watch).with("the_queue_name")
|
27
|
-
# Returns nil on the second call.
|
28
|
-
mock_pool.should_receive(:reserve).and_return(mock_job, nil)
|
17
|
+
@mock_pool = mock("Beanstalk::Pool")
|
29
18
|
|
30
|
-
|
31
|
-
|
32
|
-
Ayl::Message.stub(:from_hash).with({ :type => :ayl, :code => "23.to_s(2)" }).and_return(mock_message)
|
19
|
+
::Beanstalk::Pool.should_receive(:new).with([ "localhost:11300" ]).and_return(@mock_pool)
|
20
|
+
end
|
33
21
|
|
34
|
-
|
22
|
+
it "should loop until there are no more jobs from beanstalk" do
|
23
|
+
mock_job1 = mock("Beanstalk::Job")
|
24
|
+
mock_job2 = mock("Beanstalk::Job")
|
35
25
|
|
36
|
-
@
|
26
|
+
@mock_pool.should_receive(:reserve).and_return(mock_job1, mock_job2, nil)
|
27
|
+
|
28
|
+
index = 0
|
29
|
+
|
30
|
+
@worker.send(:reserve_job) do | job |
|
31
|
+
job.should == [ mock_job1, mock_job2 ][index]
|
32
|
+
index += 1
|
33
|
+
end
|
34
|
+
|
35
|
+
index.should == 2
|
37
36
|
|
38
37
|
end
|
39
38
|
|
40
|
-
it "should
|
41
|
-
|
42
|
-
mock_job.should_receive(:delete)
|
43
|
-
mock_job.should_receive(:ybody).and_return("a string")
|
44
|
-
mock_job.should_not_receive(:age)
|
45
|
-
|
46
|
-
mock_pool = mock("Beanstalk::Pool")
|
47
|
-
mock_pool.should_receive(:watch).with("default")
|
48
|
-
# Returns nil on the second call.
|
49
|
-
mock_pool.should_receive(:reserve).and_return(mock_job, nil)
|
39
|
+
it "should report any exception while waiting for a job" do
|
40
|
+
@mock_pool.should_receive(:reserve).and_raise('It blew')
|
50
41
|
|
51
|
-
|
42
|
+
mock_mailer = mock("Mailer")
|
43
|
+
mock_mailer.should_receive(:deliver_message).with(any_args())
|
52
44
|
|
53
|
-
::
|
45
|
+
Ayl::Mailer.should_receive(:instance).and_return(mock_mailer)
|
54
46
|
|
55
|
-
@worker.
|
47
|
+
@worker.send(:reserve_job) do | job |
|
48
|
+
end
|
56
49
|
end
|
57
50
|
|
58
|
-
|
59
|
-
mock_job = mock("Beanstalk::Job")
|
60
|
-
mock_job.should_receive(:delete)
|
61
|
-
mock_job.should_receive(:ybody).and_return({ :type => :junk, :code => "Dog" })
|
62
|
-
mock_job.should_not_receive(:age)
|
51
|
+
end
|
63
52
|
|
64
|
-
|
65
|
-
mock_pool.should_receive(:watch).with("default")
|
66
|
-
# Returns nil on the second call.
|
67
|
-
mock_pool.should_receive(:reserve).and_return(mock_job, nil)
|
53
|
+
context '#deal_with_unexpected_exception' do
|
68
54
|
|
69
|
-
|
55
|
+
before(:each) do
|
56
|
+
Kernel.stub(:puts)
|
57
|
+
@worker = Ayl::Beanstalk::Worker.new
|
58
|
+
@worker.stub_chain(:logger, :info)
|
59
|
+
@worker.stub_chain(:logger, :error)
|
60
|
+
@worker.stub_chain(:logger, :debug)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should call the job's handle_decay method if the message requires the job to decay" do
|
64
|
+
mock_job = mock("Beanstalk::Job")
|
65
|
+
mock_job.should_receive(:ayl_message).and_return do
|
66
|
+
mock("message").tap do | mock_message |
|
67
|
+
mock_message.should_receive(:options).and_return do
|
68
|
+
mock("options").tap do | mock_options |
|
69
|
+
mock_options.should_receive(:failed_job_handler).and_return('decay')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
70
74
|
|
71
|
-
|
75
|
+
mock_exception = mock("Exception")
|
76
|
+
mock_exception.should_receive(:backtrace).and_return([])
|
77
|
+
mock_job.should_receive(:handle_decay).with(mock_exception)
|
72
78
|
|
73
|
-
@worker.
|
79
|
+
@worker.send(:deal_with_unexpected_exception, mock_job, mock_exception)
|
74
80
|
end
|
75
81
|
|
76
|
-
it "should
|
82
|
+
it "should call the job's ayl_delete method if the message requires the job to be deleted" do
|
77
83
|
mock_job = mock("Beanstalk::Job")
|
78
|
-
mock_job.should_receive(:
|
79
|
-
|
80
|
-
|
84
|
+
mock_job.should_receive(:ayl_message).and_return do
|
85
|
+
mock("message").tap do | mock_message |
|
86
|
+
mock_message.should_receive(:options).and_return do
|
87
|
+
mock("options").tap do | mock_options |
|
88
|
+
mock_options.should_receive(:failed_job_handler).and_return('delete')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
81
93
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
mock_pool.should_receive(:reserve).and_return(mock_job, nil)
|
94
|
+
mock_exception = mock("Exception")
|
95
|
+
mock_exception.should_receive(:backtrace).and_return([])
|
96
|
+
mock_job.should_receive(:ayl_delete)
|
86
97
|
|
87
|
-
|
98
|
+
mock_mailer = mock("Mailer")
|
99
|
+
mock_mailer.should_receive(:deliver_message).with(any_args())
|
88
100
|
|
89
|
-
::
|
101
|
+
Ayl::Mailer.should_receive(:instance).and_return(mock_mailer)
|
90
102
|
|
91
|
-
@worker.
|
103
|
+
@worker.send(:deal_with_unexpected_exception, mock_job, mock_exception)
|
92
104
|
end
|
93
105
|
|
94
|
-
it "should
|
106
|
+
it "should call the job's ayl_bury method if the message requires the job to be buried" do
|
95
107
|
mock_job = mock("Beanstalk::Job")
|
96
|
-
mock_job.
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
mock_pool.should_receive(:reserve).and_return(mock_job, nil)
|
104
|
-
|
105
|
-
mock_message = mock("Ayl::Message")
|
106
|
-
mock_message.should_receive(:evaluate).and_raise(ActiveRecord::RecordNotFound)
|
107
|
-
mock_message.should_receive(:options).and_return do
|
108
|
-
mock("Options").tap do | mock_opts |
|
109
|
-
mock_opts.should_receive(:decay_failed_job).and_return(false)
|
108
|
+
mock_job.should_receive(:ayl_message).and_return do
|
109
|
+
mock("message").tap do | mock_message |
|
110
|
+
mock_message.should_receive(:options).and_return do
|
111
|
+
mock("options").tap do | mock_options |
|
112
|
+
mock_options.should_receive(:failed_job_handler).and_return('bury')
|
113
|
+
end
|
114
|
+
end
|
110
115
|
end
|
111
116
|
end
|
112
|
-
Ayl::Message.stub(:from_hash).with({ :type => :ayl, :code => "Dog" }).and_return(mock_message)
|
113
117
|
|
114
|
-
|
118
|
+
mock_exception = mock("Exception")
|
119
|
+
mock_exception.should_receive(:backtrace).and_return([])
|
120
|
+
mock_job.should_receive(:ayl_bury)
|
115
121
|
|
116
|
-
|
117
|
-
|
122
|
+
mock_mailer = mock("Mailer")
|
123
|
+
mock_mailer.should_receive(:deliver_message).with(any_args())
|
124
|
+
|
125
|
+
Ayl::Mailer.should_receive(:instance).and_return(mock_mailer)
|
126
|
+
|
127
|
+
@worker.send(:deal_with_unexpected_exception, mock_job, mock_exception)
|
118
128
|
end
|
119
129
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
130
|
+
end
|
131
|
+
|
132
|
+
context "Message Processing" do
|
133
|
+
|
134
|
+
before(:each) do
|
135
|
+
Kernel.stub(:puts)
|
136
|
+
@worker = Ayl::Beanstalk::Worker.new
|
137
|
+
@worker.stub_chain(:logger, :info)
|
138
|
+
@worker.stub_chain(:logger, :error)
|
139
|
+
@worker.stub_chain(:logger, :debug)
|
140
|
+
Ayl::MessageOptions.default_queue_name = 'the queue name'
|
125
141
|
|
126
|
-
mock_pool = mock("Beanstalk::Pool")
|
127
|
-
mock_pool.should_receive(:watch).with(
|
128
|
-
|
142
|
+
@mock_pool = mock("Beanstalk::Pool")
|
143
|
+
@mock_pool.should_receive(:watch).with('the queue name')
|
144
|
+
@worker.stub(:pool).and_return(@mock_pool)
|
145
|
+
end
|
129
146
|
|
147
|
+
it "should process a message received from beanstalk" do
|
130
148
|
mock_message = mock("Ayl::Message")
|
131
|
-
mock_message.should_receive(:evaluate).and_raise(ActiveRecord::RecordNotFound)
|
132
|
-
mock_message.should_receive(:options).and_return do
|
133
|
-
mock("Options").tap do | mock_opts |
|
134
|
-
mock_opts.should_receive(:decay_failed_job).and_return(true)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
Ayl::Message.stub(:from_hash).with({ :type => :ayl, :code => "Dog" }).and_return(mock_message)
|
138
149
|
|
139
|
-
|
150
|
+
mock_job = mock("Beanstalk::Job")
|
151
|
+
mock_job.should_receive(:ayl_message).at_least(1).times.and_return(mock_message)
|
152
|
+
mock_job.should_receive(:ayl_delete)
|
153
|
+
|
154
|
+
@worker.should_receive(:process_message).with(mock_message)
|
155
|
+
|
156
|
+
@worker.should_receive(:reserve_job).and_yield(mock_job)
|
140
157
|
|
141
158
|
@worker.process_messages
|
142
159
|
end
|
143
160
|
|
144
|
-
it "should
|
161
|
+
it "should do nothing if the received message is invalid (nil)" do
|
145
162
|
mock_job = mock("Beanstalk::Job")
|
146
|
-
mock_job.should_receive(:
|
147
|
-
mock_job.should_receive(:
|
148
|
-
mock_job.should_receive(:age).exactly(2).times.and_return(65)
|
163
|
+
mock_job.should_receive(:ayl_message).at_least(1).times.and_return(nil)
|
164
|
+
mock_job.should_receive(:ayl_delete)
|
149
165
|
|
150
|
-
|
151
|
-
mock_pool.should_receive(:watch).with("default")
|
152
|
-
mock_pool.should_receive(:reserve).and_return(mock_job, nil)
|
166
|
+
@worker.should_not_receive(:process_message)
|
153
167
|
|
154
|
-
|
155
|
-
mock_message.should_receive(:evaluate).and_raise(ActiveRecord::RecordNotFound)
|
156
|
-
mock_message.should_receive(:options).and_return do
|
157
|
-
mock("Options").tap do | mock_opts |
|
158
|
-
mock_opts.should_receive(:decay_failed_job).and_return(true)
|
159
|
-
end
|
160
|
-
end
|
161
|
-
Ayl::Message.stub(:from_hash).with({ :type => :ayl, :code => "Dog" }).and_return(mock_message)
|
162
|
-
|
163
|
-
::Beanstalk::Pool.should_receive(:new).with([ "localhost:11300" ]).and_return(mock_pool)
|
168
|
+
@worker.should_receive(:reserve_job).and_yield(mock_job)
|
164
169
|
|
165
170
|
@worker.process_messages
|
166
171
|
end
|
167
172
|
|
168
|
-
it "should
|
173
|
+
it "should delete the job and re-raise the exception on a SystemExit" do
|
174
|
+
mock_message = mock("Ayl::Message")
|
169
175
|
|
170
176
|
mock_job = mock("Beanstalk::Job")
|
171
|
-
mock_job.should_receive(:
|
172
|
-
mock_job.should_receive(:
|
173
|
-
|
177
|
+
mock_job.should_receive(:ayl_message).at_least(1).times.and_return(mock_message)
|
178
|
+
mock_job.should_receive(:ayl_delete)
|
179
|
+
|
180
|
+
@worker.should_receive(:process_message).with(mock_message).and_raise(SystemExit)
|
174
181
|
|
175
|
-
|
176
|
-
mock_pool.should_receive(:watch).with("default")
|
177
|
-
mock_pool.should_receive(:reserve).and_return(mock_job, nil)
|
182
|
+
@worker.should_receive(:reserve_job).and_yield(mock_job)
|
178
183
|
|
184
|
+
lambda { @worker.process_messages }.should raise_error(SystemExit)
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should decay the job if the message requires it" do
|
179
188
|
mock_message = mock("Ayl::Message")
|
180
|
-
mock_message.should_receive(:evaluate)
|
181
|
-
Ayl::Message.stub(:from_hash).with({ :type => :ayl, :code => "23.to_s(2)" }).and_return(mock_message)
|
182
189
|
|
183
|
-
|
190
|
+
mock_job = mock("Beanstalk::Job")
|
191
|
+
mock_job.should_receive(:ayl_message).at_least(1).times.and_return(mock_message)
|
192
|
+
mock_job.should_receive(:ayl_decay).with(20)
|
184
193
|
|
185
|
-
|
186
|
-
end
|
187
|
-
end
|
194
|
+
@worker.should_receive(:process_message).with(mock_message).and_raise(Ayl::Beanstalk::RequiresJobDecay.new(20))
|
188
195
|
|
189
|
-
|
196
|
+
@worker.should_receive(:reserve_job).and_yield(mock_job)
|
190
197
|
|
191
|
-
|
192
|
-
Kernel.stub(:puts)
|
193
|
-
@worker = Ayl::Beanstalk::Worker.new
|
194
|
-
@worker.stub_chain(:logger, :info)
|
195
|
-
@worker.stub_chain(:logger, :error)
|
196
|
-
@worker.stub_chain(:logger, :debug)
|
197
|
-
Ayl::MessageOptions.default_queue_name = 'default'
|
198
|
+
@worker.process_messages
|
198
199
|
end
|
199
200
|
|
200
|
-
it "should
|
201
|
-
|
202
|
-
mock_job.should_receive(:delete).and_raise("Delete failed for some reason")
|
201
|
+
it "should bury the job if the message requires it" do
|
202
|
+
mock_message = mock("Ayl::Message")
|
203
203
|
|
204
|
-
|
205
|
-
|
204
|
+
mock_job = mock("Beanstalk::Job")
|
205
|
+
mock_job.should_receive(:ayl_message).at_least(1).times.and_return(mock_message)
|
206
|
+
mock_job.should_receive(:ayl_bury)
|
206
207
|
|
207
|
-
|
208
|
+
@worker.should_receive(:process_message).with(mock_message).and_raise(Ayl::Beanstalk::RequiresJobBury)
|
208
209
|
|
209
|
-
|
210
|
+
@worker.should_receive(:reserve_job).and_yield(mock_job)
|
210
211
|
|
211
|
-
|
212
|
-
Kernel.stub(:puts)
|
213
|
-
@worker = Ayl::Beanstalk::Worker.new
|
214
|
-
@worker.stub_chain(:logger, :info)
|
215
|
-
@worker.stub_chain(:logger, :error)
|
216
|
-
@worker.stub_chain(:logger, :debug)
|
217
|
-
Ayl::MessageOptions.default_queue_name = 'default'
|
212
|
+
@worker.process_messages
|
218
213
|
end
|
219
214
|
|
220
|
-
it "should
|
221
|
-
|
222
|
-
|
215
|
+
it "should handle all other unexpected exceptions" do
|
216
|
+
mock_message = mock("Ayl::Message")
|
217
|
+
|
218
|
+
mock_job = mock("Beanstalk::Job")
|
219
|
+
mock_job.should_receive(:ayl_message).at_least(1).times.and_return(mock_message)
|
223
220
|
|
224
|
-
|
221
|
+
ex = Exception.new
|
222
|
+
|
223
|
+
@worker.should_receive(:process_message).with(mock_message).and_raise(ex)
|
224
|
+
|
225
|
+
@worker.should_receive(:reserve_job).and_yield(mock_job)
|
226
|
+
@worker.should_receive(:deal_with_unexpected_exception).with(mock_job, ex)
|
227
|
+
|
228
|
+
@worker.process_messages
|
225
229
|
end
|
226
230
|
|
227
231
|
end
|
228
232
|
|
233
|
+
|
229
234
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ayl-beanstalk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-07
|
12
|
+
date: 2013-08-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ayl
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 0.
|
21
|
+
version: 0.3.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ! '>='
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.
|
29
|
+
version: 0.3.0
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: beanstalk-client
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -165,12 +165,15 @@ files:
|
|
165
165
|
- lib/ayl-beanstalk.rb
|
166
166
|
- lib/ayl-beanstalk/command_line.rb
|
167
167
|
- lib/ayl-beanstalk/engine.rb
|
168
|
+
- lib/ayl-beanstalk/exceptions.rb
|
169
|
+
- lib/ayl-beanstalk/job.rb
|
168
170
|
- lib/ayl-beanstalk/pool.rb
|
169
171
|
- lib/ayl-beanstalk/worker.rb
|
170
172
|
- spec/ayl_worker_control_spec.rb
|
171
173
|
- spec/ayl_worker_spec.rb
|
172
174
|
- spec/command_line_spec.rb
|
173
175
|
- spec/engine_spec.rb
|
176
|
+
- spec/job_spec.rb
|
174
177
|
- spec/spec_helper.rb
|
175
178
|
- spec/support/config/environment.rb
|
176
179
|
- spec/support/exit_code_matchers.rb
|
@@ -190,7 +193,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
190
193
|
version: '0'
|
191
194
|
segments:
|
192
195
|
- 0
|
193
|
-
hash:
|
196
|
+
hash: -3806134538379917538
|
194
197
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
198
|
none: false
|
196
199
|
requirements:
|