chore-core 1.5.2 → 1.5.10

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.
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