shoryuken 1.0.0 → 1.0.1

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,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9072ce45c134cd9563ee7dddac27bc7d19733cde
4
- data.tar.gz: 2b48cb3b0d594ef1111456d0f9101ce154056995
3
+ metadata.gz: 08068a4a26c1f914715b25a27e463fac07e31b57
4
+ data.tar.gz: ac4fc670aac9fb76dec2c8c369f021ace729f0cd
5
5
  SHA512:
6
- metadata.gz: 62bde57b564da861f90c2a68816a69743766f1120dd146817f884b2d4d2e5e5119dcef8858516b7d2581517a04177ec0edb151a97f254c6185ae9092c96efab2
7
- data.tar.gz: e011b7d261eaf6e4b87d7dc322d2f458da09fdcd5074eac90eedb8101c082b2987f303b557ba20d90c89b321a7d4df7d613852890b6888263ccbc8adb1bf77db
6
+ metadata.gz: fdf1a335919582abb26bd34583de36da01b117b2e952b5d4874d40f4ee28b5d86cdca65fe9d4d52a807347569d2c9cb50bb20e82f8f6801ebf49aa2638219856
7
+ data.tar.gz: c9d462d292894b91ffe066af240ee5e7c6b8232542f19ba9d849a05bb9d7a0b6b732d6f96dbe6096610bbd9e4857f4540eed53bd5fd60b19c888dc8b88692e6b
data/.gitignore CHANGED
@@ -24,3 +24,4 @@ shoryuken.yml
24
24
  *.pid
25
25
  *.log
26
26
  .env
27
+ rubocop.html
data/.hound.yml CHANGED
@@ -1,2 +1,6 @@
1
- StringLiterals:
2
- EnforcedStyle: single_quotes
1
+ ruby:
2
+ enabled: true
3
+ config_file: .rubocop.yml
4
+
5
+ java_script:
6
+ enabled: true
data/.rubocop.yml ADDED
@@ -0,0 +1,50 @@
1
+ LineLength:
2
+ Max: 120
3
+
4
+ Style/SignalException:
5
+ Enabled: false
6
+
7
+ Style/SpaceAroundEqualsInParameterDefault:
8
+ Enabled: false
9
+
10
+ Style/Documentation:
11
+ Enabled: false
12
+
13
+ Style/ClassAndModuleChildren:
14
+ Enabled: false
15
+
16
+ Metrics/PerceivedComplexity:
17
+ Enabled: false
18
+
19
+ Metrics/CyclomaticComplexity:
20
+ Enabled: false
21
+
22
+ Style/CommentAnnotation:
23
+ Enabled: false
24
+
25
+ Metrics/ClassLength:
26
+ Enabled: false
27
+
28
+ Metrics/ParameterLists:
29
+ Enabled: false
30
+
31
+ Metrics/MethodLength :
32
+ Enabled: false
33
+
34
+ Style/PerlBackrefs:
35
+ Enabled: false
36
+
37
+ Style/StringLiterals:
38
+ EnforcedStyle: single_quotes
39
+ SupportedStyles:
40
+ - single_quotes
41
+ - double_quotes
42
+
43
+ Style/StringLiteralsInInterpolation:
44
+ EnforcedStyle: single_quotes
45
+ SupportedStyles:
46
+ - single_quotes
47
+ - double_quotes
48
+
49
+ Lint/UnusedMethodArgument:
50
+ Enabled: false
@@ -22,7 +22,7 @@ if sqs.config['endpoint'] =~ /amazonaws.com/
22
22
  ).attributes['QueueArn']
23
23
 
24
24
  attributes = {}
25
- attributes['RedrivePolicy'] = %Q{{"maxReceiveCount":"7", "deadLetterTargetArn":"#{dead_letter_queue_arn}"}}
25
+ attributes['RedrivePolicy'] = %Q({"maxReceiveCount":"7", "deadLetterTargetArn":"#{dead_letter_queue_arn}"})
26
26
 
27
27
  sqs.set_queue_attributes queue_url: default_queue_url, attributes: attributes
28
28
  end
data/lib/shoryuken.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'yaml'
2
2
  require 'aws-sdk-core'
3
- require 'aws-sdk-resources'
4
3
  require 'time'
5
4
 
6
5
  require 'shoryuken/version'
@@ -8,12 +7,15 @@ require 'shoryuken/core_ext'
8
7
  require 'shoryuken/util'
9
8
  require 'shoryuken/logging'
10
9
  require 'shoryuken/environment_loader'
10
+ require 'shoryuken/queue'
11
+ require 'shoryuken/message'
11
12
  require 'shoryuken/client'
12
13
  require 'shoryuken/worker'
13
14
  require 'shoryuken/worker_registry'
14
15
  require 'shoryuken/default_worker_registry'
15
16
  require 'shoryuken/middleware/chain'
16
17
  require 'shoryuken/middleware/server/auto_delete'
18
+ require 'shoryuken/middleware/server/exponential_backoff_retry'
17
19
  require 'shoryuken/middleware/server/timing'
18
20
  require 'shoryuken/sns_arn'
19
21
  require 'shoryuken/topic'
@@ -76,6 +78,7 @@ module Shoryuken
76
78
  'delete' => false,
77
79
  'auto_delete' => false,
78
80
  'auto_visibility_timeout' => false,
81
+ 'retry_intervals' => nil,
79
82
  'batch' => false }
80
83
  end
81
84
 
@@ -104,6 +107,7 @@ module Shoryuken
104
107
  def default_server_middleware
105
108
  Middleware::Chain.new do |m|
106
109
  m.add Middleware::Server::Timing
110
+ m.add Middleware::Server::ExponentialBackoffRetry
107
111
  m.add Middleware::Server::AutoDelete
108
112
  if defined?(::ActiveRecord::Base)
109
113
  require 'shoryuken/middleware/server/active_record'
data/lib/shoryuken/cli.rb CHANGED
@@ -19,7 +19,7 @@ module Shoryuken
19
19
  def run(args)
20
20
  self_read, self_write = IO.pipe
21
21
 
22
- %w[INT TERM USR1 USR2 TTIN].each do |sig|
22
+ %w(INT TERM USR1 USR2 TTIN).each do |sig|
23
23
  begin
24
24
  trap sig do
25
25
  self_write.puts(sig)
@@ -48,7 +48,7 @@ module Shoryuken
48
48
  begin
49
49
  launcher.run
50
50
 
51
- while readable_io = IO.select([self_read])
51
+ while (readable_io = IO.select([self_read]))
52
52
  signal = readable_io.first[0].gets.strip
53
53
  handle_signal(signal)
54
54
  end
@@ -86,7 +86,7 @@ module Shoryuken
86
86
 
87
87
  files_to_reopen.each do |file|
88
88
  begin
89
- file.reopen file.path, "a+"
89
+ file.reopen file.path, 'a+'
90
90
  file.sync = true
91
91
  rescue ::Exception
92
92
  end
@@ -102,7 +102,7 @@ module Shoryuken
102
102
  end
103
103
 
104
104
  def write_pid
105
- if path = Shoryuken.options[:pidfile]
105
+ if (path = Shoryuken.options[:pidfile])
106
106
  File.open(path, 'w') do |f|
107
107
  f.puts Process.pid
108
108
  end
@@ -150,7 +150,7 @@ module Shoryuken
150
150
  opts[:verbose] = arg
151
151
  end
152
152
 
153
- o.on '-V', '--version', 'Print version and exit' do |arg|
153
+ o.on '-V', '--version', 'Print version and exit' do
154
154
  puts "Shoryuken #{Shoryuken::VERSION}"
155
155
  exit 0
156
156
  end
@@ -170,7 +170,7 @@ module Shoryuken
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
 
@@ -181,7 +181,7 @@ module Shoryuken
181
181
  if thread.backtrace
182
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
 
@@ -5,7 +5,7 @@ module Shoryuken
5
5
 
6
6
  class << self
7
7
  def queues(name)
8
- @@queues[name.to_s] ||= sqs_resource.get_queue_by_name(queue_name: name)
8
+ @@queues[name.to_s] ||= Shoryuken::Queue.new(sqs, name)
9
9
  end
10
10
 
11
11
  def sns
@@ -20,10 +20,6 @@ module Shoryuken
20
20
  @sqs ||= Aws::SQS::Client.new(aws_client_options(:sqs_endpoint))
21
21
  end
22
22
 
23
- def sqs_resource
24
- @sqs_resource ||= Aws::SQS::Resource.new(client: sqs)
25
- end
26
-
27
23
  def topics(name)
28
24
  @@topics[name.to_s] ||= Topic.new(name, sns)
29
25
  end
@@ -33,7 +29,7 @@ module Shoryuken
33
29
 
34
30
  private
35
31
 
36
- def aws_client_options service_endpoint_key
32
+ def aws_client_options(service_endpoint_key)
37
33
  explicit_endpoint = Shoryuken.options[:aws][service_endpoint_key]
38
34
  options = {}
39
35
  options[:endpoint] = explicit_endpoint unless explicit_endpoint.to_s.empty?
@@ -28,7 +28,7 @@ module Shoryuken
28
28
  end
29
29
 
30
30
  def register_worker(queue, clazz)
31
- if worker_class = @workers[queue]
31
+ if (worker_class = @workers[queue])
32
32
  if worker_class.get_shoryuken_options['batch'] == true || clazz.get_shoryuken_options['batch'] == true
33
33
  raise ArgumentError, "Could not register #{clazz} for '#{queue}', "\
34
34
  "because #{worker_class} is already registered for this queue, "\
@@ -10,7 +10,7 @@ module Shoryuken
10
10
  load(config_file: (Rails.root + 'config' + 'shoryuken.yml'))
11
11
  end
12
12
 
13
- def initialize options
13
+ def initialize(options)
14
14
  @options = options
15
15
  end
16
16
 
@@ -64,8 +64,8 @@ module Shoryuken
64
64
 
65
65
  aws_options = aws_options.merge(credentials: credentials)
66
66
 
67
- if callback = Shoryuken.aws_initialization_callback
68
- Shoryuken.logger.info "Calling Shoryuken.on_aws_initialization block"
67
+ if (callback = Shoryuken.aws_initialization_callback)
68
+ Shoryuken.logger.info 'Calling Shoryuken.on_aws_initialization block'
69
69
  callback.call(aws_options)
70
70
  end
71
71
 
@@ -82,19 +82,19 @@ module Shoryuken
82
82
 
83
83
  require 'rails'
84
84
  if ::Rails::VERSION::MAJOR < 4
85
- require File.expand_path("config/environment.rb")
85
+ require File.expand_path('config/environment.rb')
86
86
  ::Rails.application.eager_load!
87
87
  else
88
88
  # Painful contortions, see 1791 for discussion
89
- require File.expand_path("config/application.rb")
90
- ::Rails::Application.initializer "shoryuken.eager_load" do
89
+ require File.expand_path('config/application.rb')
90
+ ::Rails::Application.initializer 'shoryuken.eager_load' do
91
91
  ::Rails.application.config.eager_load = true
92
92
  end
93
93
  require 'shoryuken/extensions/active_job_adapter' if defined?(::ActiveJob)
94
- require File.expand_path("config/environment.rb")
94
+ require File.expand_path('config/environment.rb')
95
95
  end
96
96
 
97
- Shoryuken.logger.info "Rails environment loaded"
97
+ Shoryuken.logger.info 'Rails environment loaded'
98
98
  end
99
99
 
100
100
  def merge_cli_defined_queues
@@ -37,7 +37,6 @@ module ActiveJob
37
37
 
38
38
  def message(job, options = {})
39
39
  body = job.serialize
40
- body = JSON.dump(body) if body.is_a?(Hash)
41
40
 
42
41
  { message_body: body,
43
42
  message_attributes: message_attributes }.merge(options)
@@ -16,6 +16,7 @@ module Shoryuken
16
16
  options = Shoryuken.options[:aws][:receive_message].to_h.dup
17
17
  options[:max_number_of_messages] = limit
18
18
  options[:message_attribute_names] = %w(All)
19
+ options[:attribute_names] = %w(All)
19
20
 
20
21
  Shoryuken::Client.queues(queue).receive_messages options
21
22
  end
@@ -58,6 +59,7 @@ module Shoryuken
58
59
  end
59
60
 
60
61
  end
62
+
61
63
  private
62
64
 
63
65
  def patch_sqs_msgs!(sqs_msgs)
@@ -17,12 +17,10 @@ module Shoryuken
17
17
  end
18
18
 
19
19
  def self.with_context(msg)
20
- begin
21
- Thread.current[:shoryuken_context] = msg
22
- yield
23
- ensure
24
- Thread.current[:shoryuken_context] = nil
25
- end
20
+ Thread.current[:shoryuken_context] = msg
21
+ yield
22
+ ensure
23
+ Thread.current[:shoryuken_context] = nil
26
24
  end
27
25
 
28
26
  def self.initialize_logger(log_target = STDOUT)
@@ -32,8 +32,8 @@ module Shoryuken
32
32
  watchdog('Manager#stop died') do
33
33
  @done = true
34
34
 
35
- if callback = Shoryuken.stop_callback
36
- logger.info "Calling Shoryuken.on_stop block"
35
+ if (callback = Shoryuken.stop_callback)
36
+ logger.info 'Calling Shoryuken.on_stop block'
37
37
  callback.call
38
38
  end
39
39
 
@@ -89,7 +89,7 @@ module Shoryuken
89
89
  end
90
90
 
91
91
  def assign(queue, sqs_msg)
92
- watchdog("Manager#assign died") do
92
+ watchdog('Manager#assign died') do
93
93
  logger.info "Assigning #{sqs_msg.message_id}"
94
94
 
95
95
  processor = @ready.pop
@@ -133,7 +133,7 @@ module Shoryuken
133
133
  return
134
134
  end
135
135
 
136
- if queue = next_queue
136
+ if (queue = next_queue)
137
137
  @fetcher.async.fetch(queue, @ready.size)
138
138
  else
139
139
  logger.debug { 'Pausing fetcher, because all queues are paused' }
@@ -220,7 +220,7 @@ module Shoryuken
220
220
  logger.info { "Pausing up to #{delay} seconds to allow workers to finish..." }
221
221
 
222
222
  after(delay) do
223
- watchdog("Manager#hard_shutdown_in died") do
223
+ watchdog('Manager#hard_shutdown_in died') do
224
224
  if @busy.size > 0
225
225
  logger.info { "Hard shutting down #{@busy.size} busy workers" }
226
226
 
@@ -0,0 +1,60 @@
1
+ module Shoryuken
2
+ class Message
3
+ attr_accessor :client, :queue_url, :data
4
+
5
+ def initialize(client, queue_url, data)
6
+ self.client = client
7
+ self.queue_url = queue_url
8
+ self.data = data
9
+ end
10
+
11
+ def delete
12
+ client.delete_message(
13
+ queue_url: queue_url,
14
+ receipt_handle: data.receipt_handle
15
+ )
16
+ end
17
+
18
+ def change_visibility(options)
19
+ client.change_message_visibility(
20
+ options.merge(queue_url: queue_url, receipt_handle: data.receipt_handle)
21
+ )
22
+ end
23
+
24
+ def visibility_timeout=(timeout)
25
+ client.change_message_visibility(
26
+ queue_url: queue_url,
27
+ receipt_handle: data.receipt_handle,
28
+ visibility_timeout: timeout
29
+ )
30
+ end
31
+
32
+ def message_id
33
+ data.message_id
34
+ end
35
+
36
+ def receipt_handle
37
+ data.receipt_handle
38
+ end
39
+
40
+ def md5_of_body
41
+ data.md5_of_body
42
+ end
43
+
44
+ def body
45
+ data.body
46
+ end
47
+
48
+ def attributes
49
+ data.attributes
50
+ end
51
+
52
+ def md5_of_message_attributes
53
+ data.md5_of_message_attributes
54
+ end
55
+
56
+ def message_attributes
57
+ data.message_attributes
58
+ end
59
+ end
60
+ end
@@ -72,7 +72,7 @@ module Shoryuken
72
72
  i = entries.index { |entry| entry.klass == newklass }
73
73
  new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
74
74
  i = entries.find_index { |entry| entry.klass == oldklass } || entries.count - 1
75
- entries.insert(i+1, new_entry)
75
+ entries.insert(i + 1, new_entry)
76
76
  end
77
77
 
78
78
  def exists?(klass)
@@ -0,0 +1,50 @@
1
+ module Shoryuken
2
+ module Middleware
3
+ module Server
4
+ class ExponentialBackoffRetry
5
+ include Util
6
+
7
+ def call(worker, queue, sqs_msg, body)
8
+ started_at = Time.now
9
+ yield
10
+ rescue
11
+ retry_intervals = Array(worker.class.get_shoryuken_options['retry_intervals'])
12
+
13
+ if retry_intervals.empty? || !handle_failure(sqs_msg, started_at, retry_intervals)
14
+ # Re-raise the exception if the job is not going to be exponential backoff retried.
15
+ # This allows custom middleware (like exception notifiers) to be aware of the unhandled failure.
16
+ raise
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def handle_failure(sqs_msg, started_at, retry_intervals)
23
+ attempts = sqs_msg.attributes['ApproximateReceiveCount']
24
+
25
+ return unless attempts
26
+
27
+ attempts = attempts.to_i - 1
28
+
29
+ interval = if attempts < retry_intervals.size
30
+ retry_intervals[attempts]
31
+ else
32
+ retry_intervals.last
33
+ end
34
+
35
+ # Visibility timeouts are limited to a total 12 hours, starting from the receipt of the message.
36
+ # We calculate the maximum timeout by subtracting the amount of time since the receipt of the message.
37
+ #
38
+ # From the docs: "Amazon SQS restarts the timeout period using the new value."
39
+ # http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html#AboutVT-extending-message-visibility-timeout
40
+ max_timeout = 43200 - (Time.now - started_at).ceil - 1
41
+ interval = max_timeout if interval > max_timeout
42
+
43
+ sqs_msg.change_visibility(visibility_timeout: interval.to_i)
44
+
45
+ logger.info "Message #{sqs_msg.message_id} failed, will be retried in #{interval} seconds."
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,53 @@
1
+ module Shoryuken
2
+ class Queue
3
+ attr_accessor :name, :client, :url
4
+
5
+ def initialize(client, name)
6
+ self.name = name
7
+ self.client = client
8
+ self.url = client.get_queue_url(queue_name: name).queue_url
9
+ end
10
+
11
+ def visibility_timeout
12
+ client.get_queue_attributes(
13
+ queue_url: url,
14
+ attribute_names: ['VisibilityTimeout']
15
+ ).attributes['VisibilityTimeout'].to_i
16
+ end
17
+
18
+ def delete_messages(options)
19
+ client.delete_message_batch(options.merge(queue_url: url))
20
+ end
21
+
22
+ def send_message(options)
23
+ client.send_message(sanitize_message_body(options.merge(queue_url: url)))
24
+ end
25
+
26
+ def send_messages(options)
27
+ client.send_message_batch(sanitize_message_body(options.merge(queue_url: url)))
28
+ end
29
+
30
+ def receive_messages(options)
31
+ client.receive_message(options.merge(queue_url: url)).
32
+ messages.
33
+ map { |m| Message.new(client, url, m) }
34
+ end
35
+
36
+ private
37
+
38
+ def sanitize_message_body(options)
39
+ messages = options[:entries] || [options]
40
+
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
48
+ end
49
+
50
+ options
51
+ end
52
+ end
53
+ end
@@ -1,6 +1,6 @@
1
1
  module Shoryuken
2
2
  class SnsArn
3
- def initialize topic
3
+ def initialize(topic)
4
4
  @topic = topic
5
5
  end
6
6
 
@@ -13,14 +13,14 @@ module Shoryuken
13
13
  def account_id
14
14
  Shoryuken::Client.account_id.tap do |account_id|
15
15
  if account_id.to_s.empty?
16
- fail "To generate SNS ARNs, you must assign an :account_id in your Shoryuken::Client."
16
+ fail 'To generate SNS ARNs, you must assign an :account_id in your Shoryuken::Client.'
17
17
  end
18
18
  end
19
19
  end
20
20
 
21
21
  def region
22
22
  Aws.config.fetch(:region) do
23
- fail "To generate SNS ARNs, you must include a :region in your AWS config."
23
+ fail 'To generate SNS ARNs, you must include a :region in your AWS config.'
24
24
  end
25
25
  end
26
26
  end
@@ -18,9 +18,8 @@ module Shoryuken
18
18
  end
19
19
 
20
20
  def unparse_queues(queues)
21
- queues.inject({}) do |queue_and_weights, name|
21
+ queues.each_with_object({}) do |name, queue_and_weights|
22
22
  queue_and_weights[name] = queue_and_weights[name].to_i + 1
23
- queue_and_weights
24
23
  end.to_a
25
24
  end
26
25
 
@@ -1,3 +1,3 @@
1
1
  module Shoryuken
2
- VERSION = '1.0.0'
2
+ VERSION = '1.0.1'
3
3
  end
@@ -13,7 +13,6 @@ module Shoryuken
13
13
  data_type: 'String'
14
14
  }
15
15
 
16
- body = JSON.dump(body) if body.is_a?(Hash)
17
16
  options[:message_body] = body
18
17
 
19
18
  Shoryuken::Client.queues(get_shoryuken_options['queue']).send_message(options)
data/shoryuken.gemspec CHANGED
@@ -4,27 +4,26 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'shoryuken/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "shoryuken"
7
+ spec.name = 'shoryuken'
8
8
  spec.version = Shoryuken::VERSION
9
- spec.authors = ["Pablo Cantero"]
10
- spec.email = ["pablo@pablocantero.com"]
11
- spec.description = spec.summary = %q{Shoryuken is a super efficient AWS SQS thread based message processor}
12
- spec.homepage = "https://github.com/phstc/shoryuken"
13
- spec.license = "LGPL-3.0"
9
+ spec.authors = ['Pablo Cantero']
10
+ spec.email = ['pablo@pablocantero.com']
11
+ spec.description = spec.summary = %q(Shoryuken is a super efficient AWS SQS thread based message processor)
12
+ spec.homepage = 'https://github.com/phstc/shoryuken'
13
+ spec.license = 'LGPL-3.0'
14
14
 
15
15
  spec.files = `git ls-files -z`.split("\x0")
16
16
  spec.executables = %w[shoryuken]
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
- spec.require_paths = ["lib"]
18
+ spec.require_paths = ['lib']
19
19
 
20
- spec.add_development_dependency "bundler", "~> 1.6"
21
- spec.add_development_dependency "rake"
22
- spec.add_development_dependency "rspec"
23
- spec.add_development_dependency "pry-byebug"
24
- spec.add_development_dependency "nokogiri"
25
- spec.add_development_dependency "dotenv"
20
+ spec.add_development_dependency 'bundler', '~> 1.6'
21
+ spec.add_development_dependency 'rake'
22
+ spec.add_development_dependency 'rspec'
23
+ spec.add_development_dependency 'pry-byebug'
24
+ spec.add_development_dependency 'nokogiri'
25
+ spec.add_development_dependency 'dotenv'
26
26
 
27
- spec.add_dependency "aws-sdk-core", "2.0.21"
28
- spec.add_dependency "aws-sdk-resources", "2.0.21.pre"
29
- spec.add_dependency "celluloid", "~> 0.16.0"
27
+ spec.add_dependency 'aws-sdk-core', '~> 2.0.21'
28
+ spec.add_dependency 'celluloid', '~> 0.16.0'
30
29
  end
@@ -48,7 +48,7 @@ describe Shoryuken::DefaultWorkerRegistry do
48
48
  string_value: explicit_worker.to_s,
49
49
  data_type: 'String' } if explicit_worker
50
50
 
51
- double Aws::SQS::Message,
51
+ double Shoryuken::Message,
52
52
  body: 'test',
53
53
  message_attributes: attributes,
54
54
  message_id: SecureRandom.uuid
@@ -4,11 +4,11 @@ require 'shoryuken/fetcher'
4
4
 
5
5
  describe Shoryuken::Fetcher do
6
6
  let(:manager) { double Shoryuken::Manager }
7
- let(:queue) { double Aws::SQS::Queue }
7
+ let(:queue) { double Shoryuken::Queue }
8
8
  let(:queue_name) { 'default' }
9
9
 
10
10
  let(:sqs_msg) do
11
- double Aws::SQS::Message,
11
+ double Shoryuken::Message,
12
12
  queue_url: queue_name,
13
13
  body: 'test',
14
14
  message_id: 'fc754df7-9cc2-4c41-96ca-5996a44b771e'
@@ -24,7 +24,7 @@ describe Shoryuken::Fetcher do
24
24
 
25
25
  describe '#fetch' do
26
26
  it 'calls pause when no message' do
27
- allow(queue).to receive(:receive_messages).with(max_number_of_messages: 1, message_attribute_names: ['All']).and_return([])
27
+ allow(queue).to receive(:receive_messages).with(max_number_of_messages: 1, attribute_names: ['All'], message_attribute_names: ['All']).and_return([])
28
28
 
29
29
  expect(manager).to receive(:pause_queue!).with(queue_name)
30
30
  expect(manager).to receive(:dispatch)
@@ -33,7 +33,7 @@ describe Shoryuken::Fetcher do
33
33
  end
34
34
 
35
35
  it 'assigns messages' do
36
- allow(queue).to receive(:receive_messages).with(max_number_of_messages: 5, message_attribute_names: ['All']).and_return(sqs_msg)
36
+ allow(queue).to receive(:receive_messages).with(max_number_of_messages: 5, attribute_names: ['All'], message_attribute_names: ['All']).and_return(sqs_msg)
37
37
 
38
38
  expect(manager).to receive(:rebalance_queue_weight!).with(queue_name)
39
39
  expect(manager).to receive(:assign).with(queue_name, sqs_msg)
@@ -45,7 +45,7 @@ describe Shoryuken::Fetcher do
45
45
  it 'assigns messages in batch' do
46
46
  TestWorker.get_shoryuken_options['batch'] = true
47
47
 
48
- allow(queue).to receive(:receive_messages).with(max_number_of_messages: described_class::FETCH_LIMIT, message_attribute_names: ['All']).and_return(sqs_msg)
48
+ allow(queue).to receive(:receive_messages).with(max_number_of_messages: described_class::FETCH_LIMIT, attribute_names: ['All'], message_attribute_names: ['All']).and_return(sqs_msg)
49
49
 
50
50
  expect(manager).to receive(:rebalance_queue_weight!).with(queue_name)
51
51
  expect(manager).to receive(:assign).with(queue_name, [sqs_msg])
@@ -58,7 +58,7 @@ describe Shoryuken::Fetcher do
58
58
  let(:queue_name) { 'notfound' }
59
59
 
60
60
  it 'ignores batch' do
61
- allow(queue).to receive(:receive_messages).with(max_number_of_messages: 5, message_attribute_names: ['All']).and_return(sqs_msg)
61
+ allow(queue).to receive(:receive_messages).with(max_number_of_messages: 5, attribute_names: ['All'], message_attribute_names: ['All']).and_return(sqs_msg)
62
62
 
63
63
  expect(manager).to receive(:rebalance_queue_weight!).with(queue_name)
64
64
  expect(manager).to receive(:assign).with(queue_name, sqs_msg)
@@ -2,10 +2,10 @@ require 'spec_helper'
2
2
 
3
3
  describe Shoryuken::Middleware::Server::AutoDelete do
4
4
  let(:queue) { 'default' }
5
- let(:sqs_queue) { double Aws::SQS::Queue }
5
+ let(:sqs_queue) { double Shoryuken::Queue }
6
6
 
7
7
  def build_message
8
- double Aws::SQS::Message,
8
+ double Shoryuken::Message,
9
9
  queue_url: queue,
10
10
  body: 'test',
11
11
  receipt_handle: SecureRandom.uuid
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoryuken::Middleware::Server::ExponentialBackoffRetry do
4
+ let(:queue) { 'default' }
5
+ let(:sqs_queue) { double Shoryuken::Queue }
6
+ let(:sqs_msg) { double Shoryuken::Message, queue_url: queue, body: 'test', receipt_handle: SecureRandom.uuid,
7
+ attributes: {'ApproximateReceiveCount' => 1}, message_id: SecureRandom.uuid }
8
+
9
+ before do
10
+ allow(Shoryuken::Client).to receive(:queues).with(queue).and_return(sqs_queue)
11
+ end
12
+
13
+ context 'when a job succeeds' do
14
+ it 'does not retry the job' do
15
+ TestWorker.get_shoryuken_options['retry_intervals'] = [300, 1800]
16
+
17
+ expect(sqs_msg).not_to receive(:change_visibility)
18
+
19
+ subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) {}
20
+ end
21
+ end
22
+
23
+ context 'when a job throws an exception' do
24
+
25
+ it 'does not retry the job by default' do
26
+ expect(sqs_msg).not_to receive(:change_visibility)
27
+
28
+ expect { subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise } }.to raise_error
29
+ end
30
+
31
+ it 'does not retry the job if :retry_intervals is empty' do
32
+ TestWorker.get_shoryuken_options['retry_intervals'] = []
33
+
34
+ expect(sqs_msg).not_to receive(:change_visibility)
35
+
36
+ expect { subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise } }.to raise_error
37
+ end
38
+
39
+ it 'retries the job if :retry_intervals is non-empty' do
40
+ TestWorker.get_shoryuken_options['retry_intervals'] = [300, 1800]
41
+
42
+ allow(sqs_msg).to receive(:queue){ sqs_queue }
43
+ expect(sqs_msg).to receive(:change_visibility).with(visibility_timeout: 300)
44
+
45
+ expect { subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise } }.not_to raise_error
46
+ end
47
+
48
+ it 'retries the job with exponential backoff' do
49
+ TestWorker.get_shoryuken_options['retry_intervals'] = [300, 1800]
50
+
51
+ allow(sqs_msg).to receive(:attributes){ {'ApproximateReceiveCount' => 2 } }
52
+ allow(sqs_msg).to receive(:queue){ sqs_queue }
53
+ expect(sqs_msg).to receive(:change_visibility).with(visibility_timeout: 1800)
54
+
55
+ expect { subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise } }.not_to raise_error
56
+ end
57
+
58
+ it 'uses the last retry interval when :receive_count exceeds the size of :retry_intervals' do
59
+ TestWorker.get_shoryuken_options['retry_intervals'] = [300, 1800]
60
+
61
+ allow(sqs_msg).to receive(:attributes){ {'ApproximateReceiveCount' => 3 } }
62
+ allow(sqs_msg).to receive(:queue){ sqs_queue }
63
+ expect(sqs_msg).to receive(:change_visibility).with(visibility_timeout: 1800)
64
+
65
+ expect { subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise } }.not_to raise_error
66
+ end
67
+
68
+ it 'limits the visibility timeout to 12 hours from receipt of message' do
69
+ TestWorker.get_shoryuken_options['retry_intervals'] = [86400]
70
+
71
+ allow(sqs_msg).to receive(:queue){ sqs_queue }
72
+ expect(sqs_msg).to receive(:change_visibility).with(visibility_timeout: 43198)
73
+
74
+ expect { subject.call(TestWorker.new, queue, sqs_msg, sqs_msg.body) { raise } }.not_to raise_error
75
+ end
76
+ end
77
+ end
@@ -2,10 +2,10 @@ require 'spec_helper'
2
2
 
3
3
  describe Shoryuken::Middleware::Server::Timing do
4
4
  let(:queue) { 'default' }
5
- let(:sqs_queue) { double Aws::SQS::Queue, visibility_timeout: 60 }
5
+ let(:sqs_queue) { double Shoryuken::Queue, visibility_timeout: 60 }
6
6
 
7
7
  let(:sqs_msg) do
8
- double Aws::SQS::Message,
8
+ double Shoryuken::Message,
9
9
  queue_url: queue,
10
10
  body: 'test',
11
11
  message_id: 'fc754df7-9cc2-4c41-96ca-5996a44b771e'
@@ -4,11 +4,11 @@ require 'shoryuken/manager'
4
4
 
5
5
  describe Shoryuken::Processor do
6
6
  let(:manager) { double Shoryuken::Manager, processor_done: nil }
7
- let(:sqs_queue) { double Aws::SQS::Queue, visibility_timeout: 30 }
7
+ let(:sqs_queue) { double Shoryuken::Queue, visibility_timeout: 30 }
8
8
  let(:queue) { 'default' }
9
9
 
10
10
  let(:sqs_msg) do
11
- double Aws::SQS::Message,
11
+ double Shoryuken::Message,
12
12
  queue_url: queue,
13
13
  body: 'test',
14
14
  message_attributes: {},
@@ -38,7 +38,7 @@ describe Shoryuken::Processor do
38
38
  end
39
39
 
40
40
  it 'parses the body calling the proc' do
41
- TestWorker.get_shoryuken_options['body_parser'] = Proc.new { |sqs_msg| "*#{sqs_msg.body}*" }
41
+ TestWorker.get_shoryuken_options['body_parser'] = proc { |sqs_msg| "*#{sqs_msg.body}*" }
42
42
 
43
43
  expect_any_instance_of(TestWorker).to receive(:perform).with(sqs_msg, '*test*')
44
44
 
@@ -207,15 +207,15 @@ describe Shoryuken::Processor do
207
207
 
208
208
  context 'when shoryuken_class header' do
209
209
  let(:sqs_msg) do
210
- double Aws::SQS::Message,
210
+ double Shoryuken::Message,
211
211
  queue_url: queue,
212
212
  body: 'test',
213
213
  message_attributes: {
214
214
  'shoryuken_class' => {
215
215
  string_value: TestWorker.to_s,
216
216
  data_type: 'String' }},
217
- message_id: SecureRandom.uuid,
218
- receipt_handle: SecureRandom.uuid
217
+ message_id: SecureRandom.uuid,
218
+ receipt_handle: SecureRandom.uuid
219
219
  end
220
220
 
221
221
  it 'performs without delete' do
@@ -244,7 +244,7 @@ describe Shoryuken::Processor do
244
244
 
245
245
  context 'when the worker takes a long time', slow: true do
246
246
  it 'extends the message invisibility to prevent it from being dequeued concurrently' do
247
- TestWorker.get_shoryuken_options['body_parser'] = Proc.new do |sqs_msg|
247
+ TestWorker.get_shoryuken_options['body_parser'] = proc do
248
248
  sleep visibility_timeout
249
249
  'test'
250
250
  end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoryuken::Queue do
4
+ let(:credentials) { Aws::Credentials.new('access_key_id', 'secret_access_key') }
5
+ let(:sqs) { Aws::SQS::Client.new(stub_responses: true, credentials: credentials) }
6
+ let(:queue_name) { 'shoryuken' }
7
+ let(:queue_url) { 'https://eu-west-1.amazonaws.com:6059/123456789012/shoryuken' }
8
+
9
+ subject { described_class.new(sqs, queue_name) }
10
+
11
+ describe '#send_message' do
12
+ context 'when body is invalid' do
13
+ it 'raises ArgumentError for nil' do
14
+ expect {
15
+ subject.send_message(message_body: nil)
16
+ }.to raise_error(ArgumentError, 'The message body must be a String and you passed a NilClass')
17
+ end
18
+
19
+ it 'raises ArgumentError for Fixnum' do
20
+ expect {
21
+ subject.send_message(message_body: 1)
22
+ }.to raise_error(ArgumentError, 'The message body must be a String and you passed a Fixnum')
23
+ end
24
+ end
25
+ end
26
+
27
+ describe '#send_messages' do
28
+ context 'when body is invalid' do
29
+ it 'raises ArgumentError for nil' do
30
+ expect {
31
+ subject.send_messages(entries: [message_body: nil])
32
+ }.to raise_error(ArgumentError, 'The message body must be a String and you passed a NilClass')
33
+ end
34
+
35
+ it 'raises ArgumentError for Fixnum' do
36
+ expect {
37
+ subject.send_messages(entries: [message_body: 1])
38
+ }.to raise_error(ArgumentError, 'The message body must be a String and you passed a Fixnum')
39
+ end
40
+ end
41
+ end
42
+ end
@@ -17,7 +17,7 @@ describe 'Shoryuken::Util' do
17
17
 
18
18
  describe '#worker_name' do
19
19
  let(:sqs_msg) do
20
- double Aws::SQS::Message, message_id: 'fc754df7-9cc2-4c41-96ca-5996a44b771e', message_attributes: {}
20
+ double Shoryuken::Message, message_id: 'fc754df7-9cc2-4c41-96ca-5996a44b771e', message_attributes: {}
21
21
  end
22
22
 
23
23
  it 'returns Shoryuken worker name' do
@@ -66,19 +66,6 @@ describe 'Shoryuken::Worker' do
66
66
  TestWorker.perform_async('message')
67
67
  end
68
68
 
69
- it 'enqueues a message given as hash' do
70
- expect(sqs_queue).to receive(:send_message).with(
71
- message_attributes: {
72
- 'shoryuken_class' => {
73
- string_value: TestWorker.to_s,
74
- data_type: 'String'
75
- }
76
- },
77
- message_body: '{"field":"part1","other_field":"part2"}')
78
-
79
- TestWorker.perform_async(field: 'part1', other_field: 'part2')
80
- end
81
-
82
69
  it 'enqueues a message with options' do
83
70
  expect(sqs_queue).to receive(:send_message).with(
84
71
  delay_seconds: 60,
data/spec/spec_helper.rb CHANGED
@@ -42,9 +42,6 @@ RSpec.configure do |config|
42
42
  config.filter_run_excluding slow: true unless ENV['SPEC_ALL']
43
43
 
44
44
  config.before do
45
- # remove doubles, preventing:
46
- # Double "Queue" was originally created in one example but has leaked into another example and can no longer be used.
47
- # rspec-mocks' doubles are designed to only last for one example, and you need to create a new one in each example you wish to use it for.
48
45
  Shoryuken::Client.class_variable_set :@@queues, {}
49
46
  Shoryuken::Client.class_variable_set :@@visibility_timeouts, {}
50
47
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shoryuken
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
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-03-14 00:00:00.000000000 Z
11
+ date: 2015-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -98,30 +98,16 @@ dependencies:
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
- - !ruby/object:Gem::Dependency
112
- name: aws-sdk-resources
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - '='
116
- - !ruby/object:Gem::Version
117
- version: 2.0.21.pre
118
- type: :runtime
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - '='
123
- - !ruby/object:Gem::Version
124
- version: 2.0.21.pre
125
111
  - !ruby/object:Gem::Dependency
126
112
  name: celluloid
127
113
  requirement: !ruby/object:Gem::Requirement
@@ -147,6 +133,7 @@ files:
147
133
  - .gitignore
148
134
  - .hound.yml
149
135
  - .rspec
136
+ - .rubocop.yml
150
137
  - .travis.yml
151
138
  - Gemfile
152
139
  - LICENSE.txt
@@ -166,11 +153,14 @@ files:
166
153
  - lib/shoryuken/launcher.rb
167
154
  - lib/shoryuken/logging.rb
168
155
  - lib/shoryuken/manager.rb
156
+ - lib/shoryuken/message.rb
169
157
  - lib/shoryuken/middleware/chain.rb
170
158
  - lib/shoryuken/middleware/server/active_record.rb
171
159
  - lib/shoryuken/middleware/server/auto_delete.rb
160
+ - lib/shoryuken/middleware/server/exponential_backoff_retry.rb
172
161
  - lib/shoryuken/middleware/server/timing.rb
173
162
  - lib/shoryuken/processor.rb
163
+ - lib/shoryuken/queue.rb
174
164
  - lib/shoryuken/sns_arn.rb
175
165
  - lib/shoryuken/topic.rb
176
166
  - lib/shoryuken/util.rb
@@ -188,8 +178,10 @@ files:
188
178
  - spec/shoryuken/manager_spec.rb
189
179
  - spec/shoryuken/middleware/chain_spec.rb
190
180
  - spec/shoryuken/middleware/server/auto_delete_spec.rb
181
+ - spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb
191
182
  - spec/shoryuken/middleware/server/timing_spec.rb
192
183
  - spec/shoryuken/processor_spec.rb
184
+ - spec/shoryuken/queue_spec.rb
193
185
  - spec/shoryuken/sns_arn_spec.rb
194
186
  - spec/shoryuken/topic_spec.rb
195
187
  - spec/shoryuken/util_spec.rb
@@ -230,8 +222,10 @@ test_files:
230
222
  - spec/shoryuken/manager_spec.rb
231
223
  - spec/shoryuken/middleware/chain_spec.rb
232
224
  - spec/shoryuken/middleware/server/auto_delete_spec.rb
225
+ - spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb
233
226
  - spec/shoryuken/middleware/server/timing_spec.rb
234
227
  - spec/shoryuken/processor_spec.rb
228
+ - spec/shoryuken/queue_spec.rb
235
229
  - spec/shoryuken/sns_arn_spec.rb
236
230
  - spec/shoryuken/topic_spec.rb
237
231
  - spec/shoryuken/util_spec.rb