chore-core 3.2.3 → 4.0.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.
- checksums.yaml +5 -5
- data/LICENSE.txt +1 -1
- data/README.md +170 -153
- data/chore-core.gemspec +2 -3
- data/lib/chore.rb +20 -0
- data/lib/chore/cli.rb +1 -2
- data/lib/chore/configuration.rb +1 -1
- data/lib/chore/consumer.rb +41 -9
- data/lib/chore/job.rb +2 -0
- data/lib/chore/publisher.rb +18 -2
- data/lib/chore/queues/filesystem/consumer.rb +18 -13
- data/lib/chore/queues/filesystem/publisher.rb +1 -1
- data/lib/chore/queues/sqs.rb +22 -13
- data/lib/chore/queues/sqs/consumer.rb +61 -33
- data/lib/chore/queues/sqs/publisher.rb +26 -17
- data/lib/chore/strategies/consumer/batcher.rb +6 -6
- data/lib/chore/strategies/consumer/single_consumer_strategy.rb +5 -5
- data/lib/chore/strategies/consumer/threaded_consumer_strategy.rb +6 -6
- data/lib/chore/strategies/consumer/throttled_consumer_strategy.rb +10 -11
- data/lib/chore/strategies/worker/helpers/ipc.rb +0 -1
- data/lib/chore/unit_of_work.rb +2 -1
- data/lib/chore/version.rb +3 -3
- data/lib/chore/worker.rb +4 -4
- data/spec/chore/consumer_spec.rb +1 -1
- data/spec/chore/queues/filesystem/filesystem_consumer_spec.rb +5 -7
- data/spec/chore/queues/sqs/consumer_spec.rb +117 -76
- data/spec/chore/queues/sqs/publisher_spec.rb +49 -60
- data/spec/chore/queues/sqs_spec.rb +32 -41
- data/spec/chore/strategies/consumer/single_consumer_strategy_spec.rb +3 -3
- data/spec/chore/strategies/consumer/threaded_consumer_strategy_spec.rb +6 -6
- data/spec/chore/strategies/worker/forked_worker_strategy_spec.rb +1 -1
- data/spec/chore/strategies/worker/single_worker_strategy_spec.rb +1 -1
- data/spec/chore/worker_spec.rb +21 -21
- data/spec/spec_helper.rb +1 -1
- data/spec/support/queues/sqs/fake_objects.rb +18 -0
- metadata +9 -13
data/chore-core.gemspec
CHANGED
@@ -37,11 +37,10 @@ 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-
|
40
|
+
s.add_runtime_dependency(%q<aws-sdk-sqs>, ["~> 1"])
|
41
41
|
s.add_runtime_dependency(%q<thread>, ["~> 0.1.3"])
|
42
42
|
s.add_runtime_dependency('get_process_mem', ["~> 0.2.0"])
|
43
|
-
s.add_development_dependency(%q<rspec>, ["~> 3.3
|
43
|
+
s.add_development_dependency(%q<rspec>, ["~> 3.3"])
|
44
44
|
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
45
45
|
s.add_development_dependency(%q<bundler>, [">= 0"])
|
46
46
|
end
|
47
|
-
|
data/lib/chore.rb
CHANGED
@@ -63,6 +63,24 @@ module Chore #:nodoc:
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
+
def self.log_level_to_sym
|
67
|
+
return self.config[:log_level] if self.config[:log_level].is_a?(Symbol)
|
68
|
+
case self.config[:log_level]
|
69
|
+
when 0
|
70
|
+
:debug
|
71
|
+
when 1
|
72
|
+
:info
|
73
|
+
when 2
|
74
|
+
:warn
|
75
|
+
when 3
|
76
|
+
:error
|
77
|
+
when 4
|
78
|
+
:fatal
|
79
|
+
else
|
80
|
+
:unknown
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
66
84
|
# Reopens any open files. This will match any logfile that was opened by Chore,
|
67
85
|
# Rails, or any other library.
|
68
86
|
def self.reopen_logs
|
@@ -218,6 +236,8 @@ module Chore #:nodoc:
|
|
218
236
|
end
|
219
237
|
|
220
238
|
# List of queue_names as configured via Chore::Job including their prefix, if set.
|
239
|
+
#
|
240
|
+
# @return [Array<String>]
|
221
241
|
def self.prefixed_queue_names
|
222
242
|
Chore::Job.job_classes.collect {|klass| c = constantize(klass); c.prefixed_queue_name}
|
223
243
|
end
|
data/lib/chore/cli.rb
CHANGED
@@ -92,8 +92,8 @@ module Chore #:nodoc:
|
|
92
92
|
validate_strategy!
|
93
93
|
end
|
94
94
|
|
95
|
-
|
96
95
|
private
|
96
|
+
|
97
97
|
def setup_options #:nodoc:
|
98
98
|
register_option "queues", "-q", "--queues QUEUE1,QUEUE2", "Names of queues to process (default: all known)" do |arg|
|
99
99
|
# This will remove duplicates. We ultimately force this to be a Set further below
|
@@ -289,4 +289,3 @@ module Chore #:nodoc:
|
|
289
289
|
end
|
290
290
|
end
|
291
291
|
end
|
292
|
-
|
data/lib/chore/configuration.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Chore
|
2
2
|
# Wrapper around an OpenStruct to define configuration data
|
3
|
-
#
|
3
|
+
# TODO: Add required opts, and validate that they're set
|
4
4
|
class Configuration < OpenStruct
|
5
5
|
# Helper method to make merging Hashes into OpenStructs easier
|
6
6
|
def merge_hash(hsh={})
|
data/lib/chore/consumer.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
module Chore
|
2
2
|
# Raised when Chore is booting up, but encounters a set of configuration that is impossible to boot from. Typically
|
3
|
-
# you'll find additional information around the cause of the exception by examining the logfiles
|
3
|
+
# you'll find additional information around the cause of the exception by examining the logfiles.
|
4
|
+
# You can raise this exception if your queue is in a terrible state and must shut down.
|
4
5
|
class TerribleMistake < Exception
|
5
|
-
# You can raise this exception if your queue is in a terrible state and must shut down
|
6
6
|
end
|
7
7
|
|
8
|
-
# Base class for a Chore Consumer. Provides the
|
9
|
-
# Chore Consumers.
|
8
|
+
# Base class for a Chore Consumer. Provides the interface that a Chore::Consumer implementation should adhere to.
|
10
9
|
class Consumer
|
11
10
|
|
12
11
|
attr_accessor :queue_name
|
13
12
|
|
13
|
+
# @param [String] queue_name Name of queue to be consumed from
|
14
|
+
# @param [Hash] opts
|
14
15
|
def initialize(queue_name, opts={})
|
15
16
|
@queue_name = queue_name
|
16
17
|
@running = true
|
@@ -24,18 +25,25 @@ module Chore
|
|
24
25
|
# Consume takes a block with an arity of two. The two params are
|
25
26
|
# |message_id,message_body| where message_id is any object that the
|
26
27
|
# consumer will need to be able to act on a message later (reject, complete, etc)
|
27
|
-
|
28
|
+
#
|
29
|
+
# @param [Block] &handler Message handler, used by the calling context (worker) to create & assigns a UnitOfWork
|
30
|
+
def consume(&handler)
|
28
31
|
raise NotImplementedError
|
29
32
|
end
|
30
33
|
|
31
34
|
# Reject should put a message back on a queue to be processed again later. It takes
|
32
35
|
# a message_id as returned via consume.
|
36
|
+
#
|
37
|
+
# @param [String] message_id Unique ID of the message
|
33
38
|
def reject(message_id)
|
34
39
|
raise NotImplementedError
|
35
40
|
end
|
36
41
|
|
37
|
-
# Complete should mark a message as finished.
|
38
|
-
|
42
|
+
# Complete should mark a message as finished.
|
43
|
+
#
|
44
|
+
# @param [String] message_id Unique ID of the message
|
45
|
+
# @param [Hash] receipt_handle Unique ID of the consuming transaction in non-filesystem implementations
|
46
|
+
def complete(message_id, receipt_handle)
|
39
47
|
raise NotImplementedError
|
40
48
|
end
|
41
49
|
|
@@ -45,23 +53,47 @@ module Chore
|
|
45
53
|
end
|
46
54
|
|
47
55
|
# Returns true if the Consumer is currently running
|
56
|
+
#
|
57
|
+
# @return [TrueClass, FalseClass]
|
48
58
|
def running?
|
49
59
|
@running
|
50
60
|
end
|
51
61
|
|
52
|
-
#
|
62
|
+
# Returns up to n work
|
63
|
+
#
|
64
|
+
# @param n
|
53
65
|
def provide_work(n)
|
54
66
|
raise NotImplementedError
|
55
67
|
end
|
56
68
|
|
57
|
-
#
|
69
|
+
# Determine whether or not we have already seen this message
|
70
|
+
#
|
71
|
+
# @param [String] dedupe_key
|
72
|
+
# @param [Class] klass
|
73
|
+
# @param [Integer] queue_timeout
|
74
|
+
#
|
75
|
+
# @return [TrueClass, FalseClass]
|
58
76
|
def duplicate_message?(dedupe_key, klass, queue_timeout)
|
59
77
|
dupe_detector.found_duplicate?(:id=>dedupe_key, :queue=>klass.to_s, :visibility_timeout=>queue_timeout)
|
60
78
|
end
|
61
79
|
|
80
|
+
# Instance of duplicate detection implementation class
|
81
|
+
#
|
82
|
+
# @return [DuplicateDetector]
|
62
83
|
def dupe_detector
|
63
84
|
@dupes ||= DuplicateDetector.new({:servers => Chore.config.dedupe_servers,
|
64
85
|
:dupe_on_cache_failure => false})
|
65
86
|
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
# Gets messages from queue implementation and invokes the provided block over each one. Afterwards, the :on_fetch
|
91
|
+
# hook will be invoked per message. This block call provides data necessary for the worker (calling context) to
|
92
|
+
# populate a UnitOfWork struct.
|
93
|
+
#
|
94
|
+
# @param [Block] &handler Message handler, passed along by #consume
|
95
|
+
def handle_messages(&handler)
|
96
|
+
raise NotImplementedError
|
97
|
+
end
|
66
98
|
end
|
67
99
|
end
|
data/lib/chore/job.rb
CHANGED
data/lib/chore/publisher.rb
CHANGED
@@ -1,26 +1,42 @@
|
|
1
1
|
module Chore
|
2
|
-
# Base class for Chore
|
2
|
+
# Base class for a Chore Publisher. Provides the interface that a Chore::Publisher implementation should adhere to.
|
3
3
|
class Publisher
|
4
4
|
DEFAULT_OPTIONS = { :encoder => Encoder::JsonEncoder }
|
5
5
|
|
6
6
|
attr_accessor :options
|
7
7
|
|
8
|
+
# @param [Hash] opts
|
8
9
|
def initialize(opts={})
|
9
10
|
self.options = DEFAULT_OPTIONS.merge(opts)
|
10
11
|
end
|
11
12
|
|
12
13
|
# Publishes the provided +job+ to the queue identified by the +queue_name+. Not designed to be used directly, this
|
13
14
|
# method ferries to the publish method on an instance of your configured Publisher.
|
15
|
+
#
|
16
|
+
# @param [String] queue_name Name of queue to be consumed from
|
17
|
+
# @param [Hash] job Job instance definition, will be encoded to JSON
|
14
18
|
def self.publish(queue_name,job)
|
15
19
|
self.new.publish(queue_name,job)
|
16
20
|
end
|
17
21
|
|
18
|
-
#
|
22
|
+
# Publishes a message to queue
|
23
|
+
#
|
24
|
+
# @param [String] queue_name Name of the SQS queue
|
25
|
+
# @param [Hash] job Job instance definition, will be encoded to JSON
|
19
26
|
def publish(queue_name,job)
|
20
27
|
raise NotImplementedError
|
21
28
|
end
|
29
|
+
|
30
|
+
# Sets a flag that instructs the publisher to reset the connection the next time it's used.
|
31
|
+
# Should be overriden in publishers (but is not required)
|
32
|
+
def self.reset_connection!
|
33
|
+
end
|
34
|
+
|
22
35
|
protected
|
23
36
|
|
37
|
+
# Encodes the job class to format provided by endoder implementation
|
38
|
+
#
|
39
|
+
# @param [Any] job
|
24
40
|
def encode_job(job)
|
25
41
|
options[:encoder].encode(job)
|
26
42
|
end
|
@@ -15,12 +15,12 @@ module Chore
|
|
15
15
|
# Once complete job files are deleted.
|
16
16
|
# If rejected they are moved back into new and will be processed again. This may not be the
|
17
17
|
# desired behavior long term and we may want to add configuration to this class to allow more
|
18
|
-
# creating failure handling and retrying.
|
18
|
+
# creating failure handling and retrying.
|
19
19
|
class Consumer < Chore::Consumer
|
20
20
|
extend FilesystemQueue
|
21
21
|
|
22
22
|
Chore::CLI.register_option 'fs_queue_root', '--fs-queue-root DIRECTORY', 'Root directory for fs based queue'
|
23
|
-
|
23
|
+
|
24
24
|
class << self
|
25
25
|
# Cleans up expired in-progress files by making them new again.
|
26
26
|
def cleanup(expiration_time, new_dir, in_progress_dir)
|
@@ -51,7 +51,7 @@ module Chore
|
|
51
51
|
|
52
52
|
# If the file is non-zero, this means it was successfully written to
|
53
53
|
# by a publisher and we can attempt to move it to "in progress".
|
54
|
-
#
|
54
|
+
#
|
55
55
|
# There is a small window of time where the file can be zero, but
|
56
56
|
# the publisher hasn't finished writing to the file yet.
|
57
57
|
if !File.zero?(from)
|
@@ -68,7 +68,7 @@ module Chore
|
|
68
68
|
# The file is empty (zero bytes) and enough time has passed since
|
69
69
|
# the file was written that we can safely assume it will never
|
70
70
|
# get written to be the publisher.
|
71
|
-
#
|
71
|
+
#
|
72
72
|
# The scenario where this happens is when the publisher created
|
73
73
|
# the file, but the process was killed before it had a chance to
|
74
74
|
# actually write the data.
|
@@ -114,7 +114,7 @@ module Chore
|
|
114
114
|
|
115
115
|
# The minimum number of seconds to allow to pass between checks for expired
|
116
116
|
# jobs on the filesystem.
|
117
|
-
#
|
117
|
+
#
|
118
118
|
# Since queue times are measured on the order of seconds, 1 second is the
|
119
119
|
# smallest duration. It also prevents us from burning a lot of CPU looking
|
120
120
|
# at expired jobs when the consumer sleep interval is less than 1 second.
|
@@ -144,7 +144,7 @@ module Chore
|
|
144
144
|
end
|
145
145
|
|
146
146
|
found_files = false
|
147
|
-
|
147
|
+
handle_messages do |*args|
|
148
148
|
found_files = true
|
149
149
|
yield(*args)
|
150
150
|
end
|
@@ -161,9 +161,14 @@ module Chore
|
|
161
161
|
|
162
162
|
end
|
163
163
|
|
164
|
-
|
165
|
-
|
166
|
-
|
164
|
+
# Deletes the given message from filesystem queue. Since the filesystem is not a remote API, there is no
|
165
|
+
# notion of a "receipt handle".
|
166
|
+
#
|
167
|
+
# @param [String] message_id Unique ID of the message
|
168
|
+
# @param [Hash] receipt_handle Receipt handle of the message. Always nil for the filesystem consumer
|
169
|
+
def complete(message_id, receipt_handle = nil)
|
170
|
+
Chore.logger.debug "Completing (deleting): #{message_id}"
|
171
|
+
File.delete(File.join(@in_progress_dir, message_id))
|
167
172
|
rescue Errno::ENOENT
|
168
173
|
# The job took too long to complete, was deemed expired, and moved
|
169
174
|
# back into "new". Ignore.
|
@@ -173,7 +178,7 @@ module Chore
|
|
173
178
|
|
174
179
|
# finds all new job files, moves them to in progress and starts the job
|
175
180
|
# Returns a list of the job files processed
|
176
|
-
def
|
181
|
+
def handle_messages(&block)
|
177
182
|
self.class.each_file(@new_dir, Chore.config.queue_polling_size) do |job_file|
|
178
183
|
Chore.logger.debug "Found a new job #{job_file}"
|
179
184
|
|
@@ -186,8 +191,9 @@ module Chore
|
|
186
191
|
job_json = File.read(in_progress_path)
|
187
192
|
basename, previous_attempts, * = self.class.file_info(job_file)
|
188
193
|
|
189
|
-
# job_file is just the name which is the job id
|
190
|
-
|
194
|
+
# job_file is just the name which is the job id. 2nd argument (:receipt_handle) is nil because the
|
195
|
+
# filesystem is dealt with directly, as opposed to being an external API
|
196
|
+
block.call(job_file, nil, queue_name, queue_timeout, job_json, previous_attempts)
|
191
197
|
Chore.run_hooks_for(:on_fetch, job_file, job_json)
|
192
198
|
end
|
193
199
|
end
|
@@ -203,4 +209,3 @@ module Chore
|
|
203
209
|
end
|
204
210
|
end
|
205
211
|
end
|
206
|
-
|
@@ -11,7 +11,7 @@ module Chore
|
|
11
11
|
include FilesystemQueue
|
12
12
|
|
13
13
|
# use of mutex and file locking should make this both threadsafe and safe for multiple
|
14
|
-
# processes to use the same queue directory simultaneously.
|
14
|
+
# processes to use the same queue directory simultaneously.
|
15
15
|
def publish(queue_name,job)
|
16
16
|
# First try encoding the job to avoid writing empty job files if this fails
|
17
17
|
encoded_job = encode_job(job)
|
data/lib/chore/queues/sqs.rb
CHANGED
@@ -1,10 +1,18 @@
|
|
1
1
|
module Chore
|
2
2
|
module Queues
|
3
3
|
module SQS
|
4
|
+
def self.sqs_client
|
5
|
+
Aws::SQS::Client.new(logger: Chore.logger, log_level: Chore.log_level_to_sym)
|
6
|
+
end
|
7
|
+
|
4
8
|
# Helper method to create queues based on the currently known list as provided by your configured Chore::Jobs
|
5
9
|
# This is meant to be invoked from a rake task, and not directly.
|
6
10
|
# These queues will be created with the default settings, which may not be ideal.
|
7
11
|
# This is meant only as a convenience helper for testing, and not as a way to create production quality queues in SQS
|
12
|
+
#
|
13
|
+
# @param [TrueClass, FalseClass] halt_on_existing Raise an exception if the queue already exists
|
14
|
+
#
|
15
|
+
# @return [Array<String>]
|
8
16
|
def self.create_queues!(halt_on_existing=false)
|
9
17
|
raise 'You must have atleast one Chore Job configured and loaded before attempting to create queues' unless Chore.prefixed_queue_names.length > 0
|
10
18
|
|
@@ -20,49 +28,50 @@ module Chore
|
|
20
28
|
end
|
21
29
|
end
|
22
30
|
|
23
|
-
#This will raise an exception if AWS has not been configured by the project making use of Chore
|
24
|
-
sqs_queues = AWS::SQS.new.queues
|
25
31
|
Chore.prefixed_queue_names.each do |queue_name|
|
26
32
|
Chore.logger.info "Chore Creating Queue: #{queue_name}"
|
27
33
|
begin
|
28
|
-
|
29
|
-
rescue
|
34
|
+
sqs_client.create_queue(queue_name: queue_name)
|
35
|
+
rescue Aws::SQS::Errors::QueueAlreadyExists
|
30
36
|
Chore.logger.info "exists with different config"
|
31
37
|
end
|
32
38
|
end
|
39
|
+
|
33
40
|
Chore.prefixed_queue_names
|
34
41
|
end
|
35
42
|
|
36
43
|
# Helper method to delete all known queues based on the list as provided by your configured Chore::Jobs
|
37
44
|
# This is meant to be invoked from a rake task, and not directly.
|
45
|
+
#
|
46
|
+
# @return [Array<String>]
|
47
|
+
|
38
48
|
def self.delete_queues!
|
39
49
|
raise 'You must have atleast one Chore Job configured and loaded before attempting to create queues' unless Chore.prefixed_queue_names.length > 0
|
40
|
-
|
41
|
-
sqs_queues = AWS::SQS.new.queues
|
50
|
+
|
42
51
|
Chore.prefixed_queue_names.each do |queue_name|
|
43
52
|
begin
|
44
53
|
Chore.logger.info "Chore Deleting Queue: #{queue_name}"
|
45
|
-
url =
|
46
|
-
|
54
|
+
url = sqs_client.get_queue_url(queue_name: queue_name).queue_url
|
55
|
+
sqs_client.delete_queue(queue_url: url)
|
47
56
|
rescue => e
|
48
57
|
# This could fail for a few reasons - log out why it failed, then continue on
|
49
58
|
Chore.logger.error "Deleting Queue: #{queue_name} failed because #{e}"
|
50
59
|
end
|
51
60
|
end
|
61
|
+
|
52
62
|
Chore.prefixed_queue_names
|
53
63
|
end
|
54
64
|
|
55
65
|
# Collect a list of queues that already exist
|
66
|
+
#
|
67
|
+
# @return [Array<String>]
|
56
68
|
def self.existing_queues
|
57
|
-
#This will raise an exception if AWS has not been configured by the project making use of Chore
|
58
|
-
sqs_queues = AWS::SQS.new.queues
|
59
|
-
|
60
69
|
Chore.prefixed_queue_names.select do |queue_name|
|
61
70
|
# If the NonExistentQueue exception is raised we do not care about that queue name.
|
62
71
|
begin
|
63
|
-
|
72
|
+
sqs_client.get_queue_url(queue_name: queue_name)
|
64
73
|
true
|
65
|
-
rescue
|
74
|
+
rescue Aws::SQS::Errors::NonExistentQueue
|
66
75
|
false
|
67
76
|
end
|
68
77
|
end
|
@@ -1,9 +1,6 @@
|
|
1
|
-
require 'aws
|
1
|
+
require 'aws-sdk-sqs'
|
2
2
|
require 'chore/duplicate_detector'
|
3
3
|
|
4
|
-
AWS.eager_autoload! AWS::Core
|
5
|
-
AWS.eager_autoload! AWS::SQS
|
6
|
-
|
7
4
|
module Chore
|
8
5
|
module Queues
|
9
6
|
module SQS
|
@@ -17,24 +14,29 @@ module Chore
|
|
17
14
|
Chore::CLI.register_option 'aws_secret_key', '--aws-secret-key KEY', 'Valid AWS Secret Key'
|
18
15
|
Chore::CLI.register_option 'dedupe_servers', '--dedupe-servers SERVERS', 'List of mememcache compatible server(s) to use for storing SQS Message Dedupe cache'
|
19
16
|
|
17
|
+
# @param [String] queue_name Name of SQS queue
|
18
|
+
# @param [Hash] opts Options
|
20
19
|
def initialize(queue_name, opts={})
|
21
20
|
super(queue_name, opts)
|
22
|
-
|
23
21
|
raise Chore::TerribleMistake, "Cannot specify a queue polling size greater than 10" if sqs_polling_amount > 10
|
24
22
|
end
|
25
23
|
|
26
|
-
#
|
24
|
+
# Resets the API client connection and provides @@reset_at so we know when the last time that was done
|
27
25
|
def self.reset_connection!
|
28
26
|
@@reset_at = Time.now
|
29
27
|
end
|
30
28
|
|
31
29
|
# Begins requesting messages from SQS, which will invoke the +&handler+ over each message
|
30
|
+
#
|
31
|
+
# @param [Block] &handler Message handler, used by the calling context (worker) to create & assigns a UnitOfWork
|
32
|
+
#
|
33
|
+
# @return [Array<Aws::SQS::Message>]
|
32
34
|
def consume(&handler)
|
33
35
|
while running?
|
34
36
|
begin
|
35
37
|
messages = handle_messages(&handler)
|
36
38
|
sleep (Chore.config.consumer_sleep_interval) if messages.empty?
|
37
|
-
rescue
|
39
|
+
rescue Aws::SQS::Errors::NonExistentQueue => e
|
38
40
|
Chore.logger.error "You specified a queue '#{queue_name}' that does not exist. You must create the queue before starting Chore. Shutting down..."
|
39
41
|
raise Chore::TerribleMistake
|
40
42
|
rescue => e
|
@@ -43,21 +45,34 @@ module Chore
|
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
46
|
-
# Rejects the given message from SQS
|
47
|
-
|
48
|
-
|
48
|
+
# Unimplemented. Rejects the given message from SQS.
|
49
|
+
#
|
50
|
+
# @param [String] message_id Unique ID of the SQS message
|
51
|
+
#
|
52
|
+
# @return nil
|
53
|
+
def reject(message_id)
|
49
54
|
end
|
50
55
|
|
51
|
-
# Deletes the given message from SQS
|
52
|
-
|
53
|
-
|
54
|
-
|
56
|
+
# Deletes the given message from the SQS queue
|
57
|
+
#
|
58
|
+
# @param [String] message_id Unique ID of the SQS message
|
59
|
+
# @param [Hash] receipt_handle Receipt handle (unique per consume request) of the SQS message
|
60
|
+
def complete(message_id, receipt_handle)
|
61
|
+
Chore.logger.debug "Completing (deleting): #{message_id}"
|
62
|
+
queue.delete_messages(entries: [{ id: message_id, receipt_handle: receipt_handle }])
|
55
63
|
end
|
56
64
|
|
65
|
+
# Delays retry of a job by +backoff_calc+ seconds.
|
66
|
+
#
|
67
|
+
# @param [UnitOfWork] item Item to be delayed
|
68
|
+
# @param [Block] backoff_calc Code that determines the backoff.
|
57
69
|
def delay(item, backoff_calc)
|
58
70
|
delay = backoff_calc.call(item)
|
59
71
|
Chore.logger.debug "Delaying #{item.id} by #{delay} seconds"
|
60
|
-
|
72
|
+
|
73
|
+
queue.change_message_visibility_batch(entries: [
|
74
|
+
{ id: item.id, receipt_handle: item.receipt_handle, visibility_timeout: delay },
|
75
|
+
])
|
61
76
|
|
62
77
|
return delay
|
63
78
|
end
|
@@ -66,46 +81,59 @@ module Chore
|
|
66
81
|
|
67
82
|
# Requests messages from SQS, and invokes the provided +&block+ over each one. Afterwards, the :on_fetch
|
68
83
|
# hook will be invoked, per message
|
84
|
+
#
|
85
|
+
# @param [Block] &handler Message handler, passed along by #consume
|
86
|
+
#
|
87
|
+
# @return [Array<Aws::SQS::Message>]
|
69
88
|
def handle_messages(&block)
|
70
|
-
msg = queue.receive_messages(:
|
89
|
+
msg = queue.receive_messages(:max_number_of_messages => sqs_polling_amount, :attribute_names => ['ApproximateReceiveCount'])
|
71
90
|
messages = *msg
|
91
|
+
|
72
92
|
messages.each do |message|
|
73
|
-
unless duplicate_message?(message.
|
74
|
-
block.call(message.
|
93
|
+
unless duplicate_message?(message.message_id, message.queue_url, queue_timeout)
|
94
|
+
block.call(message.message_id, message.receipt_handle, queue_name, queue_timeout, message.body, message.attributes['ApproximateReceiveCount'].to_i - 1)
|
75
95
|
end
|
76
|
-
Chore.run_hooks_for(:on_fetch, message.
|
96
|
+
Chore.run_hooks_for(:on_fetch, message.receipt_handle, message.body)
|
77
97
|
end
|
98
|
+
|
78
99
|
messages
|
79
100
|
end
|
80
101
|
|
81
|
-
# Retrieves the SQS queue
|
82
|
-
#
|
83
|
-
#
|
102
|
+
# Retrieves the SQS queue object. The method will cache the results to prevent round trips on subsequent calls
|
103
|
+
#
|
104
|
+
# If <tt>reset_connection!</tt> has been called, this will result in the connection being re-initialized,
|
105
|
+
# as well as clear any cached results from prior calls
|
106
|
+
#
|
107
|
+
# @return [Aws::SQS::Queue]
|
84
108
|
def queue
|
85
109
|
if !@sqs_last_connected || (@@reset_at && @@reset_at >= @sqs_last_connected)
|
86
|
-
|
87
|
-
p.empty!
|
88
|
-
end
|
110
|
+
Aws.empty_connection_pools!
|
89
111
|
@sqs = nil
|
90
112
|
@sqs_last_connected = Time.now
|
91
113
|
@queue = nil
|
92
114
|
end
|
93
|
-
|
94
|
-
@
|
115
|
+
|
116
|
+
@queue_url ||= sqs.get_queue_url(queue_name: @queue_name).queue_url
|
117
|
+
@queue ||= Aws::SQS::Queue.new(url: @queue_url, client: sqs)
|
95
118
|
end
|
96
119
|
|
97
|
-
# The visibility timeout of the queue
|
120
|
+
# The visibility timeout (in seconds) of the queue
|
121
|
+
#
|
122
|
+
# @return [Integer]
|
98
123
|
def queue_timeout
|
99
|
-
@queue_timeout ||= queue.
|
124
|
+
@queue_timeout ||= queue.attributes['VisibilityTimeout'].to_i
|
100
125
|
end
|
101
126
|
|
102
|
-
#
|
127
|
+
# SQS API client object
|
128
|
+
#
|
129
|
+
# @return [Aws::SQS::Client]
|
103
130
|
def sqs
|
104
|
-
@sqs ||=
|
105
|
-
:access_key_id => Chore.config.aws_access_key,
|
106
|
-
:secret_access_key => Chore.config.aws_secret_key)
|
131
|
+
@sqs ||= Chore::Queues::SQS.sqs_client
|
107
132
|
end
|
108
133
|
|
134
|
+
# Maximum number of messages to retrieve on each request
|
135
|
+
#
|
136
|
+
# @return [Integer]
|
109
137
|
def sqs_polling_amount
|
110
138
|
Chore.config.queue_polling_size
|
111
139
|
end
|