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.
- checksums.yaml +4 -4
- data/Rakefile +1 -1
- data/build +2 -6
- data/examples/client +1 -2
- data/examples/configuration.rb +3 -3
- data/examples/worker +1 -1
- data/lib/rabbit_jobs.rb +52 -42
- data/lib/rabbit_jobs/amqp_transport.rb +33 -0
- data/lib/rabbit_jobs/configuration.rb +15 -51
- data/lib/rabbit_jobs/consumer/job_consumer.rb +7 -17
- data/lib/rabbit_jobs/job.rb +102 -127
- data/lib/rabbit_jobs/main_loop.rb +25 -24
- data/lib/rabbit_jobs/publisher.rb +19 -17
- data/lib/rabbit_jobs/publisher/amqp.rb +37 -40
- data/lib/rabbit_jobs/publisher/base.rb +26 -8
- data/lib/rabbit_jobs/publisher/sync.rb +11 -4
- data/lib/rabbit_jobs/publisher/test.rb +10 -9
- data/lib/rabbit_jobs/scheduler.rb +25 -39
- data/lib/rabbit_jobs/tasks.rb +8 -8
- data/lib/rabbit_jobs/version.rb +1 -3
- data/lib/rabbit_jobs/worker.rb +26 -45
- data/lib/tasks/rabbit_jobs.rake +1 -1
- data/rabbit_jobs.gemspec +20 -19
- data/spec/fixtures/config.yml +2 -2
- data/spec/fixtures/jobs.rb +1 -3
- data/spec/integration/publisher_spec.rb +30 -9
- data/spec/integration/scheduler_spec.rb +10 -5
- data/spec/integration/worker_spec.rb +10 -3
- data/spec/spec_helper.rb +7 -3
- data/spec/unit/configuration_spec.rb +37 -18
- data/spec/unit/job_consumer_spec.rb +11 -10
- data/spec/unit/job_spec.rb +12 -14
- data/spec/unit/rabbit_jobs_spec.rb +5 -6
- data/spec/unit/worker_spec.rb +6 -7
- metadata +20 -5
@@ -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
|
23
|
+
def main_loop
|
13
24
|
loop do
|
14
|
-
sleep
|
15
|
-
|
16
|
-
time -= 1
|
17
|
-
if time == 0
|
18
|
-
shutdown
|
19
|
-
end
|
20
|
-
end
|
25
|
+
sleep 0.5
|
26
|
+
next unless @shutdown
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
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
|
-
@
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
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
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
64
|
-
|
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
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
7
|
+
fail NotImplementedError
|
7
8
|
end
|
8
9
|
|
9
|
-
def publish_to(
|
10
|
-
|
10
|
+
def publish_to(_routing_key, _klass, *_params)
|
11
|
+
fail NotImplementedError
|
11
12
|
end
|
12
13
|
|
13
|
-
def direct_publish_to(
|
14
|
-
|
14
|
+
def direct_publish_to(_routing_key, _payload, _ex = {})
|
15
|
+
fail NotImplementedError
|
15
16
|
end
|
16
17
|
|
17
|
-
def purge_queue(*
|
18
|
-
|
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
|
-
|
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
|
-
|
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(*
|
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
|
-
|
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
|
-
|
29
|
-
|
30
|
-
ret = messages.count
|
27
|
+
fail ArgumentError unless routing_keys.present?
|
31
28
|
messages.clear
|
32
|
-
|
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
|
-
|
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'] &&
|
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
|
64
|
+
def work
|
72
65
|
begin
|
73
66
|
return false unless startup
|
74
67
|
|
75
|
-
$0 =
|
68
|
+
$0 = process_name || 'rj_scheduler'
|
76
69
|
|
77
|
-
RabbitJobs.logger.info
|
70
|
+
RabbitJobs.logger.info 'Started.'
|
78
71
|
|
79
72
|
load_schedule!
|
80
73
|
|
81
|
-
return main_loop
|
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].
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
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
|