shoryuken 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c8c88a264b5df0c11cbcc440790224279c70176e
4
- data.tar.gz: 57f9b92ee36f20d9cb385ea3b5a538f0bfd42f0d
3
+ metadata.gz: cbdf9963098403163b107d51bab8dc70384b9d92
4
+ data.tar.gz: 97ccedcd84c67dda49c66f96a21899ee5a2ce215
5
5
  SHA512:
6
- metadata.gz: e8105e79af153637fb922ee9768aee12ddd69a9bb251229975f0aa54e19f277b9b06c3cbb1ee54d3ff2f865aef6adeeab0389a6e2c40225ceaa6714ae63ee527
7
- data.tar.gz: 10e3cb7ffdab9d525bb2db06021e319ef56b18bdb81ce795d548f3a3f17959887df69cb9e642046f579d2040870ef1d8fdcc1e28a6256b4fbc5331eebc966b88
6
+ metadata.gz: a323f68eff71aa37fe470239fbcd1c67210200dc999d0d1e34f4b61e68c4da38f745613cf294d0b238708980e555875afa66d713c3ea2649468a39177dc96437
7
+ data.tar.gz: 8f7f72e1b4377614e9f7a01c5917b8e0601d59bb8e7669ee2258c7ca8b0adbeeae8f8ea2e5b5130122dcb4b546c671e4f95e295d2dd8bb697cfc0a1fa8a99ff5
data/Gemfile CHANGED
@@ -3,4 +3,7 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in shoryuken.gemspec
4
4
  gemspec
5
5
 
6
- gem 'codeclimate-test-reporter', group: :test, require: nil
6
+ group :test do
7
+ gem 'codeclimate-test-reporter', require: nil
8
+ gem 'multi_xml'
9
+ end
data/README.md CHANGED
@@ -114,7 +114,7 @@ aws:
114
114
  access_key_id: ... # or <%= ENV['AWS_ACCESS_KEY_ID'] %>
115
115
  secret_access_key: ... # or <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
116
116
  region: us-east-1 # or <%= ENV['AWS_REGION'] %>
117
- receive_message: # See http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Queue.html#receive_messages-instance_method
117
+ receive_message: # See http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#receive_message-instance_method
118
118
  # wait_time_seconds: N # The number of seconds to wait for new messages when polling. Defaults to the #wait_time_seconds defined on the queue
119
119
  attribute_names:
120
120
  - ApproximateReceiveCount
@@ -132,7 +132,7 @@ The ```aws``` section is used to configure both the Aws objects used by Shoryuke
132
132
  - ```account_id``` is used when generating SNS ARNs
133
133
  - ```sns_endpoint``` can be used to explicitly override the SNS endpoint
134
134
  - ```sqs_endpoint``` can be used to explicitly override the SQS endpoint
135
- - ```receive_message``` can be used to define the options passed to the http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Queue.html#receive_messages-instance_method
135
+ - ```receive_message``` can be used to define the options passed to the http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#receive_message-instance_method
136
136
 
137
137
  ### Rails Integration
138
138
 
@@ -186,6 +186,13 @@ See [ActiveJob docs](http://edgeguides.rubyonrails.org/active_job_basics.html) f
186
186
 
187
187
  *Note:* When queueing jobs to be performed in the future (e.g when setting the `wait` or `wait_until` ActiveJob options), SQS limits the amount of time to 15 minutes. Shoryuken will raise an exception if you attempt to schedule a job further into the future than this limit.
188
188
 
189
+ *Note:* Active Job allows you to [prefix the queue names](http://edgeguides.rubyonrails.org/active_job_basics.html#queues) of all jobs. Shoryuken supports this behavior natively. By default, though, queue names defined in the config file (or passed to the CLI), are not prefixed in the same way. To have Shoryuken honor Active Job prefixes you must enable that option explicitly. A good place to do that in Rails is in an initializer:
190
+
191
+ ```
192
+ # config/initializers/shoryuken.rb
193
+ Shoryuken.active_job_queue_name_prefixing = true
194
+ ```
195
+
189
196
  ### Start Shoryuken
190
197
 
191
198
  ```shell
@@ -29,8 +29,9 @@ module Shoryuken
29
29
  timeout: 8
30
30
  }
31
31
 
32
- @@queues = []
32
+ @@queues = []
33
33
  @@worker_registry = DefaultWorkerRegistry.new
34
+ @@active_job_queue_name_prefixing = false
34
35
 
35
36
  class << self
36
37
  def options
@@ -57,11 +58,14 @@ module Shoryuken
57
58
  @@worker_registry
58
59
  end
59
60
 
60
- # Shoryuken.configure_server do |config|
61
- # config.server_middleware do |chain|
62
- # chain.add MyServerHook
63
- # end
64
- # end
61
+ def active_job_queue_name_prefixing
62
+ @@active_job_queue_name_prefixing
63
+ end
64
+
65
+ def active_job_queue_name_prefixing=(prefixing)
66
+ @@active_job_queue_name_prefixing = prefixing
67
+ end
68
+
65
69
  def configure_server
66
70
  yield self if server?
67
71
  end
@@ -72,6 +76,16 @@ module Shoryuken
72
76
  @server_chain
73
77
  end
74
78
 
79
+ def configure_client
80
+ yield self
81
+ end
82
+
83
+ def client_middleware
84
+ @client_chain ||= default_client_middleware
85
+ yield @client_chain if block_given?
86
+ @client_chain
87
+ end
88
+
75
89
  def default_worker_options
76
90
  @@default_worker_options ||= {
77
91
  'queue' => 'default',
@@ -116,6 +130,10 @@ module Shoryuken
116
130
  end
117
131
  end
118
132
 
133
+ def default_client_middleware
134
+ Middleware::Chain.new
135
+ end
136
+
119
137
  def server?
120
138
  defined?(Shoryuken::CLI)
121
139
  end
@@ -41,7 +41,7 @@ module Shoryuken
41
41
  @launcher = Shoryuken::Launcher.new
42
42
 
43
43
  if callback = Shoryuken.start_callback
44
- logger.info "Calling Shoryuken.on_start block"
44
+ logger.info { 'Calling Shoryuken.on_start block' }
45
45
  callback.call
46
46
  end
47
47
 
@@ -158,7 +158,7 @@ module Shoryuken
158
158
 
159
159
  @parser.banner = 'shoryuken [options]'
160
160
  @parser.on_tail '-h', '--help', 'Show help' do
161
- logger.info @parser
161
+ logger.info { @parser }
162
162
  exit 1
163
163
  end
164
164
  @parser.parse!(argv)
@@ -166,22 +166,22 @@ module Shoryuken
166
166
  end
167
167
 
168
168
  def handle_signal(sig)
169
- logger.info "Got #{sig} signal"
169
+ logger.info { "Got #{sig} signal" }
170
170
 
171
171
  case sig
172
172
  when 'USR1'
173
- logger.info 'Received USR1, will soft shutdown down'
173
+ logger.info { 'Received USR1, will soft shutdown down' }
174
174
 
175
175
  launcher.stop
176
176
 
177
177
  exit 0
178
178
  when 'TTIN'
179
179
  Thread.list.each do |thread|
180
- logger.info "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
180
+ logger.info { "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}" }
181
181
  if thread.backtrace
182
- logger.info thread.backtrace.join("\n")
182
+ logger.info { thread.backtrace.join("\n") }
183
183
  else
184
- logger.info '<no backtrace available>'
184
+ logger.info { '<no backtrace available>' }
185
185
  end
186
186
  end
187
187
 
@@ -189,9 +189,9 @@ module Shoryuken
189
189
  busy = launcher.manager.instance_variable_get(:@busy).size
190
190
  queues = launcher.manager.instance_variable_get(:@queues)
191
191
 
192
- logger.info "Ready: #{ready}, Busy: #{busy}, Active Queues: #{unparse_queues(queues)}"
192
+ logger.info { "Ready: #{ready}, Busy: #{busy}, Active Queues: #{unparse_queues(queues)}" }
193
193
  else
194
- logger.info "Received #{sig}, will shutdown down"
194
+ logger.info { "Received #{sig}, will shutdown down" }
195
195
 
196
196
  raise Interrupt
197
197
  end
@@ -19,6 +19,7 @@ module Shoryuken
19
19
  load_rails if options[:rails]
20
20
  Shoryuken.options.merge!(config_file_options)
21
21
  merge_cli_defined_queues
22
+ prefix_active_job_queue_names
22
23
  Shoryuken.options.merge!(options)
23
24
  parse_queues
24
25
  require_workers
@@ -33,7 +34,7 @@ module Shoryuken
33
34
  def config_file_options
34
35
  if (path = options[:config_file])
35
36
  unless File.exist?(path)
36
- Shoryuken.logger.warn "Config file #{path} does not exist"
37
+ Shoryuken.logger.warn { "Config file #{path} does not exist" }
37
38
  path = nil
38
39
  end
39
40
  end
@@ -48,11 +49,11 @@ module Shoryuken
48
49
  # when not explicit supplied
49
50
  return if Shoryuken.options[:aws].empty?
50
51
 
51
- shoryuken_keys = %i(
52
+ shoryuken_keys = %w(
52
53
  account_id
53
54
  sns_endpoint
54
55
  sqs_endpoint
55
- receive_message)
56
+ receive_message).map(&:to_sym)
56
57
 
57
58
  aws_options = Shoryuken.options[:aws].reject do |k, v|
58
59
  shoryuken_keys.include?(k)
@@ -67,7 +68,7 @@ module Shoryuken
67
68
  aws_options = aws_options.merge(credentials: credentials) if credentials.set?
68
69
 
69
70
  if (callback = Shoryuken.aws_initialization_callback)
70
- Shoryuken.logger.info 'Calling Shoryuken.on_aws_initialization block'
71
+ Shoryuken.logger.info { 'Calling Shoryuken.on_aws_initialization block' }
71
72
  callback.call(aws_options)
72
73
  end
73
74
 
@@ -96,7 +97,7 @@ module Shoryuken
96
97
  require File.expand_path('config/environment.rb')
97
98
  end
98
99
 
99
- Shoryuken.logger.info 'Rails environment loaded'
100
+ Shoryuken.logger.info { 'Rails environment loaded' }
100
101
  end
101
102
 
102
103
  def merge_cli_defined_queues
@@ -111,6 +112,21 @@ module Shoryuken
111
112
  end
112
113
  end
113
114
 
115
+ def prefix_active_job_queue_names
116
+ return unless @options[:rails]
117
+ return unless Shoryuken.active_job_queue_name_prefixing
118
+
119
+ queue_name_prefix = ::ActiveJob::Base.queue_name_prefix
120
+ queue_name_delimiter = ::ActiveJob::Base.queue_name_delimiter
121
+
122
+ # See https://github.com/rails/rails/blob/master/activejob/lib/active_job/queue_name.rb#L27
123
+ Shoryuken.options[:queues].to_a.map! do |queue_name, weight|
124
+ name_parts = [queue_name_prefix.presence, queue_name]
125
+ prefixed_queue_name = name_parts.compact.join(queue_name_delimiter)
126
+ [prefixed_queue_name, weight]
127
+ end
128
+ end
129
+
114
130
  def parse_queue(queue, weight = nil)
115
131
  [weight.to_i, 1].max.times { Shoryuken.queues << queue }
116
132
  end
@@ -125,7 +141,7 @@ module Shoryuken
125
141
  Shoryuken.worker_registry.queues.each do |queue|
126
142
  Shoryuken.worker_registry.workers(queue).each do |worker_class|
127
143
  if worker_class.instance_method(:perform).arity == 1
128
- Shoryuken.logger.warn "[DEPRECATION] #{worker_class.name}#perform(sqs_msg) is deprecated. Please use #{worker_class.name}#perform(sqs_msg, body)"
144
+ Shoryuken.logger.warn { "[DEPRECATION] #{worker_class.name}#perform(sqs_msg) is deprecated. Please use #{worker_class.name}#perform(sqs_msg, body)" }
129
145
 
130
146
  worker_class.class_eval do
131
147
  alias_method :deprecated_perform, :perform
@@ -144,13 +160,13 @@ module Shoryuken
144
160
  end
145
161
 
146
162
  def validate_queues
147
- Shoryuken.logger.warn 'No queues supplied' if Shoryuken.queues.empty?
163
+ Shoryuken.logger.warn { 'No queues supplied' } if Shoryuken.queues.empty?
148
164
 
149
165
  Shoryuken.queues.uniq.each do |queue|
150
166
  begin
151
167
  Shoryuken::Client.queues queue
152
168
  rescue Aws::SQS::Errors::NonExistentQueue
153
- Shoryuken.logger.warn "AWS Queue '#{queue}' does not exist"
169
+ Shoryuken.logger.warn { "AWS Queue '#{queue}' does not exist" }
154
170
  end
155
171
  end
156
172
  end
@@ -161,7 +177,7 @@ module Shoryuken
161
177
 
162
178
  unless defined?(::ActiveJob)
163
179
  (all_queues - queues_with_workers).each do |queue|
164
- Shoryuken.logger.warn "No worker supplied for '#{queue}'"
180
+ Shoryuken.logger.warn { "No worker supplied for '#{queue}'" }
165
181
  end
166
182
  end
167
183
  end
@@ -13,7 +13,7 @@ module Shoryuken
13
13
  # AWS limits the batch size by 10
14
14
  limit = limit > FETCH_LIMIT ? FETCH_LIMIT : limit
15
15
 
16
- options = Shoryuken.options[:aws][:receive_message].to_h.dup
16
+ options = (Shoryuken.options[:aws][:receive_message] || {}).dup
17
17
  options[:max_number_of_messages] = limit
18
18
  options[:message_attribute_names] = %w(All)
19
19
  options[:attribute_names] = %w(All)
@@ -25,14 +25,14 @@ module Shoryuken
25
25
  watchdog('Fetcher#fetch died') do
26
26
  started_at = Time.now
27
27
 
28
- logger.debug "Looking for new messages in '#{queue}'"
28
+ logger.debug { "Looking for new messages in '#{queue}'" }
29
29
 
30
30
  begin
31
31
  batch = Shoryuken.worker_registry.batch_receive_messages?(queue)
32
32
  limit = batch ? FETCH_LIMIT : available_processors
33
33
 
34
34
  if (sqs_msgs = Array(receive_messages(queue, limit))).any?
35
- logger.info "Found #{sqs_msgs.size} messages for '#{queue}'"
35
+ logger.info { "Found #{sqs_msgs.size} messages for '#{queue}'" }
36
36
 
37
37
  if batch
38
38
  @manager.async.assign(queue, patch_sqs_msgs!(sqs_msgs))
@@ -42,7 +42,7 @@ module Shoryuken
42
42
 
43
43
  @manager.async.rebalance_queue_weight!(queue)
44
44
  else
45
- logger.debug "No message found for '#{queue}'"
45
+ logger.debug { "No message found for '#{queue}'" }
46
46
 
47
47
  @manager.async.pause_queue!(queue)
48
48
  end
@@ -51,8 +51,8 @@ module Shoryuken
51
51
 
52
52
  logger.debug { "Fetcher for '#{queue}' completed in #{elapsed(started_at)} ms" }
53
53
  rescue => ex
54
- logger.error "Error fetching message: #{ex}"
55
- logger.error ex.backtrace.first
54
+ logger.error { "Error fetching message: #{ex}" }
55
+ logger.error { ex.backtrace.first }
56
56
 
57
57
  @manager.async.dispatch
58
58
  end
@@ -36,7 +36,7 @@ module Shoryuken
36
36
 
37
37
  def actor_died(actor, reason)
38
38
  return if @done
39
- logger.warn 'Shoryuken died due to the following error, cannot recover, process exiting'
39
+ logger.warn { 'Shoryuken died due to the following error, cannot recover, process exiting' }
40
40
  exit 1
41
41
  end
42
42
  end
@@ -23,7 +23,7 @@ module Shoryuken
23
23
  end
24
24
 
25
25
  def start
26
- logger.info 'Starting'
26
+ logger.info { 'Starting' }
27
27
 
28
28
  dispatch
29
29
  end
@@ -33,7 +33,7 @@ module Shoryuken
33
33
  @done = true
34
34
 
35
35
  if (callback = Shoryuken.stop_callback)
36
- logger.info 'Calling Shoryuken.on_stop block'
36
+ logger.info { 'Calling Shoryuken.on_stop block' }
37
37
  callback.call
38
38
  end
39
39
 
@@ -58,7 +58,7 @@ module Shoryuken
58
58
 
59
59
  def processor_done(queue, processor)
60
60
  watchdog('Manager#processor_done died') do
61
- logger.info "Process done for '#{queue}'"
61
+ logger.debug { "Process done for '#{queue}'" }
62
62
 
63
63
  @threads.delete(processor.object_id)
64
64
  @busy.delete processor
@@ -73,7 +73,7 @@ module Shoryuken
73
73
 
74
74
  def processor_died(processor, reason)
75
75
  watchdog("Manager#processor_died died") do
76
- logger.error "Process died, reason: #{reason}" unless reason.to_s.empty?
76
+ logger.error { "Process died, reason: #{reason}" unless reason.to_s.empty? }
77
77
 
78
78
  @threads.delete(processor.object_id)
79
79
  @busy.delete processor
@@ -90,7 +90,7 @@ module Shoryuken
90
90
 
91
91
  def assign(queue, sqs_msg)
92
92
  watchdog('Manager#assign died') do
93
- logger.info "Assigning #{sqs_msg.message_id}"
93
+ logger.debug { "Assigning #{sqs_msg.message_id}" }
94
94
 
95
95
  processor = @ready.pop
96
96
  @busy << processor
@@ -102,7 +102,7 @@ module Shoryuken
102
102
  def rebalance_queue_weight!(queue)
103
103
  watchdog('Manager#rebalance_queue_weight! died') do
104
104
  if (original = original_queue_weight(queue)) > (current = current_queue_weight(queue))
105
- logger.info "Increasing '#{queue}' weight to #{current + 1}, max: #{original}"
105
+ logger.info { "Increasing '#{queue}' weight to #{current + 1}, max: #{original}" }
106
106
 
107
107
  @queues << queue
108
108
  end
@@ -112,7 +112,7 @@ module Shoryuken
112
112
  def pause_queue!(queue)
113
113
  return if !@queues.include?(queue) || Shoryuken.options[:delay].to_f <= 0
114
114
 
115
- logger.debug "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because it's empty"
115
+ logger.debug { "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because it's empty" }
116
116
 
117
117
  @queues.delete(queue)
118
118
 
@@ -158,7 +158,7 @@ module Shoryuken
158
158
  return if stopped?
159
159
 
160
160
  unless @queues.include? queue
161
- logger.debug "Restarting '#{queue}'"
161
+ logger.debug { "Restarting '#{queue}'" }
162
162
 
163
163
  @queues << queue
164
164
 
@@ -192,7 +192,7 @@ module Shoryuken
192
192
 
193
193
  unless defined?(::ActiveJob) || !Shoryuken.worker_registry.workers(queue).empty?
194
194
  # when no worker registered pause the queue to avoid endless recursion
195
- logger.debug "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because no workers registered"
195
+ logger.debug { "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because no workers registered" }
196
196
 
197
197
  after(Shoryuken.options[:delay].to_f) { async.restart_queue!(queue) }
198
198
 
@@ -117,7 +117,7 @@ module Shoryuken
117
117
 
118
118
  def patch_deprecated_middleware!(klass)
119
119
  if klass.instance_method(:call).arity == 3
120
- Shoryuken.logger.warn "[DEPRECATION] #{klass.name}#call(worker_instance, queue, sqs_msg) is deprecated. Please use #{klass.name}#call(worker_instance, queue, sqs_msg, body)"
120
+ Shoryuken.logger.warn { "[DEPRECATION] #{klass.name}#call(worker_instance, queue, sqs_msg) is deprecated. Please use #{klass.name}#call(worker_instance, queue, sqs_msg, body)" }
121
121
 
122
122
  klass.class_eval do
123
123
  alias_method :deprecated_call, :call
@@ -42,7 +42,7 @@ module Shoryuken
42
42
 
43
43
  sqs_msg.change_visibility(visibility_timeout: interval.to_i)
44
44
 
45
- logger.info "Message #{sqs_msg.message_id} failed, will be retried in #{interval} seconds."
45
+ logger.info { "Message #{sqs_msg.message_id} failed, will be retried in #{interval} seconds." }
46
46
  end
47
47
  end
48
48
  end
@@ -9,19 +9,19 @@ module Shoryuken
9
9
  begin
10
10
  started_at = Time.now
11
11
 
12
- logger.info "started at #{started_at}"
12
+ logger.info { "started at #{started_at}" }
13
13
 
14
14
  yield
15
15
 
16
16
  total_time = elapsed(started_at)
17
17
 
18
18
  if (total_time / 1000.0) > (timeout = Shoryuken::Client.queues(queue).visibility_timeout)
19
- logger.warn "exceeded the queue visibility timeout by #{total_time - (timeout * 1000)} ms"
19
+ logger.warn { "exceeded the queue visibility timeout by #{total_time - (timeout * 1000)} ms" }
20
20
  end
21
21
 
22
- logger.info "completed in: #{total_time} ms"
22
+ logger.info { "completed in: #{total_time} ms" }
23
23
  rescue => e
24
- logger.info "failed in: #{elapsed(started_at)} ms"
24
+ logger.info { "failed in: #{elapsed(started_at)} ms" }
25
25
  raise e
26
26
  end
27
27
  end
@@ -19,12 +19,10 @@ module Shoryuken
19
19
  timer = auto_visibility_timeout(queue, sqs_msg, worker.class)
20
20
 
21
21
  begin
22
- defer do
23
- body = get_body(worker.class, sqs_msg)
22
+ body = get_body(worker.class, sqs_msg)
24
23
 
25
- worker.class.server_middleware.invoke(worker, queue, sqs_msg, body) do
26
- worker.perform(sqs_msg, body)
27
- end
24
+ worker.class.server_middleware.invoke(worker, queue, sqs_msg, body) do
25
+ worker.perform(sqs_msg, body)
28
26
  end
29
27
 
30
28
  @manager.async.processor_done(queue, current_actor)
@@ -35,22 +33,31 @@ module Shoryuken
35
33
 
36
34
  private
37
35
 
38
- def auto_visibility_timeout(queue, sqs_msg, worker_class)
39
- if worker_class.auto_visibility_timeout?
36
+ class MessageVisibilityExtender
37
+ include Celluloid
38
+ include Util
39
+
40
+ def auto_extend(queue, sqs_msg, worker_class)
40
41
  queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
41
42
 
42
- timer = every(queue_visibility_timeout - 5) do
43
+ every(queue_visibility_timeout - 5) do
43
44
  begin
44
- logger.debug "Extending message #{worker_name(worker_class, sqs_msg)}/#{queue}/#{sqs_msg.message_id} visibility timeout by #{queue_visibility_timeout}s."
45
+ logger.debug { "Extending message #{worker_name(worker_class, sqs_msg)}/#{queue}/#{sqs_msg.message_id} visibility timeout by #{queue_visibility_timeout}s." }
45
46
 
46
47
  sqs_msg.visibility_timeout = queue_visibility_timeout
47
48
  rescue => e
48
- logger.error "Could not auto extend the message #{worker_class}/#{queue}/#{sqs_msg.message_id} visibility timeout. Error: #{e.message}"
49
+ logger.error { "Could not auto extend the message #{worker_class}/#{queue}/#{sqs_msg.message_id} visibility timeout. Error: #{e.message}" }
49
50
  end
50
51
  end
51
52
  end
53
+ end
52
54
 
53
- timer
55
+ def auto_visibility_timeout(queue, sqs_msg, worker_class)
56
+ return unless worker_class.auto_visibility_timeout?
57
+
58
+ @visibility_extender ||= MessageVisibilityExtender.new_link
59
+
60
+ @visibility_extender.auto_extend(queue, sqs_msg, worker_class)
54
61
  end
55
62
 
56
63
  def get_body(worker_class, sqs_msg)
@@ -72,10 +79,17 @@ module Shoryuken
72
79
  when :text, nil
73
80
  sqs_msg.body
74
81
  else
75
- body_parser.parse(sqs_msg.body) if body_parser.respond_to?(:parse) # i.e. JSON.parse(...)
82
+ if body_parser.respond_to?(:parse)
83
+ # JSON.parse
84
+ body_parser.parse(sqs_msg.body)
85
+ elsif body_parser.respond_to?(:load)
86
+ # see https://github.com/phstc/shoryuken/pull/91
87
+ # JSON.load
88
+ body_parser.load(sqs_msg.body)
89
+ end
76
90
  end
77
91
  rescue => e
78
- logger.error "Error parsing the message body: #{e.message}\nbody_parser: #{body_parser}\nsqs_msg.body: #{sqs_msg.body}"
92
+ logger.error { "Error parsing the message body: #{e.message}\nbody_parser: #{body_parser}\nsqs_msg.body: #{sqs_msg.body}" }
79
93
  nil
80
94
  end
81
95
  end
@@ -20,11 +20,15 @@ module Shoryuken
20
20
  end
21
21
 
22
22
  def send_message(options)
23
- client.send_message(sanitize_message_body(options.merge(queue_url: url)))
23
+ options = sanitize_message!(options).merge(queue_url: url)
24
+
25
+ Shoryuken.client_middleware.invoke(options) do
26
+ client.send_message(options)
27
+ end
24
28
  end
25
29
 
26
30
  def send_messages(options)
27
- client.send_message_batch(sanitize_message_body(options.merge(queue_url: url)))
31
+ client.send_message_batch(sanitize_messages!(options).merge(queue_url: url))
28
32
  end
29
33
 
30
34
  def receive_messages(options)
@@ -35,16 +39,43 @@ module Shoryuken
35
39
 
36
40
  private
37
41
 
38
- def sanitize_message_body(options)
39
- messages = options[:entries] || [options]
42
+ def sanitize_messages!(options)
43
+ options = case
44
+ when options.is_a?(Array)
45
+ { entries: options.map.with_index { |m, index| { id: index.to_s, message_body: m } } }
46
+ when options.is_a?(Hash)
47
+ options
48
+ end
49
+
50
+ validate_messages!(options)
51
+
52
+ options
53
+ end
54
+
55
+ def sanitize_message!(options)
56
+ options = case
57
+ when options.is_a?(String)
58
+ # send_message('message')
59
+ { message_body: options }
60
+ when options.is_a?(Hash)
61
+ options
62
+ end
63
+
64
+ validate_message!(options)
65
+
66
+ options
67
+ end
68
+
69
+ def validate_messages!(options)
70
+ options[:entries].map { |m| validate_message!(m) }
71
+ end
40
72
 
41
- messages.each do |m|
42
- body = m[:message_body]
43
- if body.is_a?(Hash)
44
- m[:message_body] = JSON.dump(body)
45
- elsif !body.is_a? String
46
- fail ArgumentError, "The message body must be a String and you passed a #{body.class}"
47
- end
73
+ def validate_message!(options)
74
+ body = options[:message_body]
75
+ if body.is_a?(Hash)
76
+ options[:message_body] = JSON.dump(body)
77
+ elsif !body.is_a?(String)
78
+ fail ArgumentError, "The message body must be a String and you passed a #{body.class}"
48
79
  end
49
80
 
50
81
  options
@@ -3,9 +3,9 @@ module Shoryuken
3
3
  def watchdog(last_words)
4
4
  yield
5
5
  rescue => ex
6
- logger.error last_words
7
- logger.error ex
8
- logger.error ex.backtrace.join("\n")
6
+ logger.error { last_words }
7
+ logger.error { ex }
8
+ logger.error { ex.backtrace.join("\n") }
9
9
  end
10
10
 
11
11
  def logger
@@ -30,7 +30,7 @@ module Shoryuken
30
30
  && sqs_msg.message_attributes['shoryuken_class'] \
31
31
  && sqs_msg.message_attributes['shoryuken_class'][:string_value] == ActiveJob::QueueAdapters::ShoryukenAdapter::JobWrapper.to_s
32
32
 
33
- "ActiveJob/#{body['job_class'].constantize}"
33
+ "ActiveJob/#{body['job_class']}"
34
34
  else
35
35
  worker_class.to_s
36
36
  end
@@ -1,3 +1,3 @@
1
1
  module Shoryuken
2
- VERSION = '1.0.2'
2
+ VERSION = '1.0.3'
3
3
  end
@@ -39,7 +39,7 @@ module Shoryuken
39
39
  end
40
40
 
41
41
  def shoryuken_options(opts = {})
42
- @shoryuken_options = get_shoryuken_options.merge(stringify_keys(Hash(opts)))
42
+ @shoryuken_options = get_shoryuken_options.merge(stringify_keys(opts || {}))
43
43
  queue = @shoryuken_options['queue']
44
44
  if queue.respond_to? :call
45
45
  queue = queue.call
@@ -58,7 +58,9 @@ describe Shoryuken::Middleware::Chain do
58
58
  it 'patches deprecated middleware' do
59
59
  subject.clear
60
60
 
61
- expect(Shoryuken.logger).to receive(:warn).with("[DEPRECATION] DeprecatedMiddleware#call(worker_instance, queue, sqs_msg) is deprecated. Please use DeprecatedMiddleware#call(worker_instance, queue, sqs_msg, body)")
61
+ expect(Shoryuken.logger).to receive(:warn) do |&block|
62
+ expect(block.call).to eq('[DEPRECATION] DeprecatedMiddleware#call(worker_instance, queue, sqs_msg) is deprecated. Please use DeprecatedMiddleware#call(worker_instance, queue, sqs_msg, body)')
63
+ end
62
64
 
63
65
  subject.add DeprecatedMiddleware
64
66
 
@@ -16,8 +16,12 @@ describe Shoryuken::Middleware::Server::Timing do
16
16
  end
17
17
 
18
18
  it 'logs timing' do
19
- expect(Shoryuken.logger).to receive(:info).with(/started at/)
20
- expect(Shoryuken.logger).to receive(:info).with(/completed in/)
19
+ expect(Shoryuken.logger).to receive(:info) do |&block|
20
+ expect(block.call).to match(/started at/)
21
+ end
22
+ expect(Shoryuken.logger).to receive(:info) do |&block|
23
+ expect(block.call).to match(/completed in/)
24
+ end
21
25
 
22
26
  subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) {}
23
27
  end
@@ -26,9 +30,15 @@ describe Shoryuken::Middleware::Server::Timing do
26
30
  it 'logs exceeded' do
27
31
  allow(subject).to receive(:elapsed).and_return(120000)
28
32
 
29
- expect(Shoryuken.logger).to receive(:info).with(/started at/)
30
- expect(Shoryuken.logger).to receive(:info).with(/completed in/)
31
- expect(Shoryuken.logger).to receive(:warn).with('exceeded the queue visibility timeout by 60000 ms')
33
+ expect(Shoryuken.logger).to receive(:info) do |&block|
34
+ expect(block.call).to match(/started at/)
35
+ end
36
+ expect(Shoryuken.logger).to receive(:info) do |&block|
37
+ expect(block.call).to match(/completed in/)
38
+ end
39
+ expect(Shoryuken.logger).to receive(:warn) do |&block|
40
+ expect(block.call).to match('exceeded the queue visibility timeout by 60000 ms')
41
+ end
32
42
 
33
43
  subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) {}
34
44
  end
@@ -36,8 +46,12 @@ describe Shoryuken::Middleware::Server::Timing do
36
46
 
37
47
  context 'when exception' do
38
48
  it 'logs failed in' do
39
- expect(Shoryuken.logger).to receive(:info).with(/started at/)
40
- expect(Shoryuken.logger).to receive(:info).with(/failed in/)
49
+ expect(Shoryuken.logger).to receive(:info) do |&block|
50
+ expect(block.call).to match(/started at/)
51
+ end
52
+ expect(Shoryuken.logger).to receive(:info) do |&block|
53
+ expect(block.call).to match(/failed in/)
54
+ end
41
55
 
42
56
  expect {
43
57
  subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise }
@@ -59,8 +59,28 @@ describe Shoryuken::Processor do
59
59
  subject.process(queue, sqs_msg)
60
60
  end
61
61
 
62
+ it 'parses calling `.load`' do
63
+ TestWorker.get_shoryuken_options['body_parser'] = Class.new do
64
+ def self.load(*args)
65
+ JSON.load(*args)
66
+ end
67
+ end
68
+
69
+ body = { 'test' => 'hi' }
70
+
71
+ expect_any_instance_of(TestWorker).to receive(:perform).with(sqs_msg, body)
72
+
73
+ allow(sqs_msg).to receive(:body).and_return(JSON.dump(body))
74
+
75
+ subject.process(queue, sqs_msg)
76
+ end
77
+
62
78
  it 'parses calling `.parse`' do
63
- TestWorker.get_shoryuken_options['body_parser'] = JSON
79
+ TestWorker.get_shoryuken_options['body_parser'] = Class.new do
80
+ def self.parse(*args)
81
+ JSON.parse(*args)
82
+ end
83
+ end
64
84
 
65
85
  body = { 'test' => 'hi' }
66
86
 
@@ -79,7 +99,9 @@ describe Shoryuken::Processor do
79
99
 
80
100
  allow(sqs_msg).to receive(:body).and_return('invalid json')
81
101
 
82
- expect(subject.logger).to receive(:error).with("Error parsing the message body: 757: unexpected token at 'invalid json'\nbody_parser: json\nsqs_msg.body: invalid json")
102
+ expect(subject.logger).to receive(:error) do |&block|
103
+ expect(block.call).to eq("Error parsing the message body: 757: unexpected token at 'invalid json'\nbody_parser: json\nsqs_msg.body: invalid json")
104
+ end
83
105
 
84
106
  subject.process(queue, sqs_msg)
85
107
  end
@@ -9,6 +9,19 @@ describe Shoryuken::Queue do
9
9
  subject { described_class.new(sqs, queue_name) }
10
10
 
11
11
  describe '#send_message' do
12
+ it 'accepts SQS request parameters' do
13
+ # https://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#send_message-instance_method
14
+ expect(sqs).to receive(:send_message).with(hash_including(message_body: 'msg1'))
15
+
16
+ subject.send_message(message_body: 'msg1')
17
+ end
18
+
19
+ it 'accepts a string' do
20
+ expect(sqs).to receive(:send_message).with(hash_including(message_body: 'msg1'))
21
+
22
+ subject.send_message('msg1')
23
+ end
24
+
12
25
  context 'when body is invalid' do
13
26
  it 'raises ArgumentError for nil' do
14
27
  expect {
@@ -21,10 +34,55 @@ describe Shoryuken::Queue do
21
34
  subject.send_message(message_body: 1)
22
35
  }.to raise_error(ArgumentError, 'The message body must be a String and you passed a Fixnum')
23
36
  end
37
+
38
+ context 'when a client middleware' do
39
+ class MyClientMiddleware
40
+ def call(options)
41
+ options[:message_body] = 'changed'
42
+
43
+ yield
44
+ end
45
+ end
46
+
47
+ before do
48
+ Shoryuken.configure_client do |config|
49
+ config.client_middleware do |chain|
50
+ chain.add MyClientMiddleware
51
+ end
52
+ end
53
+ end
54
+
55
+ after do
56
+ Shoryuken.configure_client do |config|
57
+ config.client_middleware do |chain|
58
+ chain.remove MyClientMiddleware
59
+ end
60
+ end
61
+ end
62
+
63
+ it 'invokes MyClientMiddleware' do
64
+ expect(sqs).to receive(:send_message).with(hash_including(message_body: 'changed'))
65
+
66
+ subject.send_message(message_body: 'original')
67
+ end
68
+ end
24
69
  end
25
70
  end
26
71
 
27
72
  describe '#send_messages' do
73
+ it 'accepts SQS request parameters' do
74
+ # https://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#send_message_batch-instance_method
75
+ expect(sqs).to receive(:send_message_batch).with(hash_including(entries: [{ id: '0', message_body: 'msg1'}, { id: '1', message_body: 'msg2' }]))
76
+
77
+ subject.send_messages(entries: [{ id: '0', message_body: 'msg1'}, { id: '1', message_body: 'msg2' }])
78
+ end
79
+
80
+ it 'accepts an array of string' do
81
+ expect(sqs).to receive(:send_message_batch).with(hash_including(entries: [{ id: '0', message_body: 'msg1'}, { id: '1', message_body: 'msg2' }]))
82
+
83
+ subject.send_messages(%w(msg1 msg2))
84
+ end
85
+
28
86
  context 'when body is invalid' do
29
87
  it 'raises ArgumentError for nil' do
30
88
  expect {
@@ -39,7 +39,10 @@ class TestWorker
39
39
  end
40
40
 
41
41
  RSpec.configure do |config|
42
- config.filter_run_excluding slow: true unless ENV['SPEC_ALL']
42
+ # Only run slow tests if SPEC_ALL=true and AWS_ACCESS_KEY_ID is present
43
+ # The AWS_ACCESS_KEY_ID checker is because Travis CI does not expose ENV variables to pull requests from forked repositories
44
+ # http://docs.travis-ci.com/user/pull-requests/
45
+ config.filter_run_excluding slow: true if ENV['SPEC_ALL'] != 'true' || ENV['AWS_ACCESS_KEY_ID'].nil?
43
46
 
44
47
  config.before do
45
48
  Shoryuken::Client.class_variable_set :@@queues, {}
metadata CHANGED
@@ -1,125 +1,125 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shoryuken
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Cantero
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-08 00:00:00.000000000 Z
11
+ date: 2015-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.6'
20
20
  type: :development
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: '1.6'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: pry-byebug
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: nokogiri
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '>='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: dotenv
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '>='
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '>='
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: aws-sdk-core
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ~>
101
+ - - "~>"
102
102
  - !ruby/object:Gem::Version
103
103
  version: 2.0.21
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ~>
108
+ - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: 2.0.21
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: celluloid
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ~>
115
+ - - "~>"
116
116
  - !ruby/object:Gem::Version
117
117
  version: 0.16.0
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ~>
122
+ - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: 0.16.0
125
125
  description: Shoryuken is a super efficient AWS SQS thread based message processor
@@ -130,11 +130,11 @@ executables:
130
130
  extensions: []
131
131
  extra_rdoc_files: []
132
132
  files:
133
- - .gitignore
134
- - .hound.yml
135
- - .rspec
136
- - .rubocop.yml
137
- - .travis.yml
133
+ - ".gitignore"
134
+ - ".hound.yml"
135
+ - ".rspec"
136
+ - ".rubocop.yml"
137
+ - ".travis.yml"
138
138
  - Gemfile
139
139
  - LICENSE.txt
140
140
  - README.md
@@ -198,17 +198,17 @@ require_paths:
198
198
  - lib
199
199
  required_ruby_version: !ruby/object:Gem::Requirement
200
200
  requirements:
201
- - - '>='
201
+ - - ">="
202
202
  - !ruby/object:Gem::Version
203
203
  version: '0'
204
204
  required_rubygems_version: !ruby/object:Gem::Requirement
205
205
  requirements:
206
- - - '>='
206
+ - - ">="
207
207
  - !ruby/object:Gem::Version
208
208
  version: '0'
209
209
  requirements: []
210
210
  rubyforge_project:
211
- rubygems_version: 2.4.5
211
+ rubygems_version: 2.2.3
212
212
  signing_key:
213
213
  specification_version: 4
214
214
  summary: Shoryuken is a super efficient AWS SQS thread based message processor