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 CHANGED
@@ -2,7 +2,7 @@ source "http://rubygems.org"
2
2
  # Add dependencies required to use your gem here.
3
3
  # Example:
4
4
  # gem "activesupport", ">= 2.3.5"
5
- gem 'ayl', ">= 0.2.1"
5
+ gem 'ayl', ">= 0.3.0"
6
6
  gem 'beanstalk-client', ">= 1.1.0"
7
7
  gem 'daemons', ">= 1.1.0"
8
8
 
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.2.1)
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.2.1)
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.2.0
1
+ 0.3.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "ayl-beanstalk"
8
- s.version = "0.2.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-22"
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.2.1"])
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.2.1"])
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.2.1"])
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
@@ -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
- while true
20
- break if @stop
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
- msg = Ayl::Message.from_hash(job.ybody)
34
- process_message(msg)
35
- delete_job(job)
36
- rescue Ayl::UnrecoverableMessageException => ex
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
- delete_job(job)
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
- logger.error "#{self.class.name} Exception in process_messages: #{ex}\n#{ex.backtrace.join("\n")}"
48
- if msg.options.decay_failed_job
49
- handle_job_decay(job, ex)
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
- def handle_job_decay(job, ex)
59
- logger.debug "Age of job: #{job.age}"
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
- def delete_job(job)
71
- job.delete
72
- rescue RuntimeError => ex
73
- logger.error "#{self.class.name} Error deleting job: #{ex}\n#{ex.backtrace.join("\n")}"
74
- Ayl::Mailer.instance.deliver_message("#{self.class.name} Error deleting job", ex)
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
- def decay_job(job)
78
- job.decay
79
- rescue RuntimeError => ex
80
- logger.error "#{self.class.name} Error decaying job: #{ex}\n#{ex.backtrace.join("\n")}"
81
- Ayl::Mailer.instance.deliver_message("#{self.class.name} Error decaying job", ex)
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
@@ -1,5 +1,7 @@
1
1
  require 'ayl'
2
2
  require 'beanstalk-client'
3
+ require 'ayl-beanstalk/job'
4
+ require 'ayl-beanstalk/exceptions'
3
5
  require 'ayl-beanstalk/pool'
4
6
  require 'ayl-beanstalk/engine'
5
7
  require 'ayl-beanstalk/worker'
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, :decay_failed_job => false, :code => "23.to_s(2)" }, 512, 0, 120)
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 "Message Processing" do
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
- mock_message = mock("Ayl::Message")
31
- mock_message.should_receive(:evaluate)
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
- ::Beanstalk::Pool.should_receive(:new).with([ "localhost:11300" ]).and_return(mock_pool)
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
- @worker.process_messages
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 raise an UnrecoverableMessageException when the message body is not a valid hash" do
41
- mock_job = mock("Beanstalk::Job")
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
- Ayl::Message.stub(:from_hash).with("a string").and_raise(Ayl::UnrecoverableMessageException)
42
+ mock_mailer = mock("Mailer")
43
+ mock_mailer.should_receive(:deliver_message).with(any_args())
52
44
 
53
- ::Beanstalk::Pool.should_receive(:new).with([ "localhost:11300" ]).and_return(mock_pool)
45
+ Ayl::Mailer.should_receive(:instance).and_return(mock_mailer)
54
46
 
55
- @worker.process_messages
47
+ @worker.send(:reserve_job) do | job |
48
+ end
56
49
  end
57
50
 
58
- it "should raise an UnrecoverableJobException when the message body is not a valid job type" do
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
- mock_pool = mock("Beanstalk::Pool")
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
- Ayl::Message.stub(:from_hash).with({ :type => :junk, :code => "Dog" }).and_raise(Ayl::UnrecoverableMessageException)
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
- ::Beanstalk::Pool.should_receive(:new).with([ "localhost:11300" ]).and_return(mock_pool)
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.process_messages
79
+ @worker.send(:deal_with_unexpected_exception, mock_job, mock_exception)
74
80
  end
75
81
 
76
- it "should raise an UnrecoverableJobException when there is no code in the message body" do
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(:delete)
79
- mock_job.should_receive(:ybody).and_return({ :type => :ayl })
80
- mock_job.should_not_receive(:age)
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
- mock_pool = mock("Beanstalk::Pool")
83
- mock_pool.should_receive(:watch).with("default")
84
- # Returns nil on the second call.
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
- Ayl::Message.stub(:from_hash).with({ :type => :ayl }).and_raise(Ayl::UnrecoverableMessageException)
98
+ mock_mailer = mock("Mailer")
99
+ mock_mailer.should_receive(:deliver_message).with(any_args())
88
100
 
89
- ::Beanstalk::Pool.should_receive(:new).with([ "localhost:11300" ]).and_return(mock_pool)
101
+ Ayl::Mailer.should_receive(:instance).and_return(mock_mailer)
90
102
 
91
- @worker.process_messages
103
+ @worker.send(:deal_with_unexpected_exception, mock_job, mock_exception)
92
104
  end
93
105
 
94
- it "should not decay a job that receives an active-record exception when the default message options are used" do
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.should_not_receive(:decay)
97
- mock_job.should_receive(:ybody).and_return({ :type => :ayl, :code => "Dog" })
98
- mock_job.should_not_receive(:age)
99
- mock_job.should_receive(:delete)
100
-
101
- mock_pool = mock("Beanstalk::Pool")
102
- mock_pool.should_receive(:watch).with("default")
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
- ::Beanstalk::Pool.should_receive(:new).with([ "localhost:11300" ]).and_return(mock_pool)
118
+ mock_exception = mock("Exception")
119
+ mock_exception.should_receive(:backtrace).and_return([])
120
+ mock_job.should_receive(:ayl_bury)
115
121
 
116
- @worker.process_messages
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
- it "should decay a job that receives an active-record exception on receipt of message that is less than 60 seconds old" do
121
- mock_job = mock("Beanstalk::Job")
122
- mock_job.should_receive(:decay)
123
- mock_job.should_receive(:ybody).and_return({ :type => :ayl, :code => "Dog" })
124
- mock_job.should_receive(:age).exactly(2).times.and_return(10)
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("default")
128
- mock_pool.should_receive(:reserve).and_return(mock_job, nil)
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
- ::Beanstalk::Pool.should_receive(:new).with([ "localhost:11300" ]).and_return(mock_pool)
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 delete a job that receives an active-record exception on receipt of message that is more than 60 seconds old" do
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(:delete)
147
- mock_job.should_receive(:ybody).and_return({ :type => :ayl, :code => "Dog" })
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
- mock_pool = mock("Beanstalk::Pool")
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
- mock_message = mock("Ayl::Message")
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 not attempt to decay the job if there is a problem deleting a job after the message is successfully processed" do
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(:delete).and_raise("Delete failed for some reason")
172
- mock_job.should_receive(:ybody).and_return({ :type => :ayl, :code => "23.to_s(2)" })
173
- mock_job.should_not_receive(:age)
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
- mock_pool = mock("Beanstalk::Pool")
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
- ::Beanstalk::Pool.should_receive(:new).with([ "localhost:11300" ]).and_return(mock_pool)
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
- expect { @worker.process_messages }.not_to raise_error
186
- end
187
- end
194
+ @worker.should_receive(:process_message).with(mock_message).and_raise(Ayl::Beanstalk::RequiresJobDecay.new(20))
188
195
 
189
- context '#delete_job' do
196
+ @worker.should_receive(:reserve_job).and_yield(mock_job)
190
197
 
191
- before(:each) do
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 not raise an exception if the job.delete fail" do
201
- mock_job = mock("Job")
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
- expect { @worker.delete_job(mock_job) }.not_to raise_error
205
- end
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
- end
208
+ @worker.should_receive(:process_message).with(mock_message).and_raise(Ayl::Beanstalk::RequiresJobBury)
208
209
 
209
- context '#decay_job' do
210
+ @worker.should_receive(:reserve_job).and_yield(mock_job)
210
211
 
211
- before(:each) do
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 not raise an exception if the job.delete fail" do
221
- mock_job = mock("Job")
222
- mock_job.should_receive(:decay).and_raise("Decay failed for some reason")
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
- expect { @worker.decay_job(mock_job) }.not_to raise_error
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.2.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-22 00:00:00.000000000 Z
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.2.1
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.2.1
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: 2726199589731342968
196
+ hash: -3806134538379917538
194
197
  required_rubygems_version: !ruby/object:Gem::Requirement
195
198
  none: false
196
199
  requirements: