chore-core 1.5.2 → 1.5.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZDg1NjQ5Yzk0YzUwOGI5NDYxNWYyYmVhNTU4MmY5MTk2NWFjZjZmNA==
5
- data.tar.gz: !binary |-
6
- YmU5ZjI5YTFlNWUyODE3NzZlYzlkNjQ5NTFlZDViOWI4ZTZiYzU3Yg==
2
+ SHA1:
3
+ metadata.gz: b1aae723bbfe580eb5d31876ca91f32dd28baae7
4
+ data.tar.gz: 014aae64a27e2a6eb6210b76561e6cc1765056b6
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- OWRlNjA2ZTI2Mjg2ZGRiYjk5MzY4Nzk1ZjNlMTMzM2UyMzU5NTdhZDI0ZDI2
10
- Nzg1NTRkNGViZWI4NjgxNzkwYWQ4ZDM1YWU4MmQyMTgwNzRhMmYwMDRlYmMx
11
- OTM3MDY5NWY5YjllZmZiM2I2NDJjZGE4NWRlZmY3MDcyYTI3Njg=
12
- data.tar.gz: !binary |-
13
- ODk0ZjIxYTI2YTU1MWQ0M2RiYzIyOGE4MGJjNTJkOTI1MmNhYjk4ZDFhODZl
14
- YTQ5ZDIwMjAzOTEyY2JjYzBjNTZlMmFhYmEyZjYwYTU3Y2ZiMWUwYjZiYmRh
15
- ZDg1YTg4MjBlZTkyNjQyMmRkNTZmZDkwMDBmYjFiNWJmYThiYjE=
6
+ metadata.gz: 110b80593989c4ddd8bf1502705d1ce88fd26ba0e458dcffdd9072ddcbf2d3dd9b7b4760b3a7ab37bbd150f66b4acea02e280e7396c2e203330820e5dceb6939
7
+ data.tar.gz: ef5522be2d88a900151437d3a127b5a692a7e71a3a77169a3fe12e25089157b363f7a0f890d1f48d4fd84aca305eaacfbb5f1553026a02a92ce99ef159ccf4c9
data/README.md CHANGED
@@ -29,6 +29,7 @@ Other options include:
29
29
  --worker-strategy Chore::Strategy::ForkedWorkerStrategy # which worker strategy class to use
30
30
  --consumer Chore::Queues::SQS::Consumer # which consumer class to use Options are SQS::Consumer and Filesystem::Consumer. Filesystem is recommended for local and testing purposes only.
31
31
  --consumer-strategy Chore::Queues::Strategies::Consumer::ThreadedConsumerStrategy # which consuming strategy to use. Options are SingleConsumerStrategy and ThreadedConsumerStrategy. Threaded is recommended for better tuning your consuming profile
32
+ --consumer-sleep-interval 1.0 # The amount of time in seconds to sleep when a consumer doesn't receive any messages. Sub-second values are accepted. The default varies by consumer implementation. This is a weak form of backoff for when there is no work to do.
32
33
  --threads-per-queue 4 # number of threads per queue for consuming from a given queue.
33
34
  --dedupe-servers # if using SQS or similiar queue with at-least once delivery and your memcache is running on something other than localhost
34
35
  --batch-size 50 # how many messages are batched together before handing them to a worker
data/chore-core.gemspec CHANGED
@@ -37,7 +37,7 @@ Gem::Specification.new do |s|
37
37
  s.summary = "Job processing... for the future!"
38
38
 
39
39
  s.add_runtime_dependency(%q<json>, [">= 0"])
40
- s.add_runtime_dependency(%q<aws-sdk>, ["~> 1.12", ">= 1.12.0"])
40
+ s.add_runtime_dependency(%q<aws-sdk>, ["~> 1.56", ">= 1.56.0"])
41
41
  s.add_runtime_dependency(%q<thread>, ["~> 0.1.3"])
42
42
  s.add_development_dependency(%q<rspec>, ["~> 2.12.0"])
43
43
  s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
data/lib/chore/cli.rb CHANGED
@@ -92,7 +92,8 @@ module Chore #:nodoc:
92
92
  private
93
93
  def setup_options #:nodoc:
94
94
  register_option "queues", "-q", "--queues QUEUE1,QUEUE2", "Names of queues to process (default: all known)" do |arg|
95
- options[:queues] = arg.split(",")
95
+ # This will remove duplicates. We ultimately force this to be a Set further below
96
+ options[:queues] = Set.new(arg.split(","))
96
97
  end
97
98
 
98
99
  register_option "except_queues", "-x", "--except QUEUE1,QUEUE2", "Process all queues (cannot specify --queues), except for the ones listed here" do |arg|
@@ -130,6 +131,12 @@ module Chore #:nodoc:
130
131
  options[:consumer_strategy] = constantize(arg)
131
132
  end
132
133
 
134
+ register_option 'consumer_sleep_interval', '--consumer-sleep-interval INTERVAL', Float, 'Length of time in seconds to sleep when the consumer does not find any messages. Defaults vary depending on consumer implementation'
135
+
136
+ register_option 'payload_handler', '--payload_handler CLASS_NAME', 'Name of a class to use as the payload handler (default: Chore::Job)' do |arg|
137
+ options[:payload_handler] = constantize(arg)
138
+ end
139
+
133
140
  register_option 'shutdown_timeout', '--shutdown-timeout SECONDS', Float, "Upon shutdown, the number of seconds to wait before force killing worker strategies (default: #{Chore::DEFAULT_OPTIONS[:shutdown_timeout]})"
134
141
 
135
142
  register_option 'dupe_on_cache_failure', '--dupe-on-cache-failure BOOLEAN', 'Determines the deduping behavior when a cache connection error occurs. When set to false, the message is assumed not to be a duplicate. (default: false)'
@@ -187,23 +194,36 @@ module Chore #:nodoc:
187
194
  raise ArgumentError, "Cannot specify both --except and --queues"
188
195
  end
189
196
 
190
- if !options[:queues]
191
- options[:queues] = Set.new
192
- Chore::Job.job_classes.each do |j|
193
- klazz = constantize(j)
194
- options[:queues] << klazz.options[:name] if klazz.options[:name]
195
- options[:queues] -= (options[:except_queues] || [])
196
- end
197
+ ### Ensure after loading the app, that we have the prefix set (quirk of using Chore::Job#prefixed_queue_name to do the prefixing)
198
+ Chore.config.queue_prefix ||= options[:queue_prefix]
199
+
200
+ ### For ease, make sure except_queues is an array
201
+ options[:except_queues] ||= []
202
+
203
+ ### Generate a hash of all possible queues and their prefixed_names
204
+ queue_map = Chore::Job.job_classes.inject({}) do |hsh,j|
205
+ klazz = constantize(j)
206
+ hsh[klazz.options[:name]] = klazz.prefixed_queue_name if klazz.options[:name]
207
+ hsh
197
208
  end
198
209
 
199
- original_queues = options[:queues].dup
200
- # Now apply the prefixing
201
- # Because the prefix could have been detected via the apps chore config file
202
- # Lets see if that is present before we check for a CLI passed prefix
203
- prefix = Chore.config.queue_prefix || options[:queue_prefix]
204
- options[:queues] = Set.new.tap do |queue_set|
205
- original_queues.each {|oq_name| queue_set << "#{prefix}#{oq_name}"}
210
+ ### If we passed in a queues option, use it as our working set, otherwise use all the queues
211
+ if options[:queues]
212
+ queues_to_use = options[:queues]
213
+ else
214
+ queues_to_use = queue_map.keys
206
215
  end
216
+
217
+ ### Remove the excepted queues from our working set
218
+ queues_to_use = queues_to_use - options[:except_queues]
219
+
220
+ ### Set options[:queues] to the prefixed value of the current working set
221
+ options[:queues] = queues_to_use.inject([]) do |queues,k|
222
+ raise ArgumentError, "You have specified a queue #{k} for which you have no corresponding Job class" unless queue_map.has_key?(k)
223
+ queues << queue_map[k]
224
+ queues
225
+ end
226
+
207
227
  raise ArgumentError, "No queues specified. Either include classes that include Chore::Job, or specify the --queues option" if options[:queues].empty?
208
228
  end
209
229
 
@@ -29,11 +29,11 @@ module Chore
29
29
  # Checks the message against the configured dedupe server to see if the message is unique or not
30
30
  # Unique messages will return false
31
31
  # Duplicated messages will return true
32
- def found_duplicate?(msg)
33
- return false unless msg && msg.respond_to?(:queue) && msg.queue
34
- timeout = self.queue_timeout(msg.queue)
32
+ def found_duplicate?(msg_data)
33
+ return false unless msg_data && msg_data[:queue]
34
+ timeout = self.queue_timeout(msg_data)
35
35
  begin
36
- !@memcached_client.add(msg.id, "1",timeout)
36
+ !@memcached_client.add(msg_data[:id], "1",timeout)
37
37
  rescue StandardError => e
38
38
  if @dupe_on_cache_failure
39
39
  Chore.logger.error "Error accessing duplicate cache server. Assuming message is a duplicate. #{e}\n#{e.backtrace * "\n"}"
@@ -48,8 +48,8 @@ module Chore
48
48
  # Retrieves the timeout for the given queue. The timeout is the window of time in seconds that
49
49
  # we would consider the message to be non-unique, before we consider it dead in the water
50
50
  # After that timeout, we would consider the next copy of the message received to be unique, and process it.
51
- def queue_timeout(queue)
52
- @timeouts[queue.url] ||= queue.visibility_timeout || @timeout
51
+ def queue_timeout(msg_data)
52
+ @timeouts[msg_data[:queue]] ||= msg_data[:visibility_timeout] || @timeout
53
53
  end
54
54
 
55
55
  end
@@ -0,0 +1,20 @@
1
+ require 'json'
2
+
3
+ module Chore
4
+ module Encoder
5
+ # Json encoding for serializing jobs.
6
+ module JsonEncoder
7
+ class << self
8
+ # Encodes the +job+ into JSON using the standard ruby JSON parsing library
9
+ def encode(job)
10
+ JSON.generate(job.to_hash)
11
+ end
12
+
13
+ # Decodes the +job+ from JSON into a ruby Hash using the standard ruby JSON parsing library
14
+ def decode(job)
15
+ JSON.parse(job)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
data/lib/chore/job.rb CHANGED
@@ -1,10 +1,13 @@
1
1
  require 'chore/hooks'
2
+ require 'chore/util'
3
+ require 'chore/encoders/json_encoder'
2
4
 
3
5
  module Chore
4
6
 
5
7
  # <tt>Chore::Job</tt> is the module which gives your job classes the methods they need to be published
6
8
  # and run within Chore. You cannot have a Job in Chore that does not include this module
7
9
  module Job
10
+ extend Util
8
11
 
9
12
  # An exception to represent a job choosing to forcibly reject a given instance of itself.
10
13
  # The reasoning behind rejecting the job and the message that spawned it are left to
@@ -25,6 +28,18 @@ module Chore
25
28
  base.extend(Hooks)
26
29
  end
27
30
 
31
+ def self.payload_class(message)
32
+ constantize(message['class'])
33
+ end
34
+
35
+ def self.decode(data)
36
+ Encoder::JsonEncoder.decode(data)
37
+ end
38
+
39
+ def self.payload(message)
40
+ message['args']
41
+ end
42
+
28
43
  module ClassMethods
29
44
  DEFAULT_OPTIONS = { }
30
45
 
@@ -1,7 +1,7 @@
1
1
  module Chore
2
2
  # Base class for Chore Publishers. Provides the bare interface one needs to adhere to when writing custom publishers
3
3
  class Publisher
4
- DEFAULT_OPTIONS = { :encoder => JsonEncoder }
4
+ DEFAULT_OPTIONS = { :encoder => Encoder::JsonEncoder }
5
5
 
6
6
  attr_accessor :options
7
7
 
@@ -34,10 +34,10 @@ module Chore
34
34
  while running?
35
35
  begin
36
36
  messages = handle_messages(&handler)
37
- sleep 1 if messages.empty?
37
+ sleep (Chore.config.consumer_sleep_interval || 1) if messages.empty?
38
38
  rescue AWS::SQS::Errors::NonExistentQueue => e
39
- Chore.logger.error "You specified a queue that does not exist. You must create the queue before starting Chore. Shutting down..."
40
- raise Chore::TerribleMistake
39
+ Chore.logger.error "You specified a queue '#{queue_name}' that does not exist. You must create the queue before starting Chore. Shutting down..."
40
+ raise Chore::TerribleMistake
41
41
  rescue => e
42
42
  Chore.logger.error { "SQSConsumer#Consume: #{e.inspect} #{e.backtrace * "\n"}" }
43
43
  end
@@ -61,18 +61,17 @@ module Chore
61
61
  # hook will be invoked, per message
62
62
  def handle_messages(&block)
63
63
  msg = queue.receive_messages(:limit => sqs_polling_amount, :attributes => [:receive_count])
64
-
65
64
  messages = *msg
66
65
  messages.each do |message|
67
- block.call(message.handle, queue_name, queue_timeout, message.body, message.receive_count - 1) unless duplicate_message?(message)
66
+ block.call(message.handle, queue_name, queue_timeout, message.body, message.receive_count - 1) unless duplicate_message?({:id=>message.id, :queue=>message.queue.url, :visibility_timeout=>message.queue.visibility_timeout})
68
67
  Chore.run_hooks_for(:on_fetch, message.handle, message.body)
69
68
  end
70
69
  messages
71
70
  end
72
71
 
73
72
  # Checks if the given message has already been received within the timeout window for this queue
74
- def duplicate_message?(message)
75
- dupe_detector.found_duplicate?(message)
73
+ def duplicate_message?(msg_data)
74
+ dupe_detector.found_duplicate?(msg_data)
76
75
  end
77
76
 
78
77
  # Returns the instance of the DuplicateDetector used to ensure unique messages.
@@ -27,9 +27,14 @@ module Chore
27
27
  #This will raise an exception if AWS has not been configured by the project making use of Chore
28
28
  sqs_queues = AWS::SQS.new.queues
29
29
  Chore.prefixed_queue_names.each do |queue_name|
30
- Chore.logger.info "Chore Deleting Queue: #{queue_name}"
31
- url = sqs_queues.url_for(queue_name)
32
- sqs_queues[url].delete
30
+ begin
31
+ Chore.logger.info "Chore Deleting Queue: #{queue_name}"
32
+ url = sqs_queues.url_for(queue_name)
33
+ sqs_queues[url].delete
34
+ rescue => e
35
+ # This could fail for a few reasons - log out why it failed, then continue on
36
+ Chore.logger.error "Deleting Queue: #{queue_name} failed because #{e}"
37
+ end
33
38
  end
34
39
  Chore.prefixed_queue_names
35
40
  end
@@ -5,7 +5,8 @@ module Chore
5
5
  class ForkedWorkerStrategy #:nodoc:
6
6
  attr_accessor :workers
7
7
 
8
- def initialize(manager)
8
+ def initialize(manager, opts={})
9
+ @options = opts
9
10
  @manager = manager
10
11
  @stopped = false
11
12
  @workers = {}
@@ -57,7 +58,7 @@ module Chore
57
58
  return unless acquire_worker
58
59
 
59
60
  begin
60
- w = Worker.new(work)
61
+ w = Worker.new(work, @options)
61
62
  Chore.run_hooks_for(:before_fork,w)
62
63
  pid = nil
63
64
  Chore.run_hooks_for(:around_fork,w) do
@@ -7,7 +7,8 @@ module Chore
7
7
 
8
8
  attr_reader :worker
9
9
 
10
- def initialize(manager)
10
+ def initialize(manager, opts={})
11
+ @options = opts
11
12
  @manager = manager
12
13
  @worker = nil
13
14
  end
@@ -23,7 +24,7 @@ module Chore
23
24
  # Assigns work if there isn't already a worker in progress. Otherwise, is a noop
24
25
  def assign(work)
25
26
  if workers_available?
26
- @worker = Worker.new(work)
27
+ @worker = Worker.new(work, @options)
27
28
  @worker.start
28
29
  @worker = nil
29
30
  true
data/lib/chore/version.rb CHANGED
@@ -2,7 +2,7 @@ module Chore
2
2
  module Version #:nodoc:
3
3
  MAJOR = 1
4
4
  MINOR = 5
5
- PATCH = 2
5
+ PATCH = 10
6
6
 
7
7
  STRING = [ MAJOR, MINOR, PATCH ].join('.')
8
8
  end
data/lib/chore/worker.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'chore/util'
2
- require 'chore/json_encoder'
2
+ require 'chore/job'
3
3
 
4
4
  module Chore
5
5
  class TimeoutError < StandardError
@@ -12,24 +12,23 @@ module Chore
12
12
  class Worker
13
13
  include Util
14
14
 
15
- DEFAULT_OPTIONS = { :encoder => JsonEncoder }
16
15
  attr_accessor :options
17
16
  attr_reader :work
18
17
  attr_reader :started_at
19
18
 
20
- def self.start(work) #:nodoc:
21
- self.new(work).start
19
+ def self.start(work, opts={}) #:nodoc:
20
+ self.new(work, opts).start
22
21
  end
23
22
 
24
23
  # Create a Worker. Give it an array of work (or single item), and +opts+.
25
- # Currently, the only option supported by Worker is +:encoder+ which should match
26
- # the +:encoder+ used by the Publisher who created the message.
24
+ # Currently, the only option supported by Worker is +:payload_handler+ which contains helpers
25
+ # for decoding the item and finding the correct payload class
27
26
  def initialize(work=[],opts={})
28
27
  @stopping = false
29
28
  @started_at = Time.now
30
29
  @work = work
31
30
  @work = [work] unless work.kind_of?(Array)
32
- self.options = DEFAULT_OPTIONS.merge(opts)
31
+ self.options = {:payload_handler => Chore.config.payload_handler}.merge(opts)
33
32
  end
34
33
 
35
34
  # Whether this worker has existed for longer than it's allowed to
@@ -56,7 +55,6 @@ module Chore
56
55
  def start
57
56
  @work.each do |item|
58
57
  return if @stopping
59
- Chore.logger.debug { "Doing: #{item.queue_name} with #{item.message}" }
60
58
  begin
61
59
  start_item(item)
62
60
  rescue => e
@@ -76,20 +74,16 @@ module Chore
76
74
  @stopping = true
77
75
  end
78
76
 
79
- protected
80
- def payload_class(message)
81
- constantize(message['class'])
82
- end
83
-
84
77
  private
85
78
  def start_item(item)
86
- message = decode_job(item.message)
87
- klass = payload_class(message)
79
+ message = options[:payload_handler].decode(item.message)
80
+ klass = options[:payload_handler].payload_class(message)
88
81
  return unless klass.run_hooks_for(:before_perform,message)
89
-
90
82
  begin
83
+ Chore.logger.info { "Running job #{klass} with params #{message}"}
91
84
  perform_job(klass,message)
92
85
  item.consumer.complete(item.id)
86
+ Chore.logger.info { "Finished job #{klass} with params #{message}"}
93
87
  klass.run_hooks_for(:after_perform,message)
94
88
  rescue Job::RejectMessageException
95
89
  item.consumer.reject(item.id)
@@ -107,11 +101,7 @@ module Chore
107
101
  end
108
102
 
109
103
  def perform_job(klass, message)
110
- klass.perform(*message['args'])
111
- end
112
-
113
- def decode_job(data)
114
- options[:encoder].decode(data)
104
+ klass.perform(*options[:payload_handler].payload(message))
115
105
  end
116
106
  end
117
107
  end
data/lib/chore.rb CHANGED
@@ -8,7 +8,7 @@ require 'chore/configuration'
8
8
  require 'chore/cli'
9
9
  require 'chore/consumer'
10
10
  require 'chore/job'
11
- require 'chore/json_encoder'
11
+ require 'chore/encoders/json_encoder'
12
12
  require 'chore/manager'
13
13
  require 'chore/publisher'
14
14
  require 'chore/util'
@@ -21,6 +21,7 @@ require 'chore/publisher'
21
21
  end
22
22
 
23
23
  module Chore #:nodoc:
24
+ extend Util
24
25
  VERSION = Chore::Version::STRING #:nodoc:
25
26
 
26
27
  # The default configuration options for Chore.
@@ -38,7 +39,8 @@ module Chore #:nodoc:
38
39
  :default_queue_timeout => (12 * 60 * 60), # 12 hours
39
40
  :shutdown_timeout => (2 * 60),
40
41
  :max_attempts => 1.0 / 0.0, # Infinity
41
- :dupe_on_cache_failure => false
42
+ :dupe_on_cache_failure => false,
43
+ :payload_handler => Chore::Job
42
44
  }
43
45
 
44
46
  class << self
@@ -211,7 +213,7 @@ module Chore #:nodoc:
211
213
 
212
214
  # List of queue_names as configured via Chore::Job including their prefix, if set.
213
215
  def self.prefixed_queue_names
214
- Chore::Job.job_classes.collect {|klass| c = klass.constantize; c.prefixed_queue_name}
216
+ Chore::Job.job_classes.collect {|klass| c = constantize(klass); c.prefixed_queue_name}
215
217
  end
216
218
  end
217
219
 
@@ -79,6 +79,14 @@ describe Chore::CLI do
79
79
  end
80
80
  end
81
81
 
82
+ context 'when provided a queue for which there is no job class' do
83
+ let(:queue_options) {['--queues=test2,test3']}
84
+
85
+ it 'should raise an error' do
86
+ expect {cli.parse(queue_options)}.to raise_error
87
+ end
88
+ end
89
+
82
90
  context 'when both --queue_prefix and --queues have been provided' do
83
91
  let(:queue_options) {['--queue-prefix=prefixy', '--queues=test2']}
84
92
  before :each do
@@ -112,9 +120,14 @@ describe Chore::CLI do
112
120
  expect { cli.parse(['--except=something','--queues=something,else']) }.to raise_error(ArgumentError)
113
121
  end
114
122
 
115
- it 'should raise an exception if no queues are found' do
116
- Chore::Job.job_classes.clear
117
- expect { cli.parse([]) }.to raise_error(ArgumentError)
123
+ context "when no queues are found" do
124
+ before :each do
125
+ Chore::Job.stub(:job_classes).and_return([])
126
+ end
127
+
128
+ it 'should raise an exception' do
129
+ expect { cli.parse([]) }.to raise_error(ArgumentError)
130
+ end
118
131
  end
119
132
  end
120
133
 
@@ -138,6 +151,14 @@ describe Chore::CLI do
138
151
  end
139
152
  end
140
153
 
154
+ context "--payload_handler" do
155
+ let(:command) {["--payload_handler=Chore::Job"]}
156
+
157
+ it "should set the payload handler class" do
158
+ config.payload_handler.should == Chore::Job
159
+ end
160
+ end
161
+
141
162
  describe '--shutdown-timeout' do
142
163
  let(:command) { ["--shutdown-timeout=#{amount}"] }
143
164
  subject { config.shutdown_timeout }
@@ -158,6 +179,34 @@ describe Chore::CLI do
158
179
  end
159
180
  end
160
181
 
182
+ describe '--consumer_sleep_interval' do
183
+ let(:command) {["--consumer-sleep-interval=#{amount}"]}
184
+ subject {config.consumer_sleep_interval}
185
+
186
+ context 'given an integer value' do
187
+ let(:amount) { '10' }
188
+
189
+ it 'is that amount' do
190
+ subject.should == amount.to_i
191
+ end
192
+ end
193
+
194
+ context 'given a float value' do
195
+ let(:amount) { '0.5' }
196
+
197
+ it 'is that amount' do
198
+ subject.should == amount.to_f
199
+ end
200
+ end
201
+
202
+ context 'given no value' do
203
+ let(:command) { [] }
204
+ it 'is the default value, nil' do
205
+ subject.should == nil
206
+ end
207
+ end
208
+ end
209
+
161
210
  describe '--max-attempts' do
162
211
  let(:command) { ["--max-attempts=#{amount}"] }
163
212
  subject { config.max_attempts }
@@ -6,46 +6,42 @@ describe Chore::DuplicateDetector do
6
6
  let(:dupe_on_cache_failure) { false }
7
7
  let(:dedupe_params) { { :memcached_client => memcache, :dupe_on_cache_failure => dupe_on_cache_failure } }
8
8
  let(:dedupe) { Chore::DuplicateDetector.new(dedupe_params)}
9
- let(:message) { double('message') }
10
9
  let(:timeout) { 2 }
11
10
  let(:queue_url) {"queue://bogus/url"}
12
- let(:queue) { (q = double('queue')).stub(:visibility_timeout).and_return(timeout); q.stub(:url).and_return(queue_url); q }
11
+ let(:queue) { double('queue', :visibility_timeout=>timeout, :url=>queue_url) }
13
12
  let(:id) { SecureRandom.uuid }
14
-
15
- before(:each) do
16
- message.stub(:id).and_return(id)
17
- message.stub(:queue).and_return(queue)
18
- end
13
+ let(:message) { double('message', :id=>id, :queue=>queue) }
14
+ let(:message_data) {{:id=>message.id, :visibility_timeout=>queue.visibility_timeout, :queue=>queue.url}}
19
15
 
20
16
  describe "#found_duplicate" do
21
17
  it 'should not return true if the message has not already been seen' do
22
18
  memcache.should_receive(:add).and_return(true)
23
- dedupe.found_duplicate?(message).should_not be_true
19
+ dedupe.found_duplicate?(message_data).should_not be_true
24
20
  end
25
21
 
26
22
  it 'should return true if the message has already been seen' do
27
23
  memcache.should_receive(:add).and_return(false)
28
- dedupe.found_duplicate?(message).should be_true
24
+ dedupe.found_duplicate?(message_data).should be_true
29
25
  end
30
26
 
31
27
  it 'should return false if given an invalid message' do
32
- dedupe.found_duplicate?(double()).should be_false
28
+ dedupe.found_duplicate?({}).should be_false
33
29
  end
34
30
 
35
31
  it "should return false when identity store errors" do
36
32
  memcache.should_receive(:add).and_raise("no")
37
- dedupe.found_duplicate?(message).should be_false
33
+ dedupe.found_duplicate?(message_data).should be_false
38
34
  end
39
35
 
40
36
  it "should set the timeout to be the queue's " do
41
37
  memcache.should_receive(:add).with(id,"1",timeout).and_return(true)
42
- dedupe.found_duplicate?(message).should be_false
38
+ dedupe.found_duplicate?(message_data).should be_false
43
39
  end
44
40
 
45
41
  it "should call #visibility_timeout once and only once" do
46
42
  queue.should_receive(:visibility_timeout).once
47
43
  memcache.should_receive(:add).at_least(3).times.and_return(true)
48
- 3.times { dedupe.found_duplicate?(message) }
44
+ 3.times { dedupe.found_duplicate?(message_data) }
49
45
  end
50
46
 
51
47
  context 'when a memecached connection error occurs' do
@@ -54,7 +50,7 @@ describe Chore::DuplicateDetector do
54
50
 
55
51
  it "returns true" do
56
52
  memcache.should_receive(:add).and_raise
57
- dedupe.found_duplicate?(message).should be_true
53
+ dedupe.found_duplicate?(message_data).should be_true
58
54
  end
59
55
  end
60
56
  end
@@ -1,6 +1,6 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
- describe Chore::JsonEncoder do
3
+ describe Chore::Encoder::JsonEncoder do
4
4
  it 'should have an encode method' do
5
5
  subject.should respond_to :encode
6
6
  end
@@ -15,10 +15,12 @@ describe Chore::Manager do
15
15
  manager = Chore::Manager.new
16
16
  end
17
17
 
18
+
19
+
18
20
  describe 'running the manager' do
19
21
 
20
22
  let(:manager) { Chore::Manager.new}
21
- let(:work) { Chore::UnitOfWork.new(Chore::JsonEncoder.encode({:class => 'MyClass',:args => []}),mock()) }
23
+ let(:work) { Chore::UnitOfWork.new(Chore::Encoder::JsonEncoder.encode({:class => 'MyClass',:args => []}),mock()) }
22
24
 
23
25
  it 'should start the fetcher when starting the manager' do
24
26
  fetcher.should_receive(:start)
@@ -26,14 +28,17 @@ describe Chore::Manager do
26
28
  end
27
29
 
28
30
  describe 'assigning messages' do
29
- it 'should create a worker if one is available' do
30
- worker = mock()
31
- Chore::Worker.should_receive(:new).with(work).and_return(worker)
31
+ let(:worker) { mock() }
32
+
33
+ before(:each) do
32
34
  worker.should_receive(:start).with()
35
+ end
36
+
37
+ it 'should create a worker if one is available' do
38
+ Chore::Worker.should_receive(:new).with(work,{}).and_return(worker)
33
39
  manager.assign(work)
34
40
  end
35
41
  end
36
-
37
42
  end
38
43
 
39
44
  end
@@ -4,10 +4,11 @@ describe Chore::Queues::SQS::Consumer do
4
4
  let(:queue_name) { "test" }
5
5
  let(:queue_url) { "test_url" }
6
6
  let(:queues) { double("queues") }
7
- let(:queue) { double("test_queue") }
7
+ let(:queue) { double("test_queue", :visibility_timeout=>10, :url=>"test_queue", :name=>"test_queue") }
8
8
  let(:options) { {} }
9
9
  let(:consumer) { Chore::Queues::SQS::Consumer.new(queue_name) }
10
- let(:message) { TestMessage.new("handle",queue_name,"message body", 1) }
10
+ let(:message) { TestMessage.new("handle",queue, "message body", 1) }
11
+ let(:message_data) {{:id=>message.id, :queue=>message.queue.url, :visibility_timeout=>message.queue.visibility_timeout}}
11
12
  let(:pool) { double("pool") }
12
13
  let(:sqs) { double('AWS::SQS') }
13
14
 
@@ -18,7 +19,6 @@ describe Chore::Queues::SQS::Consumer do
18
19
  queues.stub(:url_for) { queue_url }
19
20
  queues.stub(:[]) { queue }
20
21
  queue.stub(:receive_message) { message }
21
- queue.stub(:visibility_timeout) { 10 }
22
22
  pool.stub(:empty!) { nil }
23
23
  end
24
24
 
@@ -72,7 +72,7 @@ describe Chore::Queues::SQS::Consumer do
72
72
  end
73
73
 
74
74
  it "should check the uniqueness of the message" do
75
- Chore::DuplicateDetector.any_instance.should_receive(:found_duplicate?).with(message).and_return(false)
75
+ Chore::DuplicateDetector.any_instance.should_receive(:found_duplicate?).with(message_data).and_return(false)
76
76
  consumer.consume
77
77
  end
78
78
 
@@ -81,7 +81,7 @@ describe Chore::Queues::SQS::Consumer do
81
81
  end
82
82
 
83
83
  it 'should not yield for a dupe message' do
84
- Chore::DuplicateDetector.any_instance.should_receive(:found_duplicate?).with(message).and_return(true)
84
+ Chore::DuplicateDetector.any_instance.should_receive(:found_duplicate?).with(message_data).and_return(true)
85
85
  expect {|b| consumer.consume(&b) }.not_to yield_control
86
86
  end
87
87
 
@@ -9,7 +9,7 @@ describe Chore::Strategy::ForkedWorkerStrategy do
9
9
  strategy
10
10
  end
11
11
  let(:job_timeout) { 60 }
12
- let(:job) { Chore::UnitOfWork.new(SecureRandom.uuid, 'test', job_timeout, Chore::JsonEncoder.encode(TestJob.job_hash([1,2,"3"])), 0) }
12
+ let(:job) { Chore::UnitOfWork.new(SecureRandom.uuid, 'test', job_timeout, Chore::Encoder::JsonEncoder.encode(TestJob.job_hash([1,2,"3"])), 0) }
13
13
  let!(:worker) { Chore::Worker.new(job) }
14
14
  let(:pid) { Random.rand(2048) }
15
15
 
@@ -41,7 +41,7 @@ describe Chore::Strategy::ForkedWorkerStrategy do
41
41
  end
42
42
 
43
43
  it 'should assign a job to a new worker' do
44
- Chore::Worker.should_receive(:new).with(job).and_return(worker)
44
+ Chore::Worker.should_receive(:new).with(job, {}).and_return(worker)
45
45
  worker.should_receive(:start)
46
46
  forker.assign(job)
47
47
  end
@@ -15,27 +15,36 @@ describe Chore::Worker do
15
15
  let(:job_args) { [1,2,'3'] }
16
16
  let(:job) { SimpleJob.job_hash(job_args) }
17
17
 
18
- it 'should use a default encoder' do
18
+ it 'should use a default payload handler' do
19
19
  worker = Chore::Worker.new
20
- worker.options[:encoder].should == Chore::JsonEncoder
20
+ worker.options[:payload_handler].should == Chore::Job
21
21
  end
22
22
 
23
- it 'should process a single job' do
24
- work = Chore::UnitOfWork.new('1', 'test', 60, Chore::JsonEncoder.encode(job), 0, consumer)
25
- SimpleJob.should_receive(:perform).with(*job_args)
26
- consumer.should_receive(:complete).with('1')
27
- w = Chore::Worker.new(work)
28
- w.start
29
- end
23
+ shared_examples_for "a worker" do
24
+ it 'processing a single job' do
25
+ work = Chore::UnitOfWork.new('1', 'test', 60, encoded_job, 0, consumer)
26
+ SimpleJob.should_receive(:perform).with(*payload)
27
+ consumer.should_receive(:complete).with('1')
28
+ w = Chore::Worker.new(work, {:payload_handler => payload_handler})
29
+ w.start
30
+ end
30
31
 
31
- it 'should process multiple jobs' do
32
- work = []
33
- 10.times do |i|
34
- work << Chore::UnitOfWork.new(i, 'test', 60, Chore::JsonEncoder.encode(job), 0, consumer)
32
+ it 'processing multiple jobs' do
33
+ work = []
34
+ 10.times do |i|
35
+ work << Chore::UnitOfWork.new(i, 'test', 60, encoded_job, 0, consumer)
36
+ end
37
+ SimpleJob.should_receive(:perform).exactly(10).times
38
+ consumer.should_receive(:complete).exactly(10).times
39
+ Chore::Worker.start(work, {:payload_handler => payload_handler})
35
40
  end
36
- SimpleJob.should_receive(:perform).exactly(10).times
37
- consumer.should_receive(:complete).exactly(10).times
38
- Chore::Worker.start(work)
41
+ end
42
+
43
+ describe "with default payload handler" do
44
+ let(:encoded_job) { Chore::Encoder::JsonEncoder.encode(job) }
45
+ let(:payload_handler) { Chore::Job }
46
+ let(:payload) {job_args}
47
+ it_behaves_like "a worker"
39
48
  end
40
49
 
41
50
  describe 'expired?' do
@@ -43,7 +52,7 @@ describe Chore::Worker do
43
52
  let(:queue_timeouts) { [10, 20, 30] }
44
53
  let(:work) do
45
54
  queue_timeouts.map do |queue_timeout|
46
- Chore::UnitOfWork.new('1', 'test', queue_timeout, Chore::JsonEncoder.encode(job), 0, consumer)
55
+ Chore::UnitOfWork.new('1', 'test', queue_timeout, Chore::Encoder::JsonEncoder.encode(job), 0, consumer)
47
56
  end
48
57
  end
49
58
  let(:worker) do
@@ -100,7 +109,7 @@ describe Chore::Worker do
100
109
  end
101
110
 
102
111
  context 'on perform' do
103
- let(:encoded_job) { Chore::JsonEncoder.encode(job) }
112
+ let(:encoded_job) { Chore::Encoder::JsonEncoder.encode(job) }
104
113
  let(:parsed_job) { JSON.parse(encoded_job) }
105
114
 
106
115
  before(:each) do
data/spec/spec_helper.rb CHANGED
@@ -26,11 +26,19 @@ class FakePublisher < Chore::Publisher
26
26
  end
27
27
  end
28
28
 
29
- TestMessage = Struct.new(:handle,:queue_name,:body,:receive_count) do
29
+ TestMessage = Struct.new(:handle,:queue,:body,:receive_count) do
30
30
  def empty?
31
31
  false
32
32
  end
33
33
 
34
+ def queue_name
35
+ self.queue.name
36
+ end
37
+
38
+ def id
39
+ self.handle
40
+ end
41
+
34
42
  # Structs define a to_a behavior that is not compatible with array splatting. Remove it so that
35
43
  # [*message] on a struct will behave the same as on a string.
36
44
  undef_method :to_a
@@ -38,6 +46,7 @@ end
38
46
 
39
47
 
40
48
  RSpec.configure do |config|
49
+ config.include Chore::Util
41
50
  config.before do
42
51
  Chore.configure do |c|
43
52
  c.aws_access_key = ""
metadata CHANGED
@@ -1,103 +1,103 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chore-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 1.5.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tapjoy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-20 00:00:00.000000000 Z
11
+ date: 2015-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ! '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ! '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: aws-sdk
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.12'
34
- - - ! '>='
33
+ version: '1.56'
34
+ - - ">="
35
35
  - !ruby/object:Gem::Version
36
- version: 1.12.0
36
+ version: 1.56.0
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
- - - ~>
41
+ - - "~>"
42
42
  - !ruby/object:Gem::Version
43
- version: '1.12'
44
- - - ! '>='
43
+ version: '1.56'
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 1.12.0
46
+ version: 1.56.0
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: thread
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - ~>
51
+ - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: 0.1.3
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - ~>
58
+ - - "~>"
59
59
  - !ruby/object:Gem::Version
60
60
  version: 0.1.3
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: rspec
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - ~>
65
+ - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: 2.12.0
68
68
  type: :development
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - ~>
72
+ - - "~>"
73
73
  - !ruby/object:Gem::Version
74
74
  version: 2.12.0
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: rdoc
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - ~>
79
+ - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '3.12'
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - ~>
86
+ - - "~>"
87
87
  - !ruby/object:Gem::Version
88
88
  version: '3.12'
89
89
  - !ruby/object:Gem::Dependency
90
90
  name: bundler
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - ! '>='
93
+ - - ">="
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
- - - ! '>='
100
+ - - ">="
101
101
  - !ruby/object:Gem::Version
102
102
  version: '0'
103
103
  description: Job processing with pluggable backends and strategies
@@ -120,10 +120,10 @@ files:
120
120
  - lib/chore/configuration.rb
121
121
  - lib/chore/consumer.rb
122
122
  - lib/chore/duplicate_detector.rb
123
+ - lib/chore/encoders/json_encoder.rb
123
124
  - lib/chore/fetcher.rb
124
125
  - lib/chore/hooks.rb
125
126
  - lib/chore/job.rb
126
- - lib/chore/json_encoder.rb
127
127
  - lib/chore/manager.rb
128
128
  - lib/chore/publisher.rb
129
129
  - lib/chore/queues/filesystem/consumer.rb
@@ -147,10 +147,10 @@ files:
147
147
  - spec/chore/cli_spec.rb
148
148
  - spec/chore/consumer_spec.rb
149
149
  - spec/chore/duplicate_detector_spec.rb
150
+ - spec/chore/encoders/json_encoder_spec.rb
150
151
  - spec/chore/fetcher_spec.rb
151
152
  - spec/chore/hooks_spec.rb
152
153
  - spec/chore/job_spec.rb
153
- - spec/chore/json_encoder_spec.rb
154
154
  - spec/chore/manager_spec.rb
155
155
  - spec/chore/queues/filesystem/filesystem_consumer_spec.rb
156
156
  - spec/chore/queues/sqs/consumer_spec.rb
@@ -176,17 +176,17 @@ require_paths:
176
176
  - lib
177
177
  required_ruby_version: !ruby/object:Gem::Requirement
178
178
  requirements:
179
- - - ! '>='
179
+ - - ">="
180
180
  - !ruby/object:Gem::Version
181
181
  version: '0'
182
182
  required_rubygems_version: !ruby/object:Gem::Requirement
183
183
  requirements:
184
- - - ! '>='
184
+ - - ">="
185
185
  - !ruby/object:Gem::Version
186
186
  version: '0'
187
187
  requirements: []
188
188
  rubyforge_project:
189
- rubygems_version: 2.2.2
189
+ rubygems_version: 2.4.5
190
190
  signing_key:
191
191
  specification_version: 4
192
192
  summary: Job processing... for the future!
@@ -1,18 +0,0 @@
1
- require 'json'
2
-
3
- module Chore
4
- # Json encoding for serializing jobs.
5
- module JsonEncoder
6
- class << self
7
- # Encodes the +job+ into JSON using the standard ruby JSON parsing library
8
- def encode(job)
9
- JSON.generate(job.to_hash)
10
- end
11
-
12
- # Decodes the +job+ from JSON into a ruby Hash using the standard ruby JSON parsing library
13
- def decode(job)
14
- JSON.parse(job)
15
- end
16
- end
17
- end
18
- end