rabbit_jobs 0.11.5 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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