shoryuken 2.0.4 → 2.0.11

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: 25d7b8d6df3ab9ede1f0d7a6aba01d18e2a102f2
4
- data.tar.gz: bf26e631ee57665f7b18d0a314259b48e71951cd
3
+ metadata.gz: 9859d893c7542fbdac0224f68e55a8b49f0fd984
4
+ data.tar.gz: d3e5419d74c6766c95c4b8ad20333bdced3cfd57
5
5
  SHA512:
6
- metadata.gz: edfb890c3b31a38252bfecfd5f0f1c4d98b31c892f12106136fd91c973434f544e76c1ca48c860f2cd4f1065342231ca1dbd3a3f8752eb49613be45b57ce4c87
7
- data.tar.gz: 3c36a03cb7bc41ce4038c5942b87f3e5b087d08a4e26e9387f9dc3222bbd4e50840af7ddd68f3b073712de5d8a629d57d29ec9387e855c16499183983fea0e04
6
+ metadata.gz: cb5725e4def20f4c82a785db1c8a055ba59892979608ce95855888bfbd321c5310f491284a2d6ef319e7bc1b3c1f186d91e376cc6b35faef600c26d4fa453325
7
+ data.tar.gz: 15274a15cd3dc7f067d4ae54a40921b90b703e09e2002ed32049ce41f6dd1261629de3b3fa842656d574455f01b13e4558536997c93d4f5ebd05880f1c80b9e2
data/CHANGELOG.md CHANGED
@@ -1,4 +1,55 @@
1
- ## [v2.0.4] -
1
+ ## [v2.0.10] - 2016-06-09
2
+
3
+ - Fix manager #225
4
+ - [#226] https://github.com/phstc/shoryuken/pull/226
5
+
6
+ ## [v2.0.9] - 2016-06-08
7
+
8
+ - Fix daemonization broken in #219
9
+ - [#224] https://github.com/phstc/shoryuken/pull/224
10
+
11
+ ## [v2.0.8] - 2016-06-07
12
+
13
+ - Fix daemonization
14
+ - [#223] https://github.com/phstc/shoryuken/pull/223
15
+
16
+ ## [v2.0.7] - 2016-06-06
17
+
18
+ - Daemonize before loading environment
19
+ - [#219] https://github.com/phstc/shoryuken/pull/219
20
+
21
+ - Fix initialization when using rails
22
+ - [#197] https://github.com/phstc/shoryuken/pull/197
23
+
24
+ - Improve message fetching
25
+ - https://github.com/phstc/shoryuken/pull/214 and https://github.com/phstc/shoryuken/commit/f4640d97950c1783a061195855d93994725ed64a
26
+
27
+ - Fix hard shutdown if there are some busy workers when signal received
28
+ - [#215] https://github.com/phstc/shoryuken/pull/215
29
+
30
+ - Fix `rake console` task
31
+ - [#208] https://github.com/phstc/shoryuken/pull/208
32
+
33
+ - Isolate `MessageVisibilityExtender` as new middleware
34
+ - [#199] https://github.com/phstc/shoryuken/pull/190
35
+
36
+ - Fail on non-existent queues
37
+ - [#196] https://github.com/phstc/shoryuken/pull/196
38
+
39
+ ## [v2.0.6] - 2016-04-18
40
+
41
+ - Fix log initialization introduced by #191
42
+ - [#195](https://github.com/phstc/shoryuken/pull/195)
43
+
44
+ ## [v2.0.5] - 2016-04-17
45
+
46
+ - Fix log initialization when using `Shoryuken::EnvironmentLoader#load`
47
+ - [#191](https://github.com/phstc/shoryuken/pull/191)
48
+
49
+ - Fix `enqueue_at` in the ActiveJob Adapter
50
+ - [#182](https://github.com/phstc/shoryuken/pull/182)
51
+
52
+ ## [v2.0.4] - 2016-02-04
2
53
 
3
54
  - Add Rails 3 support
4
55
  - [#175](https://github.com/phstc/shoryuken/pull/175)
data/README.md CHANGED
@@ -105,7 +105,7 @@ end
105
105
 
106
106
  [Check the Middleware documentation](https://github.com/phstc/shoryuken/wiki/Middleware).
107
107
 
108
- ### Configuration
108
+ ### Configuration (worker side)
109
109
 
110
110
  Sample configuration file `shoryuken.yml`.
111
111
 
@@ -136,6 +136,19 @@ The ```aws``` section is used to configure both the Aws objects used by Shoryuke
136
136
 
137
137
  The ```sns_endpoint``` and ```sqs_endpoint``` Shoryuken-specific options will also fallback to the environment variables ```AWS_SNS_ENDPOINT``` and ```AWS_SQS_ENDPOINT``` respectively, if they are set.
138
138
 
139
+ ### Configuration (producer side)
140
+
141
+ 'Producer' processes need permissions to put messages into SQS. There are a few ways:
142
+
143
+ * Ensure the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` env vars are set.
144
+ * Create a `~/.aws/credentials` file.
145
+ * Set `Aws.config[:credentials]` from Ruby code (e.g. in a Rails initializer)
146
+ * Use the Instance Profiles feature. The IAM role of the targeted machine must have an adequate SQS Policy.
147
+
148
+ Note that storing your credentials into Amazon instances represents a security risk. Instance Profiles tends to be the best choice.
149
+
150
+ You can read about these in more detail [here](http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html).
151
+
139
152
  ### Rails Integration
140
153
 
141
154
  [Check the Rails Integration Active Job documention](https://github.com/phstc/shoryuken/wiki/Rails-Integration-Active-Job).
data/Rakefile CHANGED
@@ -1,9 +1,16 @@
1
1
  require 'bundler/gem_tasks'
2
2
  $stdout.sync = true
3
3
 
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ rescue LoadError
8
+ end
9
+
4
10
  desc 'Open Shoryuken pry console'
5
11
  task :console do
6
12
  require 'pry'
13
+ require 'celluloid'
7
14
  require 'shoryuken'
8
15
 
9
16
  config_file = File.join File.expand_path('..', __FILE__), 'shoryuken.yml'
data/lib/shoryuken.rb CHANGED
@@ -15,6 +15,7 @@ require 'shoryuken/worker_registry'
15
15
  require 'shoryuken/default_worker_registry'
16
16
  require 'shoryuken/middleware/chain'
17
17
  require 'shoryuken/middleware/server/auto_delete'
18
+ Shoryuken::Middleware::Server.autoload :AutoExtendVisibility, 'shoryuken/middleware/server/auto_extend_visibility'
18
19
  require 'shoryuken/middleware/server/exponential_backoff_retry'
19
20
  require 'shoryuken/middleware/server/timing'
20
21
  require 'shoryuken/sns_arn'
@@ -126,8 +127,8 @@ module Shoryuken
126
127
  # end
127
128
  # end
128
129
  def on(event, &block)
129
- raise ArgumentError, "Symbols only please: #{event}" unless event.is_a?(Symbol)
130
- raise ArgumentError, "Invalid event name: #{event}" unless options[:lifecycle_events].key?(event)
130
+ fail ArgumentError, "Symbols only please: #{event}" unless event.is_a?(Symbol)
131
+ fail ArgumentError, "Invalid event name: #{event}" unless options[:lifecycle_events].key?(event)
131
132
  options[:lifecycle_events][event] << block
132
133
  end
133
134
 
@@ -142,6 +143,7 @@ module Shoryuken
142
143
  m.add Middleware::Server::Timing
143
144
  m.add Middleware::Server::ExponentialBackoffRetry
144
145
  m.add Middleware::Server::AutoDelete
146
+ m.add Middleware::Server::AutoExtendVisibility
145
147
  if defined?(::ActiveRecord::Base)
146
148
  require 'shoryuken/middleware/server/active_record'
147
149
  m.add Middleware::Server::ActiveRecord
data/lib/shoryuken/cli.rb CHANGED
@@ -31,10 +31,11 @@ module Shoryuken
31
31
 
32
32
  options = parse_cli_args(args)
33
33
 
34
+ daemonize(options)
35
+ write_pid(options)
36
+
34
37
  EnvironmentLoader.load(options)
35
38
 
36
- daemonize
37
- write_pid
38
39
  load_celluloid
39
40
 
40
41
  require 'shoryuken/launcher'
@@ -63,21 +64,27 @@ module Shoryuken
63
64
  private
64
65
 
65
66
  def load_celluloid
66
- raise "Celluloid cannot be required until here, or it will break Shoryuken's daemonization" if defined?(::Celluloid) && Shoryuken.options[:daemon]
67
-
68
- # Celluloid can't be loaded until after we've daemonized
69
- # because it spins up threads and creates locks which get
70
- # into a very bad state if forked.
71
67
  require 'celluloid/autostart'
72
68
  Celluloid.logger = (Shoryuken.options[:verbose] ? Shoryuken.logger : nil)
73
69
 
74
70
  require 'shoryuken/manager'
75
71
  end
76
72
 
77
- def daemonize
78
- return unless Shoryuken.options[:daemon]
73
+ def celluloid_loaded?
74
+ defined?(::Celluloid)
75
+ end
76
+
77
+ def daemonize(options)
78
+ return unless options[:daemon]
79
+
80
+ fail ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile]
79
81
 
80
- raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless Shoryuken.options[:logfile]
82
+ if celluloid_loaded?
83
+ # Celluloid can't be loaded until after we've daemonized
84
+ # because it spins up threads and creates locks which get
85
+ # into a very bad state if forked.
86
+ raise "Celluloid cannot be required until here, or it will break Shoryuken's daemonization"
87
+ end
81
88
 
82
89
  files_to_reopen = []
83
90
  ObjectSpace.each_object(File) do |file|
@@ -95,7 +102,7 @@ module Shoryuken
95
102
  end
96
103
 
97
104
  [$stdout, $stderr].each do |io|
98
- File.open(Shoryuken.options[:logfile], 'ab') do |f|
105
+ File.open(options[:logfile], 'ab') do |f|
99
106
  io.reopen(f)
100
107
  end
101
108
  io.sync = true
@@ -103,8 +110,8 @@ module Shoryuken
103
110
  $stdin.reopen('/dev/null')
104
111
  end
105
112
 
106
- def write_pid
107
- if (path = Shoryuken.options[:pidfile])
113
+ def write_pid(options)
114
+ if (path = options[:pidfile])
108
115
  File.open(path, 'w') do |f|
109
116
  f.puts Process.pid
110
117
  end
@@ -112,7 +119,7 @@ module Shoryuken
112
119
  end
113
120
 
114
121
  def parse_cli_args(argv)
115
- opts = { queues: [] }
122
+ opts = {}
116
123
 
117
124
  @parser = OptionParser.new do |o|
118
125
  o.on '-c', '--concurrency INT', 'Processor threads to use' do |arg|
@@ -125,6 +132,7 @@ module Shoryuken
125
132
 
126
133
  o.on '-q', '--queue QUEUE[,WEIGHT]...', 'Queues to process with optional weights' do |arg|
127
134
  queue, weight = arg.split(',')
135
+ opts[:queues] = [] unless opts[:queues]
128
136
  opts[:queues] << [queue, weight]
129
137
  end
130
138
 
@@ -30,9 +30,9 @@ module Shoryuken
30
30
  def register_worker(queue, clazz)
31
31
  if (worker_class = @workers[queue])
32
32
  if worker_class.get_shoryuken_options['batch'] == true || clazz.get_shoryuken_options['batch'] == true
33
- raise ArgumentError, "Could not register #{clazz} for '#{queue}', "\
33
+ fail ArgumentError, "Could not register #{clazz} for '#{queue}', "\
34
34
  "because #{worker_class} is already registered for this queue, "\
35
- "and Shoryuken doesn't support a batchable worker for a queue with multiple workers."
35
+ "and Shoryuken doesn't support a batchable worker for a queue with multiple workers"
36
36
  end
37
37
  end
38
38
 
@@ -15,12 +15,11 @@ module Shoryuken
15
15
  end
16
16
 
17
17
  def load
18
- initialize_logger
19
18
  load_rails if options[:rails]
20
- Shoryuken.options.merge!(config_file_options)
19
+ initialize_options
20
+ initialize_logger
21
21
  merge_cli_defined_queues
22
22
  prefix_active_job_queue_names
23
- Shoryuken.options.merge!(options)
24
23
  parse_queues
25
24
  require_workers
26
25
  initialize_aws
@@ -31,15 +30,15 @@ module Shoryuken
31
30
 
32
31
  private
33
32
 
33
+ def initialize_options
34
+ Shoryuken.options.merge!(config_file_options)
35
+ Shoryuken.options.merge!(options)
36
+ end
37
+
34
38
  def config_file_options
35
- if (path = options[:config_file])
36
- unless File.exist?(path)
37
- Shoryuken.logger.warn { "Config file #{path} does not exist" }
38
- path = nil
39
- end
40
- end
39
+ return {} unless (path = options[:config_file])
41
40
 
42
- return {} unless path
41
+ fail ArgumentError, "The supplied config file '#{path}' does not exist" unless File.exist?(path)
43
42
 
44
43
  YAML.load(ERB.new(IO.read(path)).result).deep_symbolize_keys
45
44
  end
@@ -96,8 +95,6 @@ module Shoryuken
96
95
  require 'shoryuken/extensions/active_job_adapter' if defined?(::ActiveJob)
97
96
  require File.expand_path('config/environment.rb')
98
97
  end
99
-
100
- Shoryuken.logger.info { 'Rails environment loaded' }
101
98
  end
102
99
 
103
100
  def merge_cli_defined_queues
@@ -162,13 +159,17 @@ module Shoryuken
162
159
  def validate_queues
163
160
  Shoryuken.logger.warn { 'No queues supplied' } if Shoryuken.queues.empty?
164
161
 
162
+ non_existent_queues = []
163
+
165
164
  Shoryuken.queues.uniq.each do |queue|
166
165
  begin
167
166
  Shoryuken::Client.queues queue
168
167
  rescue Aws::SQS::Errors::NonExistentQueue
169
- Shoryuken.logger.warn { "The specified queue '#{queue}' does not exist" }
168
+ non_existent_queues << queue
170
169
  end
171
170
  end
171
+
172
+ fail ArgumentError, "The specified queue(s) #{non_existent_queues} do not exist" if non_existent_queues.any?
172
173
  end
173
174
 
174
175
  def validate_workers
@@ -26,7 +26,7 @@ module ActiveJob
26
26
  end
27
27
 
28
28
  def enqueue_at(job, timestamp)
29
- instance.enqueue(job, timestamp)
29
+ instance.enqueue_at(job, timestamp)
30
30
  end
31
31
  end
32
32
 
@@ -47,15 +47,13 @@ module Shoryuken
47
47
  @manager.async.pause_queue!(queue)
48
48
  end
49
49
 
50
- @manager.async.dispatch
51
-
52
50
  logger.debug { "Fetcher for '#{queue}' completed in #{elapsed(started_at)} ms" }
53
51
  rescue => ex
54
52
  logger.error { "Error fetching message: #{ex}" }
55
53
  logger.error { ex.backtrace.first }
56
-
57
- @manager.async.dispatch
58
54
  end
55
+
56
+ @manager.async.dispatch
59
57
  end
60
58
 
61
59
  end
@@ -11,7 +11,8 @@ module Shoryuken
11
11
  trap_exit :processor_died
12
12
 
13
13
  def initialize(condvar)
14
- @count = Shoryuken.options[:concurrency] || 25
14
+ @count = Shoryuken.options[:concurrency] || 25
15
+ raise(ArgumentError, "Concurrency value #{@count} is invalid, it needs to be a positive number") unless @count > 0
15
16
  @queues = Shoryuken.queues.dup.uniq
16
17
  @finished = condvar
17
18
 
@@ -67,6 +68,7 @@ module Shoryuken
67
68
 
68
69
  if stopped?
69
70
  processor.terminate if processor.alive?
71
+ return after(0) { @finished.signal } if @busy.empty?
70
72
  else
71
73
  @ready << processor
72
74
  end
@@ -80,7 +82,9 @@ module Shoryuken
80
82
  @threads.delete(processor.object_id)
81
83
  @busy.delete processor
82
84
 
83
- unless stopped?
85
+ if stopped?
86
+ return after(0) { @finished.signal } if @busy.empty?
87
+ else
84
88
  @ready << build_processor
85
89
  end
86
90
  end
@@ -0,0 +1,53 @@
1
+ require 'celluloid' unless defined?(Celluloid)
2
+
3
+ module Shoryuken
4
+ module Middleware
5
+ module Server
6
+ class AutoExtendVisibility
7
+ EXTEND_UPFRONT_SECONDS = 5
8
+
9
+ def call(worker, queue, sqs_msg, body)
10
+ timer = auto_visibility_timer(queue, sqs_msg, worker.class)
11
+ begin
12
+ yield
13
+ ensure
14
+ timer.cancel if timer
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ class MessageVisibilityExtender
21
+ include Celluloid
22
+ include Util
23
+
24
+ def auto_extend(queue, sqs_msg, worker_class)
25
+ queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
26
+
27
+ every(queue_visibility_timeout - EXTEND_UPFRONT_SECONDS) do
28
+ begin
29
+ logger.debug do
30
+ "Extending message #{worker_name(worker_class, sqs_msg)}/#{queue}/#{sqs_msg.message_id} " \
31
+ "visibility timeout by #{queue_visibility_timeout}s."
32
+ end
33
+
34
+ sqs_msg.change_visibility(visibility_timeout: queue_visibility_timeout)
35
+ rescue => e
36
+ logger.error do
37
+ "Could not auto extend the message #{worker_class}/#{queue}/#{sqs_msg.message_id} " \
38
+ "visibility timeout. Error: #{e.message}"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def auto_visibility_timer(queue, sqs_msg, worker_class)
46
+ return unless worker_class.auto_visibility_timeout?
47
+ @visibility_extender ||= MessageVisibilityExtender.new_link
48
+ @visibility_extender.auto_extend(queue, sqs_msg, worker_class)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -16,9 +16,6 @@ module Shoryuken
16
16
 
17
17
  worker = Shoryuken.worker_registry.fetch_worker(queue, sqs_msg)
18
18
 
19
- timer = auto_visibility_timeout(queue, sqs_msg, worker.class)
20
-
21
- begin
22
19
  body = get_body(worker.class, sqs_msg)
23
20
 
24
21
  worker.class.server_middleware.invoke(worker, queue, sqs_msg, body) do
@@ -26,40 +23,10 @@ module Shoryuken
26
23
  end
27
24
 
28
25
  @manager.async.processor_done(queue, current_actor)
29
- ensure
30
- timer.cancel if timer
31
- end
32
26
  end
33
27
 
34
28
  private
35
29
 
36
- class MessageVisibilityExtender
37
- include Celluloid
38
- include Util
39
-
40
- def auto_extend(queue, sqs_msg, worker_class)
41
- queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
42
-
43
- every(queue_visibility_timeout - 5) do
44
- begin
45
- logger.debug { "Extending message #{worker_name(worker_class, sqs_msg)}/#{queue}/#{sqs_msg.message_id} visibility timeout by #{queue_visibility_timeout}s." }
46
-
47
- sqs_msg.visibility_timeout = queue_visibility_timeout
48
- rescue => e
49
- logger.error { "Could not auto extend the message #{worker_class}/#{queue}/#{sqs_msg.message_id} visibility timeout. Error: #{e.message}" }
50
- end
51
- end
52
- end
53
- end
54
-
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)
61
- end
62
-
63
30
  def get_body(worker_class, sqs_msg)
64
31
  if sqs_msg.is_a? Array
65
32
  sqs_msg.map { |m| parse_body(worker_class, m) }
@@ -1,3 +1,3 @@
1
1
  module Shoryuken
2
- VERSION = '2.0.4'
2
+ VERSION = '2.0.11'
3
3
  end
data/shoryuken.gemspec CHANGED
@@ -6,8 +6,8 @@ require 'shoryuken/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'shoryuken'
8
8
  spec.version = Shoryuken::VERSION
9
- spec.authors = ['Pablo Cantero']
10
- spec.email = ['pablo@pablocantero.com']
9
+ spec.authors = ['Pablo Cantero', 'Mario Kostelac']
10
+ spec.email = ['pablo@pablocantero.com', 'mariokostelac@gmail.com']
11
11
  spec.description = spec.summary = %q(Shoryuken is a super efficient AWS SQS thread based message processor)
12
12
  spec.homepage = 'https://github.com/phstc/shoryuken'
13
13
  spec.license = 'LGPL-3.0'
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+ require 'shoryuken/cli'
3
+ require 'shoryuken/launcher'
4
+
5
+ RSpec.describe Shoryuken::CLI do
6
+ let(:cli) { Shoryuken::CLI.instance }
7
+
8
+ before(:each) do
9
+ # make sure we do not bail
10
+ allow(cli).to receive(:exit)
11
+
12
+ # make sure we do not mess with standard streams
13
+ allow_any_instance_of(IO).to receive(:reopen)
14
+ end
15
+
16
+ describe '#run' do
17
+ let(:launcher) { instance_double('Shoryuken::Launcher') }
18
+
19
+ before(:each) do
20
+ allow(Shoryuken::Launcher).to receive(:new).and_return(launcher)
21
+ allow(launcher).to receive(:run).and_raise(Interrupt)
22
+ allow(launcher).to receive(:stop)
23
+ end
24
+
25
+ it 'does not raise' do
26
+ expect { cli.run([]) }.to_not raise_error
27
+ end
28
+
29
+ it 'daemonizes with --daemon --logfile' do
30
+ expect(cli).to receive(:celluloid_loaded?).and_return(false)
31
+ expect(Process).to receive(:daemon)
32
+ cli.run(['--daemon', '--logfile', '/dev/null'])
33
+ end
34
+
35
+ it 'does NOT daemonize with --daemon --logfile' do
36
+ expect(Process).to_not receive(:daemon)
37
+ cli.run(['--logfile', '/dev/null'])
38
+ end
39
+
40
+ it 'writes PID file with --pidfile' do
41
+ pidfile = instance_double('File')
42
+ expect(File).to receive(:open).with('/dev/null', 'w').and_yield(pidfile)
43
+ expect(pidfile).to receive(:puts).with(Process.pid)
44
+ cli.run(['--pidfile', '/dev/null'])
45
+ end
46
+ end
47
+
48
+ describe '#daemonize' do
49
+ before(:each) do
50
+ allow(cli).to receive(:celluloid_loaded?).and_return(false)
51
+ end
52
+
53
+ it 'raises if logfile is not set' do
54
+ expect { cli.send(:daemonize, daemon: true) }.to raise_error(ArgumentError)
55
+ end
56
+
57
+ it 'raises if Celluloid is already loaded' do
58
+ expect(cli).to receive(:celluloid_loaded?).and_return(true)
59
+ args = { daemon: true, logfile: '/dev/null' }
60
+ expect { cli.send(:daemonize, args) }.to raise_error(RuntimeError)
61
+ end
62
+
63
+ it 'calls Process.daemon' do
64
+ args = { daemon: true, logfile: '/dev/null' }
65
+ expect(Process).to receive(:daemon).with(true, true)
66
+ cli.send(:daemonize, args)
67
+ end
68
+ end
69
+ end
@@ -2,20 +2,20 @@ require 'spec_helper'
2
2
 
3
3
  RSpec.describe 'Core Extensions' do
4
4
  describe Hash do
5
- describe 'stringify_keys' do
5
+ describe '#stringify_keys' do
6
6
  it 'converts keys into strings' do
7
7
  expect({ :key1 => 'value1', 'key2' => 'value2' }.stringify_keys).to eq('key1' => 'value1', 'key2' => 'value2')
8
8
  end
9
9
  end
10
10
 
11
- describe 'symbolize_keys' do
12
- it 'converts keys into strings' do
11
+ describe '#symbolize_keys' do
12
+ it 'converts keys into symbols' do
13
13
  expect({ :key1 => 'value1', 'key2' => 'value2' }.symbolize_keys).to eq(:key1 => 'value1', key2: 'value2')
14
14
  end
15
15
  end
16
16
 
17
- describe 'deep_symbolize_keys' do
18
- it 'converts keys into strings' do
17
+ describe '#deep_symbolize_keys' do
18
+ it 'converts keys into symbols' do
19
19
  expect({ :key1 => 'value1',
20
20
  'key2' => 'value2',
21
21
  'key3' => {
@@ -1,13 +1,22 @@
1
1
  require 'spec_helper'
2
2
  require 'shoryuken/manager'
3
3
 
4
- describe Shoryuken::Manager do
4
+ RSpec.describe Shoryuken::Manager do
5
5
  subject do
6
6
  condvar = double(:condvar)
7
7
  allow(condvar).to receive(:signal).and_return(nil)
8
8
  Shoryuken::Manager.new(condvar)
9
9
  end
10
10
 
11
+ describe 'Invalid concurrency setting' do
12
+ it 'raises ArgumentError if concurrency is not positive number' do
13
+ Shoryuken.options[:concurrency] = -1
14
+ expect { Shoryuken::Manager.new(nil) }
15
+ .to raise_error(ArgumentError, 'Concurrency value -1 is invalid, it needs to be a positive number')
16
+ end
17
+
18
+ end
19
+
11
20
  describe 'Auto Scaling' do
12
21
  it 'decreases weight' do
13
22
  queue1 = 'shoryuken'
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ describe Shoryuken::Middleware::Server::AutoExtendVisibility do
4
+ let(:queue) { 'default' }
5
+ let(:visibility_timeout) { 3 }
6
+ let(:extend_upfront) { 1 }
7
+ let(:sqs_queue) { instance_double Shoryuken::Queue, visibility_timeout: visibility_timeout }
8
+
9
+ def build_message
10
+ double Shoryuken::Message, queue_url: queue, body: 'test', receipt_handle: SecureRandom.uuid
11
+ end
12
+
13
+ # We need to run our worker inside actor context.
14
+ class Runner
15
+ include Celluloid
16
+
17
+ def run_and_sleep(worker, queue, sqs_msg, interval)
18
+ Shoryuken::Middleware::Server::AutoExtendVisibility.new.call(worker, queue, sqs_msg, sqs_msg.body) do
19
+ sleep interval
20
+ end
21
+ end
22
+
23
+ def run_and_raise(worker, queue, sqs_msg, error_class)
24
+ Shoryuken::Middleware::Server::AutoExtendVisibility.new.call(worker, queue, sqs_msg, sqs_msg.body) do
25
+ raise error_class.new
26
+ end
27
+ end
28
+ end
29
+
30
+ let(:sqs_msg) { build_message }
31
+
32
+ before do
33
+ allow(Shoryuken::Client).to receive(:queues).with(queue).and_return(sqs_queue)
34
+ stub_const('Shoryuken::Middleware::Server::AutoExtendVisibility::EXTEND_UPFRONT_SECONDS', extend_upfront)
35
+ end
36
+
37
+ it 'extends message visibility if jobs takes a long time' do
38
+ TestWorker.get_shoryuken_options['auto_visibility_timeout'] = true
39
+
40
+ allow(sqs_msg).to receive(:queue) { sqs_queue }
41
+ expect(sqs_msg).to receive(:change_visibility).with(visibility_timeout: visibility_timeout)
42
+
43
+ Runner.new.run_and_sleep(TestWorker.new, queue, sqs_msg, visibility_timeout)
44
+ end
45
+
46
+ it 'does not extend message visibility if worker raises' do
47
+ TestWorker.get_shoryuken_options['auto_visibility_timeout'] = true
48
+
49
+ allow(sqs_msg).to receive(:queue) { sqs_queue }
50
+ expect(sqs_msg).to_not receive(:change_visibility)
51
+
52
+ expect { Runner.new.run_and_raise(TestWorker.new, queue, sqs_msg, StandardError) }.to raise_error(StandardError)
53
+ end
54
+
55
+ it 'does not extend message visibility if auto_visibility_timeout is not true' do
56
+ TestWorker.get_shoryuken_options['auto_visibility_timeout'] = false
57
+
58
+ allow(sqs_msg).to receive(:queue) { sqs_queue }
59
+ expect(sqs_msg).to_not receive(:change_visibility)
60
+
61
+ Runner.new.run_and_sleep(TestWorker.new, queue, sqs_msg, visibility_timeout)
62
+ end
63
+ end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
  require 'shoryuken/processor'
3
3
  require 'shoryuken/manager'
4
4
 
5
- describe Shoryuken::Processor do
5
+ RSpec.describe Shoryuken::Processor do
6
6
  let(:manager) { double Shoryuken::Manager, processor_done: nil }
7
7
  let(:sqs_queue) { double Shoryuken::Queue, visibility_timeout: 30 }
8
8
  let(:queue) { 'default' }
@@ -100,14 +100,16 @@ describe Shoryuken::Processor do
100
100
 
101
101
  it 'logs the error' do
102
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")
103
+ expect(block.call).
104
+ to include("unexpected token at 'invalid json'\nbody_parser: json\nsqs_msg.body: invalid json")
104
105
  end
105
106
 
106
107
  subject.process(queue, sqs_msg) rescue nil
107
108
  end
108
109
 
109
110
  it 're raises the error' do
110
- expect{ subject.process(queue, sqs_msg) }.to raise_error(JSON::ParserError, "757: unexpected token at 'invalid json'")
111
+ expect { subject.process(queue, sqs_msg) }.
112
+ to raise_error(JSON::ParserError, /unexpected token at 'invalid json'/)
111
113
  end
112
114
  end
113
115
 
@@ -257,50 +259,4 @@ describe Shoryuken::Processor do
257
259
  end
258
260
  end
259
261
  end
260
-
261
- describe '#auto_visibility_timeout' do
262
- let(:heartbeat) { sqs_queue.visibility_timeout - 5 }
263
- let(:visibility_timeout) { sqs_queue.visibility_timeout }
264
-
265
- before do
266
- TestWorker.get_shoryuken_options['auto_visibility_timeout'] = true
267
-
268
- allow(sqs_queue).to receive(:visibility_timeout).and_return(15)
269
- end
270
-
271
- context 'when the worker takes a long time', slow: true do
272
- it 'extends the message invisibility to prevent it from being dequeued concurrently' do
273
- TestWorker.get_shoryuken_options['body_parser'] = proc do
274
- sleep visibility_timeout
275
- 'test'
276
- end
277
-
278
- expect(sqs_msg).to receive(:visibility_timeout=).with(visibility_timeout).once
279
- expect(manager).to receive(:processor_done).with(queue, subject)
280
-
281
- allow(sqs_msg).to receive(:body).and_return('test')
282
-
283
- subject.process(queue, sqs_msg)
284
- end
285
- end
286
-
287
- context 'when the worker takes a short time' do
288
- it 'does not extend the message invisibility' do
289
- expect(sqs_msg).to receive(:visibility_timeout=).never
290
- expect(manager).to receive(:processor_done).with(queue, subject)
291
-
292
- allow(sqs_msg).to receive(:body).and_return('test')
293
-
294
- subject.process(queue, sqs_msg)
295
- end
296
- end
297
-
298
- context 'when the worker fails' do
299
- it 'does not extend the message invisibility' do
300
- expect(sqs_msg).to receive(:visibility_timeout=).never
301
- expect_any_instance_of(TestWorker).to receive(:perform).and_raise 'worker failed'
302
- expect { subject.process(queue, sqs_msg) }.to raise_error(RuntimeError, 'worker failed')
303
- end
304
- end
305
- end
306
262
  end
@@ -45,7 +45,7 @@ describe Shoryuken do
45
45
  def perform(sqs_msg, body); end
46
46
  end
47
47
  }.to raise_error("Could not register BatchableWorker for 'default', because TestWorker is already registered for this queue, " \
48
- "and Shoryuken doesn't support a batchable worker for a queue with multiple workers.")
48
+ "and Shoryuken doesn't support a batchable worker for a queue with multiple workers")
49
49
  end
50
50
  end
51
51
  end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shoryuken
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.4
4
+ version: 2.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Cantero
8
+ - Mario Kostelac
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2016-02-14 00:00:00.000000000 Z
12
+ date: 2016-07-02 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: bundler
@@ -125,6 +126,7 @@ dependencies:
125
126
  description: Shoryuken is a super efficient AWS SQS thread based message processor
126
127
  email:
127
128
  - pablo@pablocantero.com
129
+ - mariokostelac@gmail.com
128
130
  executables:
129
131
  - shoryuken
130
132
  extensions: []
@@ -158,6 +160,7 @@ files:
158
160
  - lib/shoryuken/middleware/chain.rb
159
161
  - lib/shoryuken/middleware/server/active_record.rb
160
162
  - lib/shoryuken/middleware/server/auto_delete.rb
163
+ - lib/shoryuken/middleware/server/auto_extend_visibility.rb
161
164
  - lib/shoryuken/middleware/server/exponential_backoff_retry.rb
162
165
  - lib/shoryuken/middleware/server/timing.rb
163
166
  - lib/shoryuken/processor.rb
@@ -172,6 +175,7 @@ files:
172
175
  - shoryuken.jpg
173
176
  - spec/integration/launcher_spec.rb
174
177
  - spec/shoryuken.yml
178
+ - spec/shoryuken/cli_spec.rb
175
179
  - spec/shoryuken/client_spec.rb
176
180
  - spec/shoryuken/core_ext_spec.rb
177
181
  - spec/shoryuken/default_worker_registry_spec.rb
@@ -179,6 +183,7 @@ files:
179
183
  - spec/shoryuken/manager_spec.rb
180
184
  - spec/shoryuken/middleware/chain_spec.rb
181
185
  - spec/shoryuken/middleware/server/auto_delete_spec.rb
186
+ - spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb
182
187
  - spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb
183
188
  - spec/shoryuken/middleware/server/timing_spec.rb
184
189
  - spec/shoryuken/processor_spec.rb
@@ -210,13 +215,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
210
215
  version: '0'
211
216
  requirements: []
212
217
  rubyforge_project:
213
- rubygems_version: 2.2.5
218
+ rubygems_version: 2.5.1
214
219
  signing_key:
215
220
  specification_version: 4
216
221
  summary: Shoryuken is a super efficient AWS SQS thread based message processor
217
222
  test_files:
218
223
  - spec/integration/launcher_spec.rb
219
224
  - spec/shoryuken.yml
225
+ - spec/shoryuken/cli_spec.rb
220
226
  - spec/shoryuken/client_spec.rb
221
227
  - spec/shoryuken/core_ext_spec.rb
222
228
  - spec/shoryuken/default_worker_registry_spec.rb
@@ -224,6 +230,7 @@ test_files:
224
230
  - spec/shoryuken/manager_spec.rb
225
231
  - spec/shoryuken/middleware/chain_spec.rb
226
232
  - spec/shoryuken/middleware/server/auto_delete_spec.rb
233
+ - spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb
227
234
  - spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb
228
235
  - spec/shoryuken/middleware/server/timing_spec.rb
229
236
  - spec/shoryuken/processor_spec.rb