rabbit_jobs 0.11.5 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,17 @@
1
- # -*- encoding : utf-8 -*-
2
1
  module RabbitJobs
2
+ # Main process loop.
3
3
  module MainLoop
4
+ def startup
5
+ $stdout.sync = true
6
+
7
+ @shutdown = false
8
+
9
+ Signal.trap('TERM') { shutdown }
10
+ Signal.trap('INT') { shutdown! }
11
+
12
+ true
13
+ end
14
+
4
15
  def shutdown
5
16
  @shutdown = true
6
17
  end
@@ -9,36 +20,26 @@ module RabbitJobs
9
20
  shutdown
10
21
  end
11
22
 
12
- def main_loop(time)
23
+ def main_loop
13
24
  loop do
14
- sleep 1
15
- if time > 0
16
- time -= 1
17
- if time == 0
18
- shutdown
19
- end
20
- end
25
+ sleep 0.5
26
+ next unless @shutdown
21
27
 
22
- if @shutdown
23
- RabbitJobs.logger.info "Stopping."
24
- if defined?(amqp_connection) # in worker only
25
- amqp_connection.stop
26
- amqp_channel.work_pool.join
27
- end
28
- yield if block_given?
29
- return true
28
+ RabbitJobs.logger.info 'Stopping.'
29
+ if defined?(amqp_connection) # in worker only
30
+ amqp_cleanup
30
31
  end
32
+ yield if block_given?
33
+ return true
31
34
  end
32
35
  end
33
36
 
34
37
  def log_daemon_error(error)
35
- if RabbitJobs.logger
36
- begin
37
- RabbitJobs.logger.fatal error
38
- ensure
39
- abort(error.message)
40
- end
41
- end
38
+ return unless RabbitJobs.logger
39
+
40
+ RabbitJobs.logger.fatal error
41
+ ensure
42
+ abort(error.message)
42
43
  end
43
44
  end
44
45
  end
@@ -3,34 +3,36 @@ require 'rabbit_jobs/publisher/test'
3
3
  require 'rabbit_jobs/publisher/sync'
4
4
 
5
5
  module RabbitJobs
6
+ # Interface for publishing messages to amqp queues or testing queues.
6
7
  class Publisher
7
8
  class << self
8
9
  def mode
9
- publisher_type.class_name.underscore
10
+ publisher_instance.class_name.underscore
10
11
  end
11
12
 
13
+ # Allows to switch publisher implementations.
14
+ # You can use RJ.publisher.mode = :test in testing environment.
12
15
  def mode=(value)
13
- @publisher_type = case value.to_s
14
- when 'amqp'
15
- Amqp
16
- when 'test'
17
- Test
18
- when 'sync'
19
- Sync
20
- else
21
- raise ArgumentError.new("value must be :amqp or :test. Passed: #{value.inspect}")
22
- end
16
+ @publisher_instance = case value.to_s
17
+ when 'amqp'
18
+ Amqp
19
+ when 'test'
20
+ Test
21
+ when 'sync'
22
+ Sync
23
+ else
24
+ fail ArgumentError, "value must be :amqp, :sync or :test. Passed: #{value.inspect}"
25
+ end
23
26
  end
24
27
 
25
- %i(cleanup publish_to direct_publish_to purge_queue).each do |api_method|
26
- delegate api_method, to: :publisher_type
27
- end
28
+ delegate :cleanup, :publish_to, :direct_publish_to, :purge_queue, :queue_status, to: :publisher_instance
28
29
 
29
30
  private
30
31
 
31
- def publisher_type
32
- @publisher_type || Amqp
32
+ # Default publisher type is Amqp.
33
+ def publisher_instance
34
+ @publisher_instance || Amqp
33
35
  end
34
36
  end
35
37
  end
36
- end
38
+ end
@@ -1,75 +1,72 @@
1
1
  require 'rabbit_jobs/publisher/base'
2
+ require 'rabbit_jobs/amqp_transport'
2
3
 
3
4
  module RabbitJobs
4
5
  class Publisher
6
+ # AMQP publisher implementation.
5
7
  class Amqp < Base
6
8
  class << self
9
+ delegate :amqp_cleanup, :amqp_connection, :publisher_channel, to: RabbitJobs::AmqpTransport
7
10
 
8
11
  def cleanup
9
- conn = Thread.current[:rj_publisher_connection]
10
- conn.close if conn && conn.status != :not_connected
11
- Thread.current[:rj_publisher_connection] = nil
12
+ amqp_cleanup
12
13
  end
13
14
 
14
15
  def publish_to(routing_key, klass, *params)
15
- raise ArgumentError.new("klass=#{klass.inspect}") unless klass.is_a?(Class) || klass.is_a?(String)
16
- routing_key = routing_key.to_sym unless routing_key.is_a?(Symbol)
17
- raise ArgumentError.new("routing_key=#{routing_key}") unless RabbitJobs.config[:queues][routing_key]
16
+ check_amqp_publishing_params(routing_key, klass)
18
17
 
19
18
  payload = Job.serialize(klass, *params)
20
- direct_publish_to(routing_key, payload)
19
+ direct_publish_to(routing_key.to_sym, payload)
21
20
  end
22
21
 
23
22
  def direct_publish_to(routing_key, payload, ex = {})
24
- ex = {name: ex.to_s} unless ex.is_a?(Hash)
25
- begin
26
- exchange_opts = Configuration::DEFAULT_MESSAGE_PARAMS.merge(ex || {})
27
- exchange_name = exchange_opts.delete(:name).to_s
28
-
29
- exchange = connection.default_channel.exchange(exchange_name, passive: true)
30
- exchange.on_return do |basic_deliver, properties, payload|
31
- RJ.logger.error full_message: caller.join("\r\n"),
32
- short_message: "AMQP ERROR: (#{basic_deliver[:reply_code]}) #{basic_deliver[:reply_text].to_s}. exchange: #{basic_deliver[:exchange]}, key: #{basic_deliver[:routing_key]}.",
33
- _basic_deliver: basic_deliver.inspect, _properties: properties.inspect, _payload: payload.inspect
34
- true
35
- end
36
-
37
- unless connection.default_channel.basic_publish(payload, exchange_name, routing_key, exchange_opts).connection.connected?
38
- raise "Disconnected from #{RJ.config.server}. Connection status: #{connection.try(:status).inspect}"
39
- end
40
- rescue
41
- RabbitJobs.logger.error $!.message
42
- raise $!
43
- end
23
+ exchange_name, exchange_opts = build_exchange(ex)
24
+ publisher_channel.basic_publish(payload, exchange_name, routing_key, exchange_opts)
44
25
 
26
+ fail "Disconnected from #{RJ.config.server}." unless amqp_connection.connected?
45
27
  true
28
+ rescue
29
+ RabbitJobs.logger.error $!.message
30
+ raise $!
46
31
  end
47
32
 
48
33
  def purge_queue(*routing_keys)
49
- raise ArgumentError unless routing_keys.present?
34
+ fail ArgumentError unless routing_keys.present?
50
35
 
51
- messages_count = 0
52
36
  routing_keys.map(&:to_sym).each do |routing_key|
53
- queue = connection.default_channel.queue(routing_key, RabbitJobs.config[:queues][routing_key])
54
- messages_count += queue.status[:message_count].to_i
55
- connection.default_channel.queue_purge(routing_key)
37
+ publisher_channel.queue_purge(routing_key)
56
38
  end
39
+ end
57
40
 
58
- messages_count
41
+ def queue_status(routing_key)
42
+ check_queue_status_params(routing_key)
43
+ publisher_channel.queue(routing_key, RabbitJobs.config[:queues][routing_key.to_sym]).status
59
44
  end
60
45
 
61
46
  private
62
47
 
63
- def settings
64
- Thread.current[:rj_publisher] ||= {}
48
+ def build_exchange(ex)
49
+ ex = { name: ex.to_s } unless ex.is_a?(Hash)
50
+ exchange_opts = Configuration::DEFAULT_MESSAGE_PARAMS.merge(ex || {})
51
+ exchange_name = exchange_opts.delete(:name).to_s
52
+
53
+ exchange = publisher_channel.exchange(exchange_name, passive: true)
54
+ exchange_on_return_policy(exchange)
55
+ [exchange_name, exchange_opts]
65
56
  end
66
57
 
67
- def connection
68
- unless settings[:connection]
69
- settings[:connection] = Bunny.new(RabbitJobs.config.server,
70
- properties: Bunny::Session::DEFAULT_CLIENT_PROPERTIES.merge(product: "rj_scheduler #{Process.pid}")).start
58
+ def exchange_on_return_policy(exchange)
59
+ exchange.on_return do |basic_deliver, properties, returned_payload|
60
+ RJ.logger.error full_message: caller.join("\r\n"),
61
+ short_message: "AMQP ERROR: (#{basic_deliver[:reply_code]}) " \
62
+ "#{basic_deliver[:reply_text]}. " \
63
+ "exchange: #{basic_deliver[:exchange]}, " \
64
+ "key: #{basic_deliver[:routing_key]}.",
65
+ _basic_deliver: basic_deliver.inspect,
66
+ _properties: properties.inspect,
67
+ _payload: returned_payload.inspect
68
+ true
71
69
  end
72
- settings[:connection]
73
70
  end
74
71
  end
75
72
  end
@@ -1,23 +1,41 @@
1
1
  module RabbitJobs
2
2
  class Publisher
3
+ # Abstract publisher.
3
4
  class Base
4
5
  class << self
5
6
  def cleanup
6
- raise NotImplementedError
7
+ fail NotImplementedError
7
8
  end
8
9
 
9
- def publish_to(routing_key, klass, *params)
10
- raise NotImplementedError
10
+ def publish_to(_routing_key, _klass, *_params)
11
+ fail NotImplementedError
11
12
  end
12
13
 
13
- def direct_publish_to(routing_key, payload, ex = {})
14
- raise NotImplementedError
14
+ def direct_publish_to(_routing_key, _payload, _ex = {})
15
+ fail NotImplementedError
15
16
  end
16
17
 
17
- def purge_queue(*routing_keys)
18
- raise NotImplementedError
18
+ def purge_queue(*_routing_keys)
19
+ fail NotImplementedError
20
+ end
21
+
22
+ def queue_status(_routing_key)
23
+ fail NotImplementedError
24
+ end
25
+
26
+ protected
27
+
28
+ def check_amqp_publishing_params(routing_key, klass)
29
+ fail ArgumentError, "klass=#{klass.inspect}" unless klass.is_a?(Class) || klass.is_a?(String)
30
+ routing_key = routing_key.to_sym unless routing_key.is_a?(Symbol)
31
+ fail ArgumentError, "routing_key=#{routing_key}" unless RabbitJobs.config[:queues][routing_key]
32
+ end
33
+
34
+ def check_queue_status_params(routing_key)
35
+ fail ArgumentError, 'routing_key is blank' if routing_key.blank?
36
+ fail ArgumentError, "Unknown queue: #{routing_key}" unless RJ.config.queue?(routing_key)
19
37
  end
20
38
  end
21
39
  end
22
40
  end
23
- end
41
+ end
@@ -2,21 +2,28 @@ require 'rabbit_jobs/publisher/base'
2
2
 
3
3
  module RabbitJobs
4
4
  class Publisher
5
+ # Synchronous publisher.
6
+ # Calls Job#perform with no RabbitMQ interaction.
5
7
  class Sync < Base
6
8
  class << self
7
-
8
9
  def cleanup
9
10
  end
10
11
 
11
12
  def publish_to(routing_key, klass, *params)
12
- raise ArgumentError.new("klass=#{klass.inspect}") unless klass.is_a?(Class) || klass.is_a?(String)
13
+ fail ArgumentError, "klass=#{klass.inspect}" unless klass.is_a?(Class) || klass.is_a?(String)
13
14
  routing_key = routing_key.to_sym unless routing_key.is_a?(Symbol)
14
- raise ArgumentError.new("routing_key=#{routing_key}") unless RabbitJobs.config[:queues][routing_key]
15
+ fail ArgumentError, "routing_key=#{routing_key}" unless RabbitJobs.config[:queues][routing_key]
15
16
 
16
17
  klass.perform(*params)
17
18
  end
18
19
 
19
- def purge_queue(*routing_keys)
20
+ def purge_queue(*_routing_keys)
21
+ fail ArgumentError unless routing_keys.present?
22
+ end
23
+
24
+ def queue_status(routing_key)
25
+ check_queue_status_params(routing_key)
26
+ { message_count: 0, consumer_count: 0 }
20
27
  end
21
28
  end
22
29
  end
@@ -2,20 +2,19 @@ require 'rabbit_jobs/publisher/base'
2
2
 
3
3
  module RabbitJobs
4
4
  class Publisher
5
+ # Publisher for testing.
6
+ # Stores AMQP messages to array.
5
7
  class Test < Base
6
8
  class << self
7
-
8
9
  def cleanup
9
10
  messages.clear
10
11
  end
11
12
 
12
13
  def publish_to(routing_key, klass, *params)
13
- raise ArgumentError.new("klass=#{klass.inspect}") unless klass.is_a?(Class) || klass.is_a?(String)
14
- routing_key = routing_key.to_sym unless routing_key.is_a?(Symbol)
15
- raise ArgumentError.new("routing_key=#{routing_key}") unless RabbitJobs.config[:queues][routing_key]
14
+ check_amqp_publishing_params(routing_key, klass)
16
15
 
17
16
  payload = Job.serialize(klass, *params)
18
- direct_publish_to(routing_key, payload)
17
+ direct_publish_to(routing_key.to_sym, payload)
19
18
  end
20
19
 
21
20
  def direct_publish_to(routing_key, payload, ex = {})
@@ -25,11 +24,13 @@ module RabbitJobs
25
24
  end
26
25
 
27
26
  def purge_queue(*routing_keys)
28
- raise ArgumentError unless routing_keys.present?
29
-
30
- ret = messages.count
27
+ fail ArgumentError unless routing_keys.present?
31
28
  messages.clear
32
- ret
29
+ end
30
+
31
+ def queue_status(routing_key)
32
+ check_queue_status_params(routing_key)
33
+ { message_count: messages.count, consumer_count: 0 }
33
34
  end
34
35
 
35
36
  private
@@ -1,5 +1,5 @@
1
- # -*- encoding : utf-8 -*-
2
1
  module RabbitJobs
2
+ # Scheduler daemon.
3
3
  class Scheduler
4
4
  include MainLoop
5
5
 
@@ -10,21 +10,12 @@ module RabbitJobs
10
10
  @schedule = HashWithIndifferentAccess.new(value)
11
11
  end
12
12
 
13
- def load_default_schedule
14
- if defined?(Rails)
15
- file = Rails.root.join('config/schedule.yml')
16
- if file.file?
17
- @schedule = HashWithIndifferentAccess.new(YAML.load_file(file))
18
- end
19
- end
20
- end
21
-
22
13
  # Pulls the schedule from Resque.schedule and loads it into the
23
14
  # rufus scheduler instance
24
15
  def load_schedule!
25
16
  @schedule ||= load_default_schedule
26
17
 
27
- raise "You should setup a schedule or place it in config/schedule.yml" unless schedule
18
+ fail 'You should setup a schedule or place it in config/schedule.yml' unless schedule
28
19
 
29
20
  schedule.each do |name, config|
30
21
  # If rails_env is set in the config, enforce ENV['RAILS_ENV'] as
@@ -39,7 +30,9 @@ module RabbitJobs
39
30
 
40
31
  # Returns true if the given schedule config hash matches the current ENV['RAILS_ENV']
41
32
  def rails_env_matches?(config)
42
- config['rails_env'] && ENV['RAILS_ENV'] && config['rails_env'].gsub(/\s/,'').split(',').include?(ENV['RAILS_ENV'])
33
+ config['rails_env'] &&
34
+ ENV['RAILS_ENV'] &&
35
+ config['rails_env'].gsub(/\s/, '').split(',').include?(ENV['RAILS_ENV'])
43
36
  end
44
37
 
45
38
  # Publish a job based on a config hash
@@ -68,17 +61,17 @@ module RabbitJobs
68
61
  end
69
62
 
70
63
  # Subscribes to channel and working on jobs
71
- def work(time = 0)
64
+ def work
72
65
  begin
73
66
  return false unless startup
74
67
 
75
- $0 = self.process_name || "rj_scheduler"
68
+ $0 = process_name || 'rj_scheduler'
76
69
 
77
- RabbitJobs.logger.info "Started."
70
+ RabbitJobs.logger.info 'Started.'
78
71
 
79
72
  load_schedule!
80
73
 
81
- return main_loop(time)
74
+ return main_loop
82
75
  rescue
83
76
  log_daemon_error($!)
84
77
  end
@@ -86,33 +79,26 @@ module RabbitJobs
86
79
  true
87
80
  end
88
81
 
89
- def startup
90
- # Fix buffering so we can `rake rj:work > resque.log` and
91
- # get output from the child in there.
92
- $stdout.sync = true
93
-
94
- @shutdown = false
95
-
96
- Signal.trap('TERM') { shutdown }
97
- Signal.trap('INT') { shutdown! }
98
-
99
- true
100
- end
101
-
102
82
  def setup_job_schedule(name, config)
103
83
  interval_defined = false
104
84
  %w(cron every).each do |interval_type|
105
- if config[interval_type].present?
106
- RabbitJobs.logger.info "queueing #{config['class']} (#{name})"
107
- rufus_scheduler.send(interval_type, config[interval_type], blocking: true) do
108
- publish_from_config(config)
109
- end
110
- interval_defined = true
85
+ next if config[interval_type].blank?
86
+ RabbitJobs.logger.info "queueing #{config['class']} (#{name})"
87
+ rufus_scheduler.send(interval_type, config[interval_type], blocking: true) do
88
+ publish_from_config(config)
111
89
  end
90
+ interval_defined = true
112
91
  end
113
- unless interval_defined
114
- RabbitJobs.logger.warn "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
115
- end
92
+ return if interval_defined
93
+
94
+ RabbitJobs.logger.warn "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
95
+ end
96
+
97
+ def load_default_schedule
98
+ return unless defined?(Rails)
99
+ file = Rails.root.join('config/schedule.yml')
100
+ return unless file.file?
101
+ @schedule = HashWithIndifferentAccess.new(YAML.load_file(file))
116
102
  end
117
103
  end
118
- end
104
+ end