shoryuken 2.1.3 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +2 -0
  3. data/.rubocop.yml +8 -2
  4. data/.travis.yml +1 -0
  5. data/CHANGELOG.md +19 -0
  6. data/README.md +20 -104
  7. data/Rakefile +0 -1
  8. data/bin/cli/base.rb +42 -0
  9. data/bin/cli/sqs.rb +188 -0
  10. data/bin/shoryuken +47 -9
  11. data/examples/default_worker.rb +1 -1
  12. data/lib/shoryuken.rb +75 -55
  13. data/lib/shoryuken/client.rb +3 -15
  14. data/lib/shoryuken/default_worker_registry.rb +9 -5
  15. data/lib/shoryuken/environment_loader.rb +9 -40
  16. data/lib/shoryuken/fetcher.rb +16 -18
  17. data/lib/shoryuken/launcher.rb +5 -28
  18. data/lib/shoryuken/manager.rb +60 -140
  19. data/lib/shoryuken/message.rb +4 -13
  20. data/lib/shoryuken/middleware/chain.rb +1 -18
  21. data/lib/shoryuken/middleware/server/auto_extend_visibility.rb +7 -16
  22. data/lib/shoryuken/middleware/server/exponential_backoff_retry.rb +25 -21
  23. data/lib/shoryuken/polling.rb +2 -4
  24. data/lib/shoryuken/processor.rb +2 -11
  25. data/lib/shoryuken/queue.rb +1 -3
  26. data/lib/shoryuken/runner.rb +143 -0
  27. data/lib/shoryuken/util.rb +0 -8
  28. data/lib/shoryuken/version.rb +1 -1
  29. data/lib/shoryuken/worker.rb +1 -1
  30. data/shoryuken.gemspec +6 -5
  31. data/spec/integration/launcher_spec.rb +4 -3
  32. data/spec/shoryuken/client_spec.rb +2 -45
  33. data/spec/shoryuken/default_worker_registry_spec.rb +12 -10
  34. data/spec/shoryuken/environment_loader_spec.rb +34 -0
  35. data/spec/shoryuken/manager_spec.rb +11 -21
  36. data/spec/shoryuken/middleware/chain_spec.rb +0 -24
  37. data/spec/shoryuken/middleware/server/auto_extend_visibility_spec.rb +0 -2
  38. data/spec/shoryuken/middleware/server/exponential_backoff_retry_spec.rb +46 -29
  39. data/spec/shoryuken/processor_spec.rb +5 -5
  40. data/spec/shoryuken/{cli_spec.rb → runner_spec.rb} +8 -22
  41. data/spec/shoryuken_spec.rb +13 -1
  42. data/spec/spec_helper.rb +3 -8
  43. metadata +29 -22
  44. data/lib/shoryuken/aws_config.rb +0 -64
  45. data/lib/shoryuken/cli.rb +0 -215
  46. data/lib/shoryuken/sns_arn.rb +0 -27
  47. data/lib/shoryuken/topic.rb +0 -17
  48. data/spec/shoryuken/sns_arn_spec.rb +0 -42
  49. data/spec/shoryuken/topic_spec.rb +0 -32
  50. data/spec/shoryuken_endpoint.yml +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 84439f7b9ae680a0363a63e98c6cbba645541b86
4
- data.tar.gz: 72e3f662b660f594d78bb4328b137c43774eaf9d
3
+ metadata.gz: 3810f509ebbedaf23dfcd756b17f487097d99ab7
4
+ data.tar.gz: 2dabc0be8dbab5536377b7fe6cb6e371b1c0a71c
5
5
  SHA512:
6
- metadata.gz: 2c50f7e305327dc7e155c84a8e520579b3d72f0e5d0d224d6b9a0e346a6923c5348e66bd1c63e3a3cbd9ed22adc488680194266d80004556670250c7cc8db450
7
- data.tar.gz: c0154a86dd1098ff213c55411fb6fcc50b6a5f2eebb5d207443cce9952355ed5f2ab191f5d321ac91322bb2f1e44b3099404d7b0018aaf300d450f87450d85c2
6
+ metadata.gz: 5fe6096c4df914e64b56dcbcad889bda4827cbf6882ab018d4e8568cab9caf61f548a6117ba3b5607da13abcad049be58c59d8498d76abcc0b6f366579eaa79d
7
+ data.tar.gz: 61c52ec5a273c3294679baff1b00f5938d1aec218f0a304ff7503c2c28f537407306380e8fce9e954ba4caa92113e682289a68de42dd26e3d5515f9714e30d92
data/.codeclimate.yml CHANGED
@@ -11,6 +11,8 @@ engines:
11
11
  enabled: true
12
12
  rubocop:
13
13
  enabled: true
14
+ config:
15
+ file: .rubocop.yml
14
16
  ratings:
15
17
  paths:
16
18
  - "**.rb"
data/.rubocop.yml CHANGED
@@ -1,9 +1,12 @@
1
- LineLength:
2
- Max: 120
1
+ AllCops:
2
+ TargetRubyVersion: 2.0
3
3
 
4
4
  Style/SignalException:
5
5
  Enabled: false
6
6
 
7
+ Style/DoubleNegation:
8
+ Enabled: false
9
+
7
10
  Style/SpaceAroundEqualsInParameterDefault:
8
11
  Enabled: false
9
12
 
@@ -28,6 +31,9 @@ Metrics/ClassLength:
28
31
  Metrics/ParameterLists:
29
32
  Enabled: false
30
33
 
34
+ Metrics/LineLength:
35
+ Max: 120
36
+
31
37
  Metrics/MethodLength :
32
38
  Enabled: false
33
39
 
data/.travis.yml CHANGED
@@ -3,6 +3,7 @@ rvm:
3
3
  - 2.0.0
4
4
  - 2.1.0
5
5
  - 2.2.0
6
+ - 2.3.3
6
7
 
7
8
  notifications:
8
9
  email:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## [v3.0.0] - 2017-03-12
2
+ - Replace Celluloid with Concurrent Ruby
3
+ - [#291](https://github.com/phstc/shoryuken/pull/291)
4
+
5
+ - Remove AWS configuration from Shoryuken. Now AWS should be configured from outside. Check [this](https://github.com/phstc/shoryuken/wiki/Configure-the-AWS-Client) for more details
6
+ - [#317](https://github.com/phstc/shoryuken/pull/291)
7
+
8
+ - Remove deprecation warnings
9
+ - [#326](https://github.com/phstc/shoryuken/pull/326)
10
+
11
+ - Allow dynamic adding queues
12
+ - [#322](https://github.com/phstc/shoryuken/pull/326)
13
+
14
+ - Support retry_intervals passed in as a lambda. Auto coerce intervals into integer
15
+ - [#329](https://github.com/phstc/shoryuken/pull/329)
16
+
17
+ - Add SQS commands `shoryuken help sqs`, such `ls`, `mv`, `dump` and `requeue`
18
+ - [#330](https://github.com/phstc/shoryuken/pull/330)
19
+
1
20
  ## [v2.1.3] - 2017-01-27
2
21
  - Show a warn message when batch isn't supported
3
22
  - [#302](https://github.com/phstc/shoryuken/pull/302)
data/README.md CHANGED
@@ -20,18 +20,18 @@ concurrency: 25
20
20
  delay: 25
21
21
  queues:
22
22
  - [high_priority, 6]
23
- - [default, 2]
23
+ - [normal_priority, 2]
24
24
  - [low_priority, 1]
25
25
  ```
26
26
 
27
- And supposing all the queues are full of messages, the configuration above will make Shoryuken to process `high_priority` 3 times more than `default` and 6 times more than `low_priority`,
28
- splitting the work among the `concurrency: 25` available processors.
27
+ And supposing all the queues are full of messages, the configuration above will make Shoryuken to process `high_priority` 3 times more than `normal_priority` and 6 times more than `low_priority`,
28
+ splitting the work load among all available processors `concurrency: 25` .
29
29
 
30
- If `high_priority` gets empty, Shoryuken will keep using the 25 processors, but only to process `default` (2 times more than `low_priority`) and `low_priority`.
30
+ If `high_priority` gets empty, Shoryuken will keep using the 25 processors, but only to process `normal_priority` and `low_priority`.
31
31
 
32
- If `high_priority` receives a new message, Shoryuken will smoothly increase back the `high_priority` weight one by one until it reaches the weight of 6 again, which is the maximum configured for `high_priority`.
32
+ If `high_priority` receives a new message, Shoryuken will smoothly increase back its weight one by one until it reaches the weight of 6 again.
33
33
 
34
- If all queues get empty, all processors will be changed to the waiting state and the queues will be checked every `delay: 25`. If any queue receives a new message, Shoryuken will start processing again. [Check the delay option documentation for more information](https://github.com/phstc/shoryuken/wiki/Shoryuken-options#delay).
34
+ [If a queue gets empty, Shoryuken will pause checking it for `delay: 25`](https://github.com/phstc/shoryuken/wiki/Shoryuken-options#delay).
35
35
 
36
36
 
37
37
  ### Fetch in batches
@@ -40,7 +40,7 @@ To be even more performant and cost effective, Shoryuken fetches SQS messages in
40
40
 
41
41
  ## Requirements
42
42
 
43
- Ruby 2.0 or greater. Ruby 1.9 is no longer supported.
43
+ Ruby 2.0 or greater.
44
44
 
45
45
  ## Installation
46
46
 
@@ -105,7 +105,7 @@ end
105
105
 
106
106
  [Check the Middleware documentation](https://github.com/phstc/shoryuken/wiki/Middleware).
107
107
 
108
- ### Configuration (worker side)
108
+ ### Shoryuken Configuration
109
109
 
110
110
  Sample configuration file `shoryuken.yml`.
111
111
 
@@ -114,87 +114,13 @@ concurrency: 25 # The number of allocated threads to process messages. Default
114
114
  delay: 25 # The delay in seconds to pause a queue when it's empty. Default 0
115
115
  queues:
116
116
  - [high_priority, 6]
117
- - [default, 2]
117
+ - [normal_priority, 2]
118
118
  - [low_priority, 1]
119
119
  ```
120
120
 
121
- And setup ```aws``` options to use ```configure_client``` in `config/initializers/shoryuken.rb`:
121
+ #### AWS Configuration
122
122
 
123
- ```ruby
124
- Shoryuken.configure_client do |config|
125
- config.aws = {
126
- secret_access_key: ..., # or ENV["AWS_SECRET_ACCESS_KEY"]
127
- access_key_id: ..., # or ENV["AWS_ACCESS_KEY_ID"]
128
- region: "us-east-1", # or ENV["AWS_REGION"]
129
- receive_message: { # See http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#receive_message-instance_method
130
- # 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
131
- attribute_names: [
132
- "ApproximateReceiveCount",
133
- "SentTimestamp"
134
- ]
135
- }
136
- }
137
- end
138
- ```
139
-
140
- If you use Shoryuken with plain ruby worker class (not Rails), please call `configure_client` at the beginning of the worker file:
141
-
142
- ```ruby
143
- Shoryuken.configure_client do |config|
144
- config.aws = {
145
- secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
146
- access_key_id: ENV["AWS_ACCESS_KEY_ID"],
147
- region: ENV["AWS_REGION"]
148
- }
149
- end
150
-
151
- class MyWorker
152
- end
153
- ```
154
-
155
- The `aws` section is used to configure both the Aws objects used by Shoryuken internally, and also to set up some Shoryuken-specific config. The Shoryuken-specific keys are listed below, and you can expect any other key defined in that block to be passed on untouched to `Aws::SQS::Client#initialize`:
156
-
157
- - `account_id` is used when generating SNS ARNs
158
- - `sns_endpoint` can be used to explicitly override the SNS endpoint
159
- - `sqs_endpoint` can be used to explicitly override the SQS endpoint
160
- - `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
161
-
162
- 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.
163
-
164
- ### Configuration (producer side)
165
-
166
- 'Producer' processes need permissions to put messages into SQS. There are a few ways:
167
-
168
- * Use the `configure_server` in Rails initializer
169
- * Ensure the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` env vars are set.
170
- * Create a `~/.aws/credentials` file.
171
- * Set `Aws.config[:credentials]` from Ruby code (e.g. in a Rails initializer)
172
- * Use the Instance Profiles feature. The IAM role of the targeted machine must have an adequate SQS Policy.
173
-
174
- For example, use the `configure_server` in `config/initializers/shoryuken.rb`:
175
-
176
- ```ruby
177
- Shoryuken.configure_client do |config|
178
- config.aws = {
179
- secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
180
- access_key_id: ENV["AWS_ACCESS_KEY_ID"],
181
- region: ENV["AWS_REGION"]
182
- }
183
- end
184
-
185
- Shoryuken.configure_server do |config|
186
- config.aws = {
187
- secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
188
- access_key_id: ENV["AWS_ACCESS_KEY_ID"],
189
- region: ENV["AWS_REGION"]
190
- }
191
- end
192
- ```
193
-
194
-
195
- Note that storing your credentials into Amazon instances represents a security risk. Instance Profiles tends to be the best choice.
196
-
197
- You can read about these in more detail [here](http://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html).
123
+ [Check the Configure AWS Client documentation](https://github.com/phstc/shoryuken/wiki/Configure-the-AWS-Client)
198
124
 
199
125
  ### Rails Integration
200
126
 
@@ -206,26 +132,16 @@ You can read about these in more detail [here](http://docs.aws.amazon.com/sdkfor
206
132
  bundle exec shoryuken -r worker.rb -C shoryuken.yml
207
133
  ```
208
134
 
209
- Other options:
210
-
211
- ```bash
212
- shoryuken --help
213
-
214
- shoryuken [options]
215
- -c, --concurrency INT Processor threads to use
216
- -d, --daemon Daemonize process
217
- -q, --queue QUEUE[,WEIGHT]... Queues to process with optional weights
218
- -r, --require [PATH|DIR] Location of the worker
219
- -C, --config PATH Path to YAML config file
220
- -R, --rails Attempts to load the containing Rails project
221
- -L, --logfile PATH Path to writable logfile
222
- -P, --pidfile PATH Path to pidfile
223
- -v, --verbose Print more verbose output
224
- -V, --version Print version and exit
225
- -h, --help Show help
226
- ...
227
- ```
135
+ For other options check `bundle exec shoryuken help start`
136
+
137
+ #### SQS commands
138
+
139
+ Check also some available SQS commands `bundle exec shoryuken help sqs`, such as:
228
140
 
141
+ - `ls` list queues
142
+ - `mv` move messages from one queue to another
143
+ - `dump` dump messages from a queue into a JSON lines file
144
+ - `requeue` requeue messages from a dump file
229
145
 
230
146
  ## More Information
231
147
 
data/Rakefile CHANGED
@@ -10,7 +10,6 @@ end
10
10
  desc 'Open Shoryuken pry console'
11
11
  task :console do
12
12
  require 'pry'
13
- require 'celluloid/current'
14
13
  require 'shoryuken'
15
14
 
16
15
  config_file = File.join File.expand_path('..', __FILE__), 'shoryuken.yml'
data/bin/cli/base.rb ADDED
@@ -0,0 +1,42 @@
1
+ # rubocop:disable Metrics/BlockLength
2
+ module Shoryuken
3
+ module CLI
4
+ class Base < Thor
5
+ no_commands do
6
+ def print_table(entries)
7
+ column_sizes = print_columns_size(entries)
8
+
9
+ entries.map do |entry|
10
+ puts entry.map.with_index { |e, i| print_format_column(e, column_sizes[i]) }.join
11
+ end
12
+ end
13
+
14
+ def print_columns_size(entries)
15
+ column_sizes = Hash.new(0)
16
+
17
+ entries.each do |entry|
18
+ entry.each_with_index do |e, i|
19
+ e = e.to_s
20
+ column_sizes[i] = e.size if column_sizes[i] < e.size
21
+ end
22
+ end
23
+
24
+ column_sizes
25
+ end
26
+
27
+ def print_format_column(column, size)
28
+ size = 40 if size > 40
29
+ size_with_padding = size + 4
30
+ column = column.to_s.ljust(size_with_padding)
31
+ column = "#{column[0...size - 2]}.." if column.size > size_with_padding
32
+ column
33
+ end
34
+
35
+ def fail_task(msg, quit = true)
36
+ say "[FAIL] #{msg}", :red
37
+ exit(1) if quit
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
data/bin/cli/sqs.rb ADDED
@@ -0,0 +1,188 @@
1
+ require 'date'
2
+
3
+ # rubocop:disable Metrics/AbcSize, Metrics/BlockLength
4
+ module Shoryuken
5
+ module CLI
6
+ class SQS < Base
7
+ namespace :sqs
8
+
9
+ no_commands do
10
+ def normalize_dump_message(message)
11
+ message[:id] = message.delete(:message_id)
12
+ message[:message_body] = message.delete(:body)
13
+ message.delete(:receipt_handle)
14
+ message.delete(:md5_of_body)
15
+ message.delete(:md5_of_message_attributes)
16
+ message
17
+ end
18
+
19
+ def sqs
20
+ @_sqs ||= Aws::SQS::Client.new
21
+ end
22
+
23
+ def find_queue_url(queue_name_prefix)
24
+ urls = sqs.list_queues(queue_name_prefix: queue_name_prefix).queue_urls
25
+
26
+ if urls.size > 1
27
+ fail_task "There's more than one queue starting with #{queue_name_prefix}: #{urls.join(', ')}"
28
+ end
29
+
30
+ url = urls.first
31
+
32
+ fail_task "Queue #{queue_name_prefix} not found" unless url
33
+
34
+ url
35
+ end
36
+
37
+ def batch_delete(url, messages)
38
+ messages.to_a.flatten.each_slice(10) do |batch|
39
+ sqs.delete_message_batch(
40
+ queue_url: url,
41
+ entries: batch.map { |message| { id: message.message_id, receipt_handle: message.receipt_handle } }
42
+ ).failed.any? do |failure|
43
+ say "Could not delete #{failure.id}, code: #{failure.code}", :yellow
44
+ end
45
+ end
46
+ end
47
+
48
+ def batch_send(url, messages)
49
+ messages.to_a.flatten.map(&method(:normalize_dump_message)).each_slice(10) do |batch|
50
+ sqs.send_message_batch(queue_url: url, entries: batch).failed.any? do |failure|
51
+ say "Could not requeue #{failure.id}, code: #{failure.code}", :yellow
52
+ end
53
+ end
54
+ end
55
+
56
+ def find_all(url, limit, &block)
57
+ count = 0
58
+ batch_size = limit > 10 ? 10 : limit
59
+
60
+ loop do
61
+ n = limit - count
62
+ batch_size = n if n < batch_size
63
+
64
+ messages = sqs.receive_message(
65
+ queue_url: url,
66
+ max_number_of_messages: batch_size,
67
+ message_attribute_names: ['All']
68
+ ).messages
69
+
70
+ messages.each { |m| yield m }
71
+
72
+ count += messages.size
73
+
74
+ break if count >= limit
75
+ break if messages.empty?
76
+ end
77
+
78
+ count
79
+ end
80
+
81
+ def list_and_print_queues(urls)
82
+ attrs = %w(QueueArn ApproximateNumberOfMessages ApproximateNumberOfMessagesNotVisible LastModifiedTimestamp)
83
+
84
+ entries = urls.map { |u| sqs.get_queue_attributes(queue_url: u, attribute_names: attrs).attributes }.map do |q|
85
+ [
86
+ q['QueueArn'].split(':').last,
87
+ q['ApproximateNumberOfMessages'],
88
+ q['ApproximateNumberOfMessagesNotVisible'],
89
+ Time.at(q['LastModifiedTimestamp'].to_i)
90
+ ]
91
+ end
92
+
93
+ entries.unshift(['Queue', 'Messages Available', 'Messages Inflight', 'Last Modified'])
94
+
95
+ print_table(entries)
96
+ end
97
+
98
+ def dump_file(path, queue_name)
99
+ File.join(path, "#{queue_name}-#{Date.today}.jsonl")
100
+ end
101
+ end
102
+
103
+ desc 'ls [QUEUE-NAME-PREFIX]', 'List queues'
104
+ method_option :watch, aliases: '-w', type: :boolean, desc: 'watch queues'
105
+ method_option :watch_interval, type: :numeric, default: 10, desc: 'watch interval'
106
+ def ls(queue_name_prefix = '')
107
+ trap('SIGINT', 'EXIT') # expect ctrl-c from loop
108
+
109
+ urls = sqs.list_queues(queue_name_prefix: queue_name_prefix).queue_urls
110
+
111
+ loop do
112
+ list_and_print_queues(urls)
113
+
114
+ break unless options.watch
115
+
116
+ sleep options.watch_interval
117
+ puts
118
+ end
119
+ end
120
+
121
+ desc 'dump QUEUE-NAME', 'Dump messages from a queue into a JSON lines file'
122
+ method_option :number, aliases: '-n', type: :numeric, default: Float::INFINITY, desc: 'number of messages to dump'
123
+ method_option :path, aliases: '-p', type: :string, default: './', desc: 'path to save the dump file'
124
+ method_option :delete, aliases: '-d', type: :boolean, default: true, desc: 'delete from the queue'
125
+ def dump(queue_name)
126
+ path = dump_file(options.path, queue_name)
127
+
128
+ fail_task "File #{path} already exists" if File.exist?(path)
129
+
130
+ url = find_queue_url(queue_name)
131
+
132
+ messages = []
133
+
134
+ file = nil
135
+
136
+ count = find_all(url, options.number) do |m|
137
+ file ||= File.open(path, 'w')
138
+
139
+ file.puts(JSON.dump(m.to_h))
140
+
141
+ messages << m if options.delete
142
+ end
143
+
144
+ batch_delete(url, messages) if options.delete
145
+
146
+ if count.zero?
147
+ say "Queue #{queue_name} is empty", :yellow
148
+ else
149
+ say "Dump saved in #{path} with #{count} messages", :green
150
+ end
151
+ ensure
152
+ file.close if file
153
+ end
154
+
155
+ desc 'requeue QUEUE-NAME PATH', 'Requeue messages from a dump file'
156
+ def requeue(queue_name, path)
157
+ fail_task "Path #{path} not found" unless File.exist?(path)
158
+
159
+ messages = File.readlines(path).map { |line| JSON.parse(line, symbolize_names: true) }
160
+
161
+ batch_send(find_queue_url(queue_name), messages)
162
+
163
+ say "Requeued #{messages.size} messages from #{path} to #{queue_name}", :green
164
+ end
165
+
166
+ desc 'mv QUEUE-NAME-SOURCE QUEUE-NAME-TARGET', 'Move messages from one queue (source) to another (target)'
167
+ method_option :number, aliases: '-n', type: :numeric, default: Float::INFINITY, desc: 'number of messages to move'
168
+ method_option :delete, aliases: '-d', type: :boolean, default: true, desc: 'delete from the queue'
169
+ def mv(queue_name_source, queue_name_target)
170
+ url_source = find_queue_url(queue_name_source)
171
+ messages = []
172
+
173
+ count = find_all(url_source, options.number) do |m|
174
+ messages << m
175
+ end
176
+
177
+ batch_send(find_queue_url(queue_name_target), messages.map(&:to_h))
178
+ batch_delete(url_source, messages) if options.delete
179
+
180
+ if count.zero?
181
+ say "Queue #{queue_name_source} is empty", :yellow
182
+ else
183
+ say "Moved #{count} messages from #{queue_name_source} to #{queue_name_target}", :green
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end