shoryuken 0.0.4 → 0.0.5

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: 70a39ae890a6fe9dcff44b0c6ceeab9c77931ab2
4
- data.tar.gz: ff223f629ed2f60cc0abb32fa17f28e15d680d2f
3
+ metadata.gz: 93f2193b2c553192ca3d38d1dbcd981bbaa35eeb
4
+ data.tar.gz: d46dcc643bb7fffeb78f4e2f6ca7f4e1b236d688
5
5
  SHA512:
6
- metadata.gz: f0afdb29904c40200d1976f2b26973ae6694e7018b07bf133153c355e250a1d613cf1fd9c0500c2aaa612d77f4c5d631823ab186642b4a2051a76ea78feea4e6
7
- data.tar.gz: 8b3d0dc3f255b3d921386a322e645e7875f2962c902094a48671d24a95211b02e5f0558be53e30619baf92551e9f3d7e23b60def11a2c11b84107525e339f141
6
+ metadata.gz: 5827e0327eba8f21ab4156d197ea0fd5a8587aa86afda60d14ffc9269e5606aa9688060d59d711821bc117d69ae49a41c0837aaea09433dbb3081fc66b36b9d3
7
+ data.tar.gz: fcc6b86772574af993cf9145f4d1b0f5bd3e4900937f13c2328ab7dd292dc9bbe1c9e502ea5826220734f091a6ae9d759c0ec1e836c10ce85858925071d94f01
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![](shoryuken.jpg)
4
4
 
5
- Shoryuken _sho-ryu-ken_ is a super efficient [AWS SQS](https://aws.amazon.com/sqs/) thread based message processor.
5
+ Shoryuken _sho-ryu-ken_ is a super-efficient [AWS SQS](https://aws.amazon.com/sqs/) thread-based message processor.
6
6
 
7
7
  [![Build Status](https://travis-ci.org/phstc/shoryuken.svg)](https://travis-ci.org/phstc/shoryuken)
8
8
 
@@ -41,11 +41,15 @@ To be even more performance and cost efficient, Shoryuken fetches SQS messages i
41
41
 
42
42
  Add this line to your application's Gemfile:
43
43
 
44
- gem 'shoryuken'
44
+ ```ruby
45
+ gem 'shoryuken'
46
+ ```
45
47
 
46
48
  Or to get the latest updates:
47
49
 
48
- gem 'shoryuken', github: 'phstc/shoryuken', branch: 'master'
50
+ ```ruby
51
+ gem 'shoryuken', github: 'phstc/shoryuken', branch: 'master'
52
+ ```
49
53
 
50
54
  And then execute:
51
55
 
@@ -64,7 +68,7 @@ class MyWorker
64
68
  include Shoryuken::Worker
65
69
 
66
70
  shoryuken_options queue: 'default', auto_delete: true
67
- # shoryuken_options queue: ->{ "#{ENV['environment']_default" }
71
+ # shoryuken_options queue: ->{ "#{ENV['environment']}_default" }
68
72
 
69
73
  # shoryuken_options body_parser: :json
70
74
  # shoryuken_options body_parser: ->(sqs_msg){ REXML::Document.new(sqs_msg.body) }
@@ -82,7 +86,7 @@ end
82
86
 
83
87
  [Check the Sending a message documentation](https://github.com/phstc/shoryuken/wiki/Sending-a-message)
84
88
 
85
- ### Midleware
89
+ ### Middleware
86
90
 
87
91
  ```ruby
88
92
  class MyMiddleware
@@ -124,8 +128,52 @@ You can tell Shoryuken to load your Rails application by passing the `-R` or `--
124
128
 
125
129
  If you load Rails, and assuming your workers are located in the `app/workers` directory, they will be auto-loaded. This means you don't need to require them explicitly with `-r`.
126
130
 
131
+ For middleware and other configuration, you might want to create an initializer:
132
+
133
+ ```ruby
134
+ Shoryuken.configure_server do |config|
135
+ # Replace Rails logger so messages are logged wherever Shoryuken is logging
136
+ # Note: this entire block is only run by the processor, so we don't overwrite
137
+ # the logger when the app is running as usual.
138
+ Rails.logger = Shoryuken::Logging.logger
139
+
140
+ config.server_middleware do |chain|
141
+ chain.add Shoryuken::MyMiddleware
142
+ end
143
+ end
144
+ ```
145
+
146
+ *Note:* In the above case, since we are replacing the Rails logger, it's desired that this initializer runs before other initializers (in case they themselves use the logger). Since by Rails conventions initializers are executed in alphabetical order, this can be achieved by prepending the initializer filename with `00_` (assuming no other initializers alphabetically precede this one).
147
+
127
148
  This feature works for Rails 4+, but needs to be confirmed for older versions.
128
149
 
150
+ #### ActiveJob Support
151
+
152
+ Yes, Shoryuken supports ActiveJob! This means that you can put your jobs in processor-agnostic `ActiveJob::Base` subclasses, and change processors whenever you want (or better yet, switch to Shoryuken from another processor easily!).
153
+
154
+ It works as expected. Just put your job in `app/jobs`. Here's an example:
155
+
156
+ ```ruby
157
+ class ProcessPhotoJob < ActiveJob::Base
158
+ queue_as :default
159
+
160
+ rescue_from ActiveJob::DeserializationError do |e|
161
+ Shoryuken.logger.error ex
162
+ Shoryuken.logger.error ex.backtrace.join("\n")
163
+ end
164
+
165
+ def perform(photo)
166
+ photo.process_image!
167
+ end
168
+ end
169
+ ```
170
+
171
+ Delayed mailers, ActiveRecord serialization, etc. all work.
172
+
173
+ See [ActiveJob docs](http://edgeguides.rubyonrails.org/active_job_basics.html) for more info.
174
+
175
+ *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.
176
+
129
177
  ### Start Shoryuken
130
178
 
131
179
  ```shell
data/lib/shoryuken.rb CHANGED
@@ -7,7 +7,8 @@ require 'shoryuken/core_ext'
7
7
  require 'shoryuken/util'
8
8
  require 'shoryuken/client'
9
9
  require 'shoryuken/worker'
10
- require 'shoryuken/worker_loader'
10
+ require 'shoryuken/worker_registry'
11
+ require 'shoryuken/default_worker_registry'
11
12
  require 'shoryuken/logging'
12
13
  require 'shoryuken/middleware/chain'
13
14
  require 'shoryuken/middleware/server/auto_delete'
@@ -22,31 +23,14 @@ module Shoryuken
22
23
  timeout: 8
23
24
  }
24
25
 
25
- @@workers = {}
26
- @@queues = []
27
- @@worker_loader = WorkerLoader
26
+ @@queues = []
27
+ @@worker_registry = DefaultWorkerRegistry.new
28
28
 
29
29
  class << self
30
30
  def options
31
31
  @options ||= DEFAULTS.dup
32
32
  end
33
33
 
34
- def register_worker(queue, clazz)
35
- if worker_class = @@workers[queue]
36
- if worker_class.get_shoryuken_options['batch'] == true || clazz.get_shoryuken_options['batch'] == true
37
- raise ArgumentError, "Could not register #{clazz} for '#{queue}', "\
38
- "because #{worker_class} is already registered for this queue, "\
39
- "and Shoryuken doesn't support a batchable worker for a queue with multiple workers"
40
- end
41
- end
42
-
43
- @@workers[queue] = clazz
44
- end
45
-
46
- def workers
47
- @@workers
48
- end
49
-
50
34
  def queues
51
35
  @@queues
52
36
  end
@@ -55,12 +39,16 @@ module Shoryuken
55
39
  Shoryuken::Logging.logger
56
40
  end
57
41
 
58
- def worker_loader=(worker_loader)
59
- @@worker_loader = worker_loader
42
+ def register_worker(*args)
43
+ worker_registry.register_worker(*args)
60
44
  end
61
45
 
62
- def worker_loader
63
- @@worker_loader
46
+ def worker_registry=(worker_registry)
47
+ @@worker_registry = worker_registry
48
+ end
49
+
50
+ def worker_registry
51
+ @@worker_registry
64
52
  end
65
53
 
66
54
  # Shoryuken.configure_server do |config|
@@ -78,6 +66,18 @@ module Shoryuken
78
66
  @server_chain
79
67
  end
80
68
 
69
+ def default_worker_options
70
+ @@default_worker_options ||= {
71
+ 'queue' => 'default',
72
+ 'delete' => false,
73
+ 'auto_delete' => false,
74
+ 'auto_visibility_timeout' => false,
75
+ 'batch' => false }
76
+ end
77
+
78
+ def default_worker_options=(options)
79
+ @@default_worker_options = options
80
+ end
81
81
 
82
82
  private
83
83
 
@@ -89,8 +89,9 @@ module Shoryuken
89
89
  require 'shoryuken/middleware/server/active_record'
90
90
  m.add Middleware::Server::ActiveRecord
91
91
  end
92
- # TODO m.add Middleware::Server::RetryJobs
93
92
  end
94
93
  end
95
94
  end
96
95
  end
96
+
97
+ require 'shoryuken/extensions/active_job_adapter' if defined?(::ActiveJob)
data/lib/shoryuken/cli.rb CHANGED
@@ -7,6 +7,9 @@ require 'erb'
7
7
  require 'shoryuken'
8
8
 
9
9
  module Shoryuken
10
+ # See: https://github.com/mperham/sidekiq/blob/33f5d6b2b6c0dfaab11e5d39688cab7ebadc83ae/lib/sidekiq/cli.rb#L20
11
+ class Shutdown < Interrupt; end
12
+
10
13
  class CLI
11
14
  include Util
12
15
  include Singleton
@@ -24,9 +27,9 @@ module Shoryuken
24
27
 
25
28
  setup_options(args) do |cli_options|
26
29
  # this needs to happen before configuration is parsed, since it may depend on Rails env
30
+ initialize_logger(cli_options)
27
31
  load_rails if cli_options[:rails]
28
32
  end
29
- initialize_logger
30
33
  require_workers
31
34
  validate!
32
35
  patch_deprecated_workers!
@@ -77,6 +80,7 @@ module Shoryuken
77
80
  ::Rails::Application.initializer "shoryuken.eager_load" do
78
81
  ::Rails.application.config.eager_load = true
79
82
  end
83
+ require 'shoryuken/extensions/active_job_adapter' if defined?(::ActiveJob)
80
84
  require File.expand_path("config/environment.rb")
81
85
  end
82
86
 
@@ -233,22 +237,21 @@ module Shoryuken
233
237
  end
234
238
  end
235
239
 
236
- def initialize_logger
237
- Shoryuken::Logging.initialize_logger(Shoryuken.options[:logfile]) if Shoryuken.options[:logfile]
240
+ def initialize_logger(options = Shoryuken.options)
241
+ Shoryuken::Logging.initialize_logger(options[:logfile]) if options[:logfile]
238
242
 
239
- Shoryuken.logger.level = Logger::DEBUG if Shoryuken.options[:verbose]
243
+ Shoryuken.logger.level = Logger::DEBUG if options[:verbose]
240
244
  end
241
245
 
242
246
  def validate!
243
247
  raise ArgumentError, 'No queues supplied' if Shoryuken.queues.empty?
244
248
 
245
- Shoryuken.queues.each do |queue|
246
- logger.warn "No worker supplied for '#{queue}'" unless Shoryuken.workers.include? queue
247
- end
249
+ all_queues = Shoryuken.queues
250
+ queues_with_workers = Shoryuken.worker_registry.queues
248
251
 
249
- if Shoryuken.options[:aws][:access_key_id].nil? && Shoryuken.options[:aws][:secret_access_key].nil?
250
- if ENV['AWS_ACCESS_KEY_ID'].nil? && ENV['AWS_SECRET_ACCESS_KEY'].nil?
251
- raise ArgumentError, 'No AWS credentials supplied'
252
+ unless defined?(::ActiveJob)
253
+ (all_queues - queues_with_workers).each do |queue|
254
+ logger.warn "No worker supplied for '#{queue}'"
252
255
  end
253
256
  end
254
257
 
@@ -285,15 +288,17 @@ module Shoryuken
285
288
  end
286
289
 
287
290
  def patch_deprecated_workers!
288
- Shoryuken.workers.each do |queue, worker_class|
289
- if worker_class.instance_method(:perform).arity == 1
290
- logger.warn "[DEPRECATION] #{worker_class.name}#perform(sqs_msg) is deprecated. Please use #{worker_class.name}#perform(sqs_msg, body)"
291
+ Shoryuken.worker_registry.queues.each do |queue|
292
+ Shoryuken.worker_registry.workers(queue).each do |worker_class|
293
+ if worker_class.instance_method(:perform).arity == 1
294
+ logger.warn "[DEPRECATION] #{worker_class.name}#perform(sqs_msg) is deprecated. Please use #{worker_class.name}#perform(sqs_msg, body)"
291
295
 
292
- worker_class.class_eval do
293
- alias_method :deprecated_perform, :perform
296
+ worker_class.class_eval do
297
+ alias_method :deprecated_perform, :perform
294
298
 
295
- def perform(sqs_msg, body = nil)
296
- deprecated_perform(sqs_msg)
299
+ def perform(sqs_msg, body = nil)
300
+ deprecated_perform(sqs_msg)
301
+ end
297
302
  end
298
303
  end
299
304
  end
@@ -0,0 +1,46 @@
1
+ module Shoryuken
2
+ class DefaultWorkerRegistry < WorkerRegistry
3
+ def initialize
4
+ @workers = {}
5
+ end
6
+
7
+ def batch_receive_messages?(queue)
8
+ !!(@workers[queue] && @workers[queue].get_shoryuken_options['batch'])
9
+ end
10
+
11
+ def clear
12
+ @workers.clear
13
+ end
14
+
15
+ def fetch_worker(queue, message)
16
+ worker_class = !message.is_a?(Array) &&
17
+ message.message_attributes &&
18
+ message.message_attributes['shoryuken_class'] &&
19
+ message.message_attributes['shoryuken_class'][:string_value]
20
+
21
+ worker_class = (worker_class.constantize rescue nil) || @workers[queue]
22
+
23
+ worker_class.new
24
+ end
25
+
26
+ def queues
27
+ @workers.keys
28
+ end
29
+
30
+ def register_worker(queue, clazz)
31
+ if worker_class = @workers[queue]
32
+ if worker_class.get_shoryuken_options['batch'] == true || clazz.get_shoryuken_options['batch'] == true
33
+ raise ArgumentError, "Could not register #{clazz} for '#{queue}', "\
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."
36
+ end
37
+ end
38
+
39
+ @workers[queue] = clazz
40
+ end
41
+
42
+ def workers(queue)
43
+ [@workers.fetch(queue, [])].flatten
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,63 @@
1
+ # ActiveJob docs: http://edgeguides.rubyonrails.org/active_job_basics.html
2
+ # Example adapters ref: https://github.com/rails/rails/tree/master/activejob/lib/active_job/queue_adapters
3
+
4
+ require 'shoryuken'
5
+
6
+ module ActiveJob
7
+ module QueueAdapters
8
+ # == Shoryuken adapter for Active Job
9
+ #
10
+ # Shoryuken ("sho-ryu-ken") is a super-efficient AWS SQS thread based message processor.
11
+ #
12
+ # Read more about Shoryuken {here}[https://github.com/phstc/shoryuken].
13
+ #
14
+ # To use Shoryuken set the queue_adapter config to +:shoryuken+.
15
+ #
16
+ # Rails.application.config.active_job.queue_adapter = :shoryuken
17
+ class ShoryukenAdapter
18
+ class << self
19
+ def enqueue(job) #:nodoc:
20
+ register_worker!(job)
21
+
22
+ Shoryuken::Client.send_message(job.queue_name, job.serialize, message_attributes: message_attributes)
23
+ end
24
+
25
+ def enqueue_at(job, timestamp) #:nodoc:
26
+ register_worker!(job)
27
+
28
+ delay = (timestamp - Time.current.to_f).round
29
+ raise 'The maximum allowed delay is 15 minutes' if delay > 15.minutes
30
+
31
+ Shoryuken::Client.send_message(job.queue_name, job.serialize, delay_seconds: delay,
32
+ message_attributes: message_attributes)
33
+ end
34
+
35
+
36
+ private
37
+
38
+ def register_worker!(job)
39
+ Shoryuken.register_worker(job.queue_name, JobWrapper)
40
+ end
41
+
42
+ def message_attributes
43
+ @message_attributes ||= {
44
+ 'shoryuken_class' => {
45
+ string_value: JobWrapper.to_s,
46
+ data_type: 'String'
47
+ }
48
+ }
49
+ end
50
+ end
51
+
52
+ class JobWrapper #:nodoc:
53
+ include Shoryuken::Worker
54
+
55
+ shoryuken_options body_parser: :json, auto_delete: true
56
+
57
+ def perform(sqs_msg, hash)
58
+ Base.execute hash
59
+ end
60
+ end
61
+ end
62
+ end
63
+ 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
16
+ options = Shoryuken.options[:aws][:receive_message].to_h.dup
17
17
  options[:limit] = limit
18
18
  options[:message_attribute_names] ||= []
19
19
  options[:message_attribute_names] << 'shoryuken_class'
@@ -28,8 +28,7 @@ module Shoryuken
28
28
  logger.debug "Looking for new messages in '#{queue}'"
29
29
 
30
30
  begin
31
- batch = !!(Shoryuken.workers[queue] && Shoryuken.workers[queue].get_shoryuken_options['batch'])
32
-
31
+ batch = Shoryuken.worker_registry.batch_receive_messages?(queue)
33
32
  limit = batch ? FETCH_LIMIT : available_processors
34
33
 
35
34
  if (sqs_msgs = Array(receive_message(queue, limit))).any?
@@ -8,7 +8,8 @@ module Shoryuken
8
8
  attr_accessor :manager
9
9
 
10
10
  def initialize
11
- @manager = Shoryuken::Manager.new_link
11
+ @condvar = Celluloid::Condition.new
12
+ @manager = Shoryuken::Manager.new_link(@condvar)
12
13
  @fetcher = Shoryuken::Fetcher.new_link(manager)
13
14
 
14
15
  @done = false
@@ -22,7 +23,8 @@ module Shoryuken
22
23
  @fetcher.terminate if @fetcher.alive?
23
24
 
24
25
  manager.async.stop(shutdown: !!options[:shutdown], timeout: Shoryuken.options[:timeout])
25
- manager.wait(:shutdown)
26
+ @condvar.wait
27
+ manager.terminate
26
28
  end
27
29
  end
28
30
 
@@ -10,14 +10,16 @@ module Shoryuken
10
10
 
11
11
  trap_exit :processor_died
12
12
 
13
- def initialize
13
+ def initialize(condvar)
14
14
  @count = Shoryuken.options[:concurrency] || 25
15
15
  @queues = Shoryuken.queues.dup.uniq
16
+ @finished = condvar
16
17
 
17
18
  @done = false
18
19
 
19
20
  @busy = []
20
- @ready = @count.times.map { Processor.new_link(current_actor) }
21
+ @ready = @count.times.map { build_processor }
22
+ @threads = {}
21
23
  end
22
24
 
23
25
  def start
@@ -39,7 +41,7 @@ module Shoryuken
39
41
  end
40
42
  @ready.clear
41
43
 
42
- return after(0) { signal(:shutdown) } if @busy.empty?
44
+ return after(0) { @finished.signal } if @busy.empty?
43
45
 
44
46
  if options[:shutdown]
45
47
  hard_shutdown_in(options[:timeout])
@@ -53,6 +55,7 @@ module Shoryuken
53
55
  watchdog('Manager#processor_done died') do
54
56
  logger.info "Process done for '#{queue}'"
55
57
 
58
+ @threads.delete(processor.object_id)
56
59
  @busy.delete processor
57
60
 
58
61
  if stopped?
@@ -65,12 +68,13 @@ module Shoryuken
65
68
 
66
69
  def processor_died(processor, reason)
67
70
  watchdog("Manager#processor_died died") do
68
- logger.info "Process died, reason: #{reason}"
71
+ logger.error "Process died, reason: #{reason}" unless reason.to_s.empty?
69
72
 
73
+ @threads.delete(processor.object_id)
70
74
  @busy.delete processor
71
75
 
72
76
  unless stopped?
73
- @ready << Processor.new_link(current_actor)
77
+ @ready << build_processor
74
78
  end
75
79
  end
76
80
  end
@@ -133,8 +137,18 @@ module Shoryuken
133
137
  end
134
138
  end
135
139
 
140
+ def real_thread(proxy_id, thr)
141
+ @threads[proxy_id] = thr
142
+ end
143
+
136
144
  private
137
145
 
146
+ def build_processor
147
+ processor = Processor.new_link(current_actor)
148
+ processor.proxy_id = processor.object_id
149
+ processor
150
+ end
151
+
138
152
  def restart_queue!(queue)
139
153
  return if stopped?
140
154
 
@@ -148,7 +162,6 @@ module Shoryuken
148
162
 
149
163
  @fetcher_paused = false
150
164
 
151
-
152
165
  dispatch
153
166
  end
154
167
  end
@@ -172,11 +185,10 @@ module Shoryuken
172
185
  # get/remove the first queue in the list
173
186
  queue = @queues.shift
174
187
 
175
-
176
- unless Shoryuken.workers.include? queue
188
+ unless defined?(::ActiveJob) || !Shoryuken.worker_registry.workers(queue).empty?
177
189
  # when no worker registered pause the queue to avoid endless recursion
178
190
 
179
- logger.debug "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because of no workers registered"
191
+ logger.debug "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because no workers registered"
180
192
 
181
193
  after(Shoryuken.options[:delay].to_f) { async.restart_queue!(queue) }
182
194
 
@@ -195,7 +207,7 @@ module Shoryuken
195
207
  if @busy.size > 0
196
208
  after(delay) { soft_shutdown(delay) }
197
209
  else
198
- after(0) { signal(:shutdown) }
210
+ @finished.signal
199
211
  end
200
212
  end
201
213
 
@@ -209,12 +221,13 @@ module Shoryuken
209
221
  logger.info { "Hard shutting down #{@busy.size} busy workers" }
210
222
 
211
223
  @busy.each do |processor|
212
- t = processor.bare_object.actual_work_thread
213
- t.raise Shutdown if processor.alive?
224
+ if processor.alive? && t = @threads.delete(processor.object_id)
225
+ t.raise Shutdown
226
+ end
214
227
  end
215
228
  end
216
229
 
217
- after(0) { signal(:shutdown) }
230
+ @finished.signal
218
231
  end
219
232
  end
220
233
  end