advanced-sneakers-activejob 0.2.1 → 0.3.2

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
  SHA256:
3
- metadata.gz: e90d1ae3dd94326c1cc1dca5a14e8f89ec9925a580d8691f407da8fe311a18c4
4
- data.tar.gz: 515d2bc7bb33961a44883d66d03b6ae9c6de7a225f61c5a25cfdff37efbf41ee
3
+ metadata.gz: b1edfa8193fc63efdc88c97b46bf55dddfe0bf062c81915cc32a0935c4621ac7
4
+ data.tar.gz: 474ee8337a89e4546774190c22c77429687b02b81232a033a32a67213753b2e8
5
5
  SHA512:
6
- metadata.gz: 28fbc6a780beafe264116a2dd8fe9de7ea3153b56941dc63ef0f35e6165507bc59444281e2cac34f0265455f39daea206fcb73f674a9b8d25f2e0a2bf18193db
7
- data.tar.gz: 91ed4fb21ad128f36e05d29becab8d0edadc58cdbdc9280d8186ad7d45670521f39138af3cc39fa3fbff39c97a6e2340cbad8a9b92b431bec51f800182ff1241
6
+ metadata.gz: a955f43e34ea3128052261fcd03c88d5e20563ab5df37b18bdb5f1ffecdf70c13d659aa70acb382e7d7f4f7e76fd9f1f7a2b1f323e1aadeb107229164258a57c
7
+ data.tar.gz: 88bed92d02624c64834f87ae62baaa9e43edde9556bf6082c96f0d4021e35da542132a773ccf9c7ce9102a800f63e2ce510e5a1384fcef6e126fbb79a9d1f811
@@ -3,17 +3,17 @@ dist: xenial
3
3
  language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
- - 2.4.9
7
- - 2.5.7
8
- - 2.6.5
6
+ - 2.5.8
7
+ - 2.6.6
8
+ - 2.7.1
9
9
  env:
10
10
  - RAILS_VERSION="~> 4.2.11"
11
11
  - RAILS_VERSION="~> 5.2.4"
12
12
  - RAILS_VERSION="~> 6.0.2"
13
13
  jobs:
14
14
  exclude:
15
- - rvm: 2.4.9
16
- env: RAILS_VERSION="~> 6.0.2"
15
+ - rvm: 2.7.1
16
+ env: RAILS_VERSION="~> 4.2.11"
17
17
 
18
18
  before_install: gem install bundler -v 1.17.3
19
19
  before_script:
@@ -1,12 +1,47 @@
1
+ ## Changes Between 0.3.1 and 0.3.2
2
+
3
+ ### [Add ability to run specified ActiveJob queues consumers](https://github.com/veeqo/advanced-sneakers-activejob/pull/8)
4
+
5
+ Sneakers allows to specify consumer classes to run:
6
+
7
+ ```sh
8
+ WORKERS=MyConsumer rake sneakers:run
9
+ ```
10
+
11
+ Now it works for ActiveJob queues consumers as well:
12
+
13
+ ```sh
14
+ WORKERS=AdvancedSneakersActiveJob::MailersConsumer rake sneakers:run
15
+ ```
16
+
17
+ ## Changes Between 0.3.0 and 0.3.1
18
+
19
+ ### [Restore Sneakers::Worker::Classes methods](https://github.com/veeqo/advanced-sneakers-activejob/pull/6)
20
+
21
+ ## Changes Between 0.2.3 and 0.3.0
22
+
23
+ This release does not change the observed behavior, but replaces the publisher with completely new implementation.
24
+
25
+ ### Reusable parts of publisher are extracted to [bunny-publisher](https://github.com/veeqo/bunny-publisher)
26
+
27
+ ## Changes Between 0.2.2 and 0.2.3
28
+
29
+ ### [Refactored support for ActiveJob prefix](https://github.com/veeqo/advanced-sneakers-activejob/pull/3)
30
+ ### [Support for custom adapter per job](https://github.com/veeqo/advanced-sneakers-activejob/pull/4)
31
+
32
+ ## Changes Between 0.2.1 and 0.2.2
33
+
34
+ Cleanup of `puts` and logger mistakenly introduced in previous version
35
+
1
36
  ## Changes Between 0.2.0 and 0.2.1
2
37
 
3
- ### Support for ActiveJob prefix https://github.com/veeqo/advanced-sneakers-activejob/pull/2
38
+ ### [Support for ActiveJob prefix](https://github.com/veeqo/advanced-sneakers-activejob/pull/2)
4
39
 
5
40
  Fixed worker class name in rake task description
6
41
 
7
42
  ## Changes Between 0.1.0 and 0.2.0
8
43
 
9
- ### `message_options` https://github.com/veeqo/advanced-sneakers-activejob/pull/1
44
+ ### [`message_options`](https://github.com/veeqo/advanced-sneakers-activejob/pull/1)
10
45
 
11
46
  Customizable options for message publishing (`routing_key`, `headers`, etc)
12
47
 
data/README.md CHANGED
@@ -49,7 +49,7 @@ If message is published before routing has been configured (e.g. by consumer), i
49
49
 
50
50
  There is a setting `handle_unrouted_messages` in [configuration](#configuration) to disable this behavior. If it is disabled, publisher will only log unrouted messages.
51
51
 
52
- Take into accout that **this process is asynchronous**. It means that in case of network failures or process exit unrouted messages could be lost. The adapter tries to postpone application exit up to 30 seconds in case if there are unrouted messages, but it does not provide any guarantees.
52
+ Take into accout that **this process is asynchronous**. It means that in case of network failures or process exit unrouted messages could be lost. The adapter tries to postpone application exit up to 5 seconds in case if there are unrouted messages, but it does not provide any guarantees.
53
53
 
54
54
  **Delayed messages are not handled!** If job is delayed `GuestsCleanupJob.set(wait: 1.week).perform_later(guest)` and there is no proper routing defined at the moment of job execution, it would be lost.
55
55
 
@@ -152,12 +152,19 @@ AdvancedSneakersActiveJob.configure do |config|
152
152
 
153
153
  # Custom sneakers configuration for ActiveJob publisher & runner
154
154
  config.sneakers = {
155
+ connection: Bunny.new('CUSTOM_URL', with: { other: 'options' }),
155
156
  exchange: 'activejob',
156
157
  handler: AdvancedSneakersActiveJob::Handler
157
158
  }
158
159
 
159
160
  # Define custom delay for retries, but remember - each unique delay leads to new queue on RabbitMQ side
160
161
  config.retry_delay_proc = ->(count) { AdvancedSneakersActiveJob::EXPONENTIAL_BACKOFF[count] }
162
+
163
+ # Connection for publisher (fallbacks to connection of consumers)
164
+ config.publish_connection = Bunny.new('CUSTOM_URL', with: { other: 'options' })
165
+
166
+ # Unrouted messages republish requires extra connection and will try to "clone" publish_connection unless it is provided
167
+ config.republish_connection = Bunny.new('CUSTOM_URL', with: { other: 'options' })
161
168
  end
162
169
  ```
163
170
 
@@ -168,3 +175,5 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/veeqo/
168
175
  ## License
169
176
 
170
177
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
178
+
179
+ ## Sponsored by [Veeqo](https://veeqo.com/)
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = ['lib']
28
28
 
29
29
  spec.add_dependency 'activejob', '>= 4.2'
30
+ spec.add_dependency 'bunny-publisher', '~> 0.1'
30
31
  spec.add_dependency 'sneakers', '~> 2.7'
31
32
 
32
33
  spec.add_development_dependency 'bundler'
@@ -26,7 +26,10 @@ module ActiveJob
26
26
  delay = AdvancedSneakersActiveJob.config.delay_proc.call(timestamp).to_i
27
27
 
28
28
  if delay.positive?
29
- AdvancedSneakersActiveJob.publisher.publish_delayed(*publish_params(job).tap { |params| params.last[:delay] = delay })
29
+ message, options = publish_params(job)
30
+ options[:headers] = { 'delay' => delay.to_i } # do not use x- prefix because headers exchanges ignore such headers
31
+
32
+ AdvancedSneakersActiveJob.delayed_publisher.publish(message, options)
30
33
  else
31
34
  enqueue(job)
32
35
  end
@@ -38,7 +41,7 @@ module ActiveJob
38
41
  @monitor.synchronize do
39
42
  [
40
43
  Sneakers::ContentType.serialize(job.serialize, AdvancedSneakersActiveJob::CONTENT_TYPE),
41
- build_publish_params(job)
44
+ build_publish_params(job).merge(content_type: AdvancedSneakersActiveJob::CONTENT_TYPE)
42
45
  ]
43
46
  end
44
47
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'active_support'
4
4
  require 'active_support/core_ext'
5
+ require 'bunny_publisher'
5
6
 
6
7
  require 'sneakers'
7
8
  require 'advanced_sneakers_activejob/workers_registry'
@@ -15,6 +16,7 @@ require 'advanced_sneakers_activejob/handler'
15
16
  require 'advanced_sneakers_activejob/configuration'
16
17
  require 'advanced_sneakers_activejob/errors'
17
18
  require 'advanced_sneakers_activejob/publisher'
19
+ require 'advanced_sneakers_activejob/delayed_publisher'
18
20
  require 'advanced_sneakers_activejob/active_job_patch'
19
21
  require 'advanced_sneakers_activejob/railtie' if defined?(::Rails::Railtie)
20
22
  require 'active_job/queue_adapters/advanced_sneakers_adapter'
@@ -31,18 +33,46 @@ module AdvancedSneakersActiveJob
31
33
  end
32
34
 
33
35
  def define_consumer(queue_name:)
34
- @consumers ||= {}
35
-
36
- @consumers[queue_name] ||= begin
37
- klass = Class.new(ActiveJob::QueueAdapters::AdvancedSneakersAdapter::JobWrapper)
38
- klass.include Sneakers::Worker
39
- const_set([queue_name, 'queue_consumer'].join('_').classify, klass)
40
- klass.from_queue(queue_name, AdvancedSneakersActiveJob.config.sneakers)
41
- end
36
+ name = consumer_name(queue_name: queue_name)
37
+
38
+ return const_get(name) if const_defined?(name)
39
+
40
+ klass = Class.new(ActiveJob::QueueAdapters::AdvancedSneakersAdapter::JobWrapper)
41
+ const_set(name, klass)
42
+ klass.include Sneakers::Worker
43
+ klass.from_queue(queue_name, config.sneakers)
44
+
45
+ klass
42
46
  end
43
47
 
44
48
  def publisher
45
- @publisher ||= AdvancedSneakersActiveJob::Publisher.new
49
+ @publisher ||= AdvancedSneakersActiveJob::Publisher.new(config.publisher_config)
50
+ end
51
+
52
+ def delayed_publisher
53
+ @delayed_publisher ||= AdvancedSneakersActiveJob::DelayedPublisher.new(config.publisher_config)
54
+ end
55
+
56
+ # Based on ActiveSupport::Inflector#parameterize
57
+ def consumer_name(queue_name:)
58
+ # replace accented chars with their ascii equivalents
59
+ parameterized_string = ::ActiveSupport::Inflector.transliterate(queue_name)
60
+ # Turn unwanted chars into the separator
61
+ parameterized_string.gsub!(/[^a-z0-9\-_]+/, '_')
62
+ # No more than one of the separator in a row.
63
+ parameterized_string.gsub!(/_{2,}/, '_')
64
+ # Remove leading/trailing separator.
65
+ parameterized_string.gsub!(/^_|_$/, '')
66
+ # Ruby does not allow classes with leading digits
67
+ parameterized_string.gsub!(/\A(\d)/, 'queue\1')
68
+
69
+ [parameterized_string, 'consumer'].join('_').classify
70
+ end
71
+
72
+ def const_missing(name)
73
+ Sneakers::Worker::Classes.define_active_job_consumers
74
+
75
+ constants.include?(name) ? const_get(name) : super
46
76
  end
47
77
  end
48
78
  end
@@ -19,31 +19,11 @@ module AdvancedSneakersActiveJob
19
19
  end
20
20
  end
21
21
 
22
- def queue_as(*args)
23
- super(*args)
24
- define_consumer
25
- end
26
-
27
22
  def message_options(options)
28
23
  raise ArgumentError, 'message_options accepts Hash argument only' unless options.is_a?(Hash)
29
24
 
30
25
  self.publish_options = options.symbolize_keys
31
26
  end
32
-
33
- private
34
-
35
- def define_consumer
36
- Rails.logger.warn queue_name_without_prefix
37
- AdvancedSneakersActiveJob.define_consumer(queue_name: queue_name_without_prefix)
38
- end
39
-
40
- def queue_name_without_prefix
41
- name = queue_name.respond_to?(:call) ? queue_name.call : queue_name
42
-
43
- return name if queue_name_prefix.blank?
44
-
45
- name.to_s.sub([queue_name_prefix, queue_name_delimiter].join, '')
46
- end
47
27
  end
48
28
  end
49
29
  end
@@ -17,6 +17,9 @@ module AdvancedSneakersActiveJob
17
17
  config_accessor(:delayed_queue_prefix) { 'delayed' }
18
18
  config_accessor(:retry_delay_proc) { ->(count) { AdvancedSneakersActiveJob::EXPONENTIAL_BACKOFF[count] } } # seconds
19
19
 
20
+ config_accessor(:publish_connection)
21
+ config_accessor(:republish_connection)
22
+
20
23
  def sneakers
21
24
  custom_config = DEFAULT_SNEAKERS_CONFIG.deep_merge(config.sneakers || {})
22
25
 
@@ -30,5 +33,9 @@ module AdvancedSneakersActiveJob
30
33
  def sneakers=(custom)
31
34
  config.sneakers = custom
32
35
  end
36
+
37
+ def publisher_config
38
+ sneakers.merge(publish_connection: publish_connection, republish_connection: republish_connection)
39
+ end
33
40
  end
34
41
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AdvancedSneakersActiveJob
4
+ # This publisher relies on TTL to keep messages in a queue.
5
+ # When TTL is reached, messages go to another exchange (see dlx_exchange_name param).
6
+ class DelayedPublisher < ::BunnyPublisher::Base
7
+ include ::BunnyPublisher::Mandatory
8
+
9
+ delegate :logger, to: :'::ActiveJob::Base'
10
+
11
+ delegate :name_prefix, :delayed_queue_prefix,
12
+ to: :'AdvancedSneakersActiveJob.config',
13
+ prefix: :config
14
+
15
+ before_publish :log_message
16
+
17
+ attr_reader :dlx_exchange_name
18
+
19
+ def initialize(exchange:, **options)
20
+ super(options.merge(exchange: [exchange, 'delayed'].join('-'), exchange_options: { type: 'headers', durable: true }))
21
+
22
+ @dlx_exchange_name = exchange
23
+ end
24
+
25
+ private
26
+
27
+ def log_message(publisher, message, options = {})
28
+ logger.debug do
29
+ delay = options.dig(:headers, 'delay')
30
+
31
+ "Publishing <#{message}> to [#{publisher.exchange.name}] with routing_key [#{options[:routing_key]}] and delay [#{delay}]"
32
+ end
33
+ end
34
+
35
+ def declare_republish_queue(_return_info, properties, _message)
36
+ delay = properties.headers.fetch('delay')
37
+
38
+ queue_name = delayed_queue_name(delay: delay)
39
+
40
+ queue_arguments = {
41
+ 'x-queue-mode' => 'lazy', # tell RabbitMQ not to use RAM for this queue as it won't be consumed
42
+ 'x-message-ttl' => delay * 1000, # make messages die after requested time
43
+ 'x-dead-letter-exchange' => dlx_exchange_name # dead messages go to original exchange and then routed to proper queues
44
+ }
45
+
46
+ logger.debug { "Creating delayed queue [#{queue_name}]" }
47
+
48
+ republish_channel.queue(queue_name, durable: true, arguments: queue_arguments)
49
+ end
50
+
51
+ def declare_republish_queue_binding(queue, _return_info, properties, _message)
52
+ queue.bind(republish_exchange, arguments: { delay: properties.headers.fetch('delay') })
53
+ end
54
+
55
+ def delayed_queue_name(delay:)
56
+ [
57
+ ::ActiveJob::Base.queue_name_prefix,
58
+ [config_delayed_queue_prefix, delay].join(':')
59
+ ].compact.join(::ActiveJob::Base.queue_name_delimiter)
60
+ end
61
+ end
62
+ end
@@ -5,11 +5,10 @@ module AdvancedSneakersActiveJob
5
5
  class Handler < Sneakers::Handlers::Oneshot
6
6
  def error(delivery_info, properties, message, error)
7
7
  params = properties.to_h
8
- params[:headers] = patch_headers(params[:headers], delivery_info, error)
8
+ params[:headers] = patch_headers(params[:headers] || {}, delivery_info, error)
9
9
  params[:routing_key] = delivery_info.routing_key
10
- params[:delay] = calculate_delay(params[:headers], delivery_info)
11
10
 
12
- AdvancedSneakersActiveJob.publisher.publish_delayed(message, params)
11
+ AdvancedSneakersActiveJob.delayed_publisher.publish(message, params)
13
12
 
14
13
  acknowledge(delivery_info, properties, message)
15
14
  end
@@ -23,6 +22,7 @@ module AdvancedSneakersActiveJob
23
22
 
24
23
  track_error_in_headers(headers, error)
25
24
  track_death_in_headers(headers, queue, exchange, routing_key)
25
+ set_delay_in_headers(headers, delivery_info)
26
26
 
27
27
  headers
28
28
  end
@@ -41,6 +41,10 @@ module AdvancedSneakersActiveJob
41
41
  end
42
42
  end
43
43
 
44
+ def set_delay_in_headers(headers, delivery_info)
45
+ headers['delay'] = calculate_delay(headers, delivery_info)
46
+ end
47
+
44
48
  def build_death_row(queue, exchange, routing_key)
45
49
  {
46
50
  'count' => 1,
@@ -1,210 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AdvancedSneakersActiveJob
4
- # Based on Sneakers::Publisher, but refactored to utilize :mandatory option to handle unrouted messages
5
- # http://rubybunny.info/articles/exchanges.html#publishing_messages_as_mandatory
6
- class Publisher
7
- WAIT_FOR_UNROUTED_MESSAGES_AT_EXIT_TIMEOUT = 30
4
+ class Publisher < ::BunnyPublisher::Base
5
+ include ::BunnyPublisher::Mandatory
8
6
 
9
- delegate :sneakers, :handle_unrouted_messages, :delayed_queue_prefix,
10
- to: :'AdvancedSneakersActiveJob.config', prefix: :config
7
+ before_publish :log_message
11
8
 
12
- delegate :logger, to: :'ActiveJob::Base'
9
+ delegate :logger, to: :'::ActiveJob::Base'
13
10
 
14
- attr_reader :publish_channel, :republish_channel,
15
- :publish_exchange, :republish_exchange,
16
- :publish_delayed_exchange, :republish_delayed_exchange
17
-
18
- def initialize
19
- @mutex = Mutex.new
20
- at_exit { wait_for_unrouted_messages_processing(timeout: WAIT_FOR_UNROUTED_MESSAGES_AT_EXIT_TIMEOUT) }
21
- end
22
-
23
- def publish(message, routing_key: nil, headers: {}, **properties)
24
- ensure_connection!
25
-
26
- logger.debug "Publishing <#{message}> to [#{publish_exchange.name}] with routing_key [#{routing_key}]"
27
-
28
- params = properties.deep_symbolize_keys.merge(
29
- routing_key: routing_key,
30
- mandatory: true,
31
- content_type: AdvancedSneakersActiveJob::CONTENT_TYPE,
32
- headers: headers
33
- )
34
-
35
- publish_exchange.publish(message, params)
36
- end
37
-
38
- def publish_delayed(message, delay:, routing_key: nil, headers: {}, **properties)
39
- ensure_connection!
40
-
41
- logger.debug "Publishing <#{message}> to [#{publish_delayed_exchange.name}] with routing_key [#{routing_key}] and delay [#{delay}]"
42
-
43
- params = properties.deep_symbolize_keys.merge(
44
- routing_key: routing_key,
45
- mandatory: true,
46
- content_type: AdvancedSneakersActiveJob::CONTENT_TYPE,
47
- headers: headers.deep_symbolize_keys.merge(delay: delay.to_i) # do not use x- prefix because headers exchanges ignore such headers
48
- )
49
-
50
- publish_delayed_exchange.publish(message, params)
51
- end
11
+ delegate :handle_unrouted_messages,
12
+ to: :'AdvancedSneakersActiveJob.config',
13
+ prefix: :config
52
14
 
53
15
  private
54
16
 
55
- def ensure_connection!
56
- @mutex.synchronize do
57
- unless connected?
58
- start_connections!
59
- create_channels!
60
- configure_exchanges!
61
- end
17
+ def log_message(publisher, message, options = {})
18
+ logger.debug do
19
+ "Publishing <#{message}> to [#{publisher.exchange.name}] with routing_key [#{options[:routing_key]}]"
62
20
  end
63
21
  end
64
22
 
65
- def start_connections!
66
- @publish_connection ||= create_bunny_connection
67
- @publish_connection.start
68
-
69
- @republish_connection ||= create_bunny_connection
70
- @republish_connection.start
71
- end
72
-
73
- def create_channels!
74
- @publish_channel = @publish_connection.create_channel
75
- @republish_channel = @republish_connection.create_channel
76
- end
77
-
78
- def configure_exchanges!
79
- @publish_exchange = build_exchange(@publish_channel)
80
- @publish_exchange.on_return { |*attrs| handle_unrouted_messages(*attrs) }
81
-
82
- @publish_delayed_exchange = build_delayed_exchange(@publish_channel)
83
- @publish_delayed_exchange.on_return { |*attrs| handle_unrouted_delayed_messages(*attrs) }
84
-
85
- @republish_exchange = build_exchange(republish_channel)
86
- @republish_delayed_exchange = build_delayed_exchange(republish_channel)
87
- end
88
-
89
- def connected?
90
- @publish_connection&.connected? &&
91
- @republish_connection&.connected? &&
92
- @publish_channel &&
93
- @republish_channel
94
- end
95
-
96
- # Returned messages are processed asynchronously and there is a probability for messages loses on program exit or network failure.
97
- # Second connection is required because `on_return` is called within a frameset of amqp connection.
98
- # Any interaction within the connection (even by another channel) can lead to connection error.
99
- # https://github.com/ruby-amqp/bunny/blob/7fb05abf36637557f75a69790be78f9cc1cea807/lib/bunny/session.rb#L683
100
- def handle_unrouted_messages(return_info, properties, message)
101
- @unrouted_message = true
102
-
103
- params = { message: message, return_info: return_info, properties: properties }
104
-
105
- raise(PublishError, params) if return_info.reply_code != 312 # NO_ROUTE
106
-
23
+ def on_message_return(return_info, properties, message)
107
24
  if config_handle_unrouted_messages
108
- setup_routing_and_republish_message(params)
25
+ super
109
26
  else
110
- logger.warn("Message is not routed! #{params}")
111
- end
112
-
113
- @unrouted_message = false
114
- end
115
-
116
- def handle_unrouted_delayed_messages(return_info, properties, message)
117
- @unrouted_delayed_message = true
118
-
119
- params = { message: message, return_info: return_info, properties: properties }
120
-
121
- raise(PublishError, params) if return_info.reply_code != 312 # NO_ROUTE
122
-
123
- setup_routing_and_republish_delayed_message(params)
124
-
125
- @unrouted_delayed_message = false
126
- end
127
-
128
- # TODO: introduce more reliable way to wait for handling of unrouted messages at exit
129
- def wait_for_unrouted_messages_processing(timeout:)
130
- sleep(0.05) # gives publish_exchange some time to receive retuned message
131
-
132
- return unless @unrouted_message || @unrouted_delayed_message
133
-
134
- logger.warn("Waiting up to #{timeout} seconds for unrouted messages handling")
135
-
136
- Timeout.timeout(timeout) { sleep 0.01 while @unrouted_message || @unrouted_delayed_message }
137
- rescue Timeout::Error
138
- logger.warn('Some unrouted messages are lost on process exit!')
139
- end
140
-
141
- def setup_routing_and_republish_message(message:, return_info:, properties:)
142
- logger.debug("Performing queue/binding setup & re-publish for unrouted message. #{{ message: message, return_info: return_info }}")
143
-
144
- routing_key = return_info.routing_key
145
-
146
- create_queue_and_binding(queue_name: deserialize(message).fetch('queue_name'), routing_key: routing_key)
147
-
148
- logger.debug "Re-publishing <#{message}> to [#{republish_exchange.name}] with routing_key [#{routing_key}]"
149
- republish_exchange.publish(message, properties.to_h.merge(routing_key: routing_key))
150
- end
151
-
152
- def create_queue_and_binding(queue_name:, routing_key:)
153
- logger.debug "Creating queue [#{queue_name}] and binding with routing_key [#{routing_key}] to [#{republish_exchange.name}]"
154
- republish_channel.queue(queue_name, config_sneakers[:queue_options]).tap do |queue|
155
- queue.bind(republish_exchange, routing_key: routing_key)
156
- republish_channel.deregister_queue(queue) # we are not going to work with this queue in this channel
157
- end
158
- end
159
-
160
- def setup_routing_and_republish_delayed_message(message:, return_info:, properties:)
161
- delay = properties.headers.fetch('delay').to_i
162
- queue_name = delayed_queue_name(delay: delay)
163
-
164
- logger.debug "Creating delayed queue [#{queue_name}]"
165
-
166
- create_delayed_queue_and_binding(queue_name: queue_name, delay: delay)
167
-
168
- republish_delayed_exchange.publish message, properties.to_h.merge(routing_key: return_info.routing_key)
169
- end
170
-
171
- def delayed_queue_name(delay:)
172
- [
173
- config_delayed_queue_prefix,
174
- delay
175
- ].join(':')
176
- end
177
-
178
- def create_delayed_queue_and_binding(queue_name:, delay:)
179
- queue_arguments = {
180
- 'x-queue-mode' => 'lazy', # tell RabbitMQ not to use RAM for this queue as it won't be consumed
181
- 'x-message-ttl' => delay * 1000, # make messages die after requested time
182
- 'x-dead-letter-exchange' => republish_exchange.name # died messages go to original exchange and then routed to consumers
183
- }
184
-
185
- republish_channel.queue(queue_name, durable: true, arguments: queue_arguments).tap do |queue|
186
- queue.bind(republish_delayed_exchange, arguments: { delay: delay })
187
- republish_channel.deregister_queue(queue) # we are not going to work with this queue in this channel
27
+ logger.warn do
28
+ "Message is not routed! #{{ message: message, return_info: return_info, properties: properties }}"
29
+ end
188
30
  end
189
31
  end
190
-
191
- def build_exchange(channel)
192
- channel.exchange(config_sneakers[:exchange], config_sneakers[:exchange_options])
193
- end
194
-
195
- def build_delayed_exchange(channel)
196
- channel.exchange([config_sneakers[:exchange], 'delayed'].join('-'), type: 'headers', durable: true)
197
- end
198
-
199
- def create_bunny_connection
200
- Bunny.new config_sneakers[:amqp],
201
- vhost: config_sneakers[:vhost],
202
- heartbeat: config_sneakers[:heartbeat],
203
- properties: config_sneakers.fetch(:properties, {})
204
- end
205
-
206
- def deserialize(message)
207
- Sneakers::ContentType.deserialize(message, AdvancedSneakersActiveJob::CONTENT_TYPE)
208
- end
209
32
  end
210
33
  end
@@ -12,7 +12,6 @@ module AdvancedSneakersActiveJob
12
12
  initializer 'advanced_sneakers_activejob.discover_default_job' do
13
13
  ActiveSupport.on_load(:active_job) do
14
14
  ActiveJob::Base.include AdvancedSneakersActiveJob::ActiveJobPatch
15
- ActiveJob::Base.queue_as # Enforce definition of ActiveJob::Base::Consumer (default queue)
16
15
  end
17
16
  end
18
17
 
@@ -5,7 +5,7 @@ require 'sneakers/tasks'
5
5
  task :environment
6
6
 
7
7
  namespace :sneakers do
8
- desc 'Start work for ActiveJob only (set $WORKERS=AdvancedSneakersActiveJob::FooQueueConsumer for processing of :foo queue)'
8
+ desc 'Start work for ActiveJob only (set $WORKERS=AdvancedSneakersActiveJob::FooConsumer for processing of :foo queue)'
9
9
  task :active_job do
10
10
  Rake::Task['environment'].invoke
11
11
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AdvancedSneakersActiveJob
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.2'
5
5
  end
@@ -4,7 +4,7 @@ module AdvancedSneakersActiveJob
4
4
  # Sneakers uses Sneakers::Worker::Classes array to track all workers.
5
5
  # WorkersRegistry mocks original array to track ActiveJob workers separately.
6
6
  class WorkersRegistry
7
- attr_reader :sneakers_workers, :activejob_workers
7
+ attr_reader :sneakers_workers
8
8
 
9
9
  delegate :activejob_workers_strategy, to: :'AdvancedSneakersActiveJob.config'
10
10
 
@@ -17,7 +17,7 @@ module AdvancedSneakersActiveJob
17
17
 
18
18
  def <<(worker)
19
19
  if worker <= ActiveJob::QueueAdapters::AdvancedSneakersAdapter::JobWrapper
20
- activejob_workers << worker
20
+ @activejob_workers << worker
21
21
  else
22
22
  sneakers_workers << worker
23
23
  end
@@ -36,12 +36,49 @@ module AdvancedSneakersActiveJob
36
36
  end
37
37
  end
38
38
 
39
- # For cleaner output on inspecting Sneakers::Worker::Classes in console.
40
- def inspect
39
+ def to_hash
41
40
  {
42
41
  sneakers_workers: sneakers_workers,
43
42
  activejob_workers: activejob_workers
44
43
  }
45
44
  end
45
+
46
+ alias to_h to_hash
47
+
48
+ # For cleaner output on inspecting Sneakers::Worker::Classes in console.
49
+ alias inspect to_hash
50
+
51
+ def activejob_workers
52
+ define_active_job_consumers
53
+
54
+ @activejob_workers
55
+ end
56
+
57
+ def method_missing(method_name, *args, &block)
58
+ if call.respond_to?(method_name)
59
+ call.send(method_name, *args, &block)
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ def respond_to_missing?(method_name, include_private = false)
66
+ call.respond_to?(method_name) || super
67
+ end
68
+
69
+ def define_active_job_consumers
70
+ active_job_classes_with_matching_adapter.each do |worker|
71
+ AdvancedSneakersActiveJob.define_consumer(queue_name: worker.new.queue_name)
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def active_job_classes_with_matching_adapter
78
+ ([ActiveJob::Base] + ActiveJob::Base.descendants).select do |klass|
79
+ klass.queue_adapter == ::ActiveJob::QueueAdapters::AdvancedSneakersAdapter ||
80
+ klass.queue_adapter.is_a?(::ActiveJob::QueueAdapters::AdvancedSneakersAdapter)
81
+ end
82
+ end
46
83
  end
47
84
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: advanced-sneakers-activejob
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Sharshenov
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-04-05 00:00:00.000000000 Z
12
+ date: 2020-06-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activejob
@@ -25,6 +25,20 @@ dependencies:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '4.2'
28
+ - !ruby/object:Gem::Dependency
29
+ name: bunny-publisher
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '0.1'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '0.1'
28
42
  - !ruby/object:Gem::Dependency
29
43
  name: sneakers
30
44
  requirement: !ruby/object:Gem::Requirement
@@ -147,6 +161,7 @@ files:
147
161
  - lib/advanced_sneakers_activejob/active_job_patch.rb
148
162
  - lib/advanced_sneakers_activejob/configuration.rb
149
163
  - lib/advanced_sneakers_activejob/content_type.rb
164
+ - lib/advanced_sneakers_activejob/delayed_publisher.rb
150
165
  - lib/advanced_sneakers_activejob/errors.rb
151
166
  - lib/advanced_sneakers_activejob/exponential_backoff.rb
152
167
  - lib/advanced_sneakers_activejob/handler.rb