ayl-beanstalk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: