rabbit_jobs 0.2.0.pre4 → 0.3
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.
- data/Gemfile +1 -0
- data/bin/rj_scheduler +23 -0
- data/bin/rj_worker +24 -0
- data/examples/client +50 -0
- data/examples/configuration.rb +1 -1
- data/examples/worker +28 -0
- data/lib/rabbit_jobs.rb +89 -5
- data/lib/rabbit_jobs/amqp_helper.rb +81 -29
- data/lib/rabbit_jobs/configuration.rb +28 -11
- data/lib/rabbit_jobs/job.rb +22 -5
- data/lib/rabbit_jobs/publisher.rb +25 -32
- data/lib/rabbit_jobs/scheduler.rb +32 -27
- data/lib/rabbit_jobs/tasks.rb +211 -29
- data/lib/rabbit_jobs/util.rb +1 -1
- data/lib/rabbit_jobs/version.rb +1 -1
- data/lib/rabbit_jobs/worker.rb +39 -36
- data/rabbit_jobs.gemspec +2 -1
- data/spec/integration/publisher_spec.rb +27 -14
- data/spec/integration/scheduler_spec.rb +2 -2
- data/spec/integration/worker_spec.rb +33 -7
- data/spec/unit/configuration_spec.rb +2 -2
- metadata +33 -13
data/Gemfile
CHANGED
data/bin/rj_scheduler
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/bin/env ruby
|
2
|
+
require 'pathname'
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'rake'
|
5
|
+
|
6
|
+
rails_root = Pathname.new(Dir.getwd)
|
7
|
+
ENV['RAILS_ROOT'] = rails_root.to_s
|
8
|
+
ENV['RAILS_ENV'] ||= 'development'
|
9
|
+
|
10
|
+
require File.expand_path('../../lib/rabbit_jobs/tasks', __FILE__)
|
11
|
+
|
12
|
+
case ARGV.first
|
13
|
+
when 'start'
|
14
|
+
require 'bundler/setup'
|
15
|
+
require rails_root.join('config/application')
|
16
|
+
Rake::Task['rj:scheduler:start'].invoke
|
17
|
+
when 'stop'
|
18
|
+
Rake::Task['rj:scheduler:stop'].invoke
|
19
|
+
when 'status'
|
20
|
+
Rake::Task['rj:scheduler:status'].invoke
|
21
|
+
else
|
22
|
+
raise "usage: rj_scheduler <start>|<stop>|<status>"
|
23
|
+
end
|
data/bin/rj_worker
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/bin/env ruby
|
2
|
+
require 'pathname'
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'rake'
|
5
|
+
|
6
|
+
ENV['RAILS_ENV'] ||= 'development'
|
7
|
+
|
8
|
+
rails_root = Pathname.new(Dir.getwd)
|
9
|
+
|
10
|
+
require 'bundler/setup'
|
11
|
+
require rails_root.join('config/application')
|
12
|
+
Rails.application.require_environment!
|
13
|
+
Rails.application.load_tasks
|
14
|
+
|
15
|
+
case ARGV.first
|
16
|
+
when 'start'
|
17
|
+
Rake::Task['rj:worker:start'].invoke
|
18
|
+
when 'stop'
|
19
|
+
Rake::Task['rj:worker:stop'].invoke
|
20
|
+
when 'status'
|
21
|
+
Rake::Task['rj:worker:status'].invoke
|
22
|
+
else
|
23
|
+
raise "usage: rj_worker <start>|<stop>|<status>"
|
24
|
+
end
|
data/examples/client
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding : utf-8 -*-
|
3
|
+
|
4
|
+
require 'bundler'
|
5
|
+
Bundler.setup
|
6
|
+
require File.expand_path('../../lib/rabbit_jobs', __FILE__)
|
7
|
+
|
8
|
+
class MyCurrentJob
|
9
|
+
include RJ::Job
|
10
|
+
def self.perform(count)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
RJ.configure { |c|
|
15
|
+
c.queue "failover_test"
|
16
|
+
c.server "amqp://dev.lan/"
|
17
|
+
c.server "amqp://localhost/"
|
18
|
+
|
19
|
+
}
|
20
|
+
|
21
|
+
RJ.logger = Logger.new($stdout)
|
22
|
+
|
23
|
+
RJ.run {
|
24
|
+
RJ.publish(MyCurrentJob) {
|
25
|
+
puts 'published first message'
|
26
|
+
RJ.stop
|
27
|
+
}
|
28
|
+
|
29
|
+
# EM.add_timer(5) {
|
30
|
+
# RJ.publish(MyCurrentJob) {
|
31
|
+
# puts 'published second message'
|
32
|
+
# RJ.stop
|
33
|
+
# }
|
34
|
+
# }
|
35
|
+
}
|
36
|
+
|
37
|
+
|
38
|
+
# 1000.times {
|
39
|
+
# RJ.run do
|
40
|
+
# count = 10000
|
41
|
+
# published = 0
|
42
|
+
# count.times {
|
43
|
+
# RJ.publish_to(:default, MyCurrentJob) {
|
44
|
+
# published += 1
|
45
|
+
# RJ.stop if published >= count
|
46
|
+
# }
|
47
|
+
# }
|
48
|
+
# end
|
49
|
+
# puts 'returned from em'
|
50
|
+
# }
|
data/examples/configuration.rb
CHANGED
@@ -4,7 +4,7 @@ require 'rabbit_jobs'
|
|
4
4
|
require 'json'
|
5
5
|
|
6
6
|
RabbitJobs.configure do |c|
|
7
|
-
c.
|
7
|
+
c.server "amqp://localhost/"
|
8
8
|
|
9
9
|
c.queue 'rabbit_jobs_test1', durable: true, auto_delete: false, ack: true, arguments: {'x-ha-policy' => 'all'}
|
10
10
|
c.queue 'rabbit_jobs_test2', durable: true, auto_delete: false, ack: true, arguments: {'x-ha-policy' => 'all'}
|
data/examples/worker
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding : utf-8 -*-
|
3
|
+
|
4
|
+
require 'bundler'
|
5
|
+
Bundler.setup
|
6
|
+
require File.expand_path('../../lib/rabbit_jobs', __FILE__)
|
7
|
+
|
8
|
+
class MyCurrentJob
|
9
|
+
include RJ::Job
|
10
|
+
def self.perform(count = 0)
|
11
|
+
# puts count
|
12
|
+
# RJ.publish_to(:default, MyCurrentJob, count - 1) if count > 0
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
RJ.configure { |c|
|
17
|
+
c.queue "failover_test"
|
18
|
+
c.server "amqp://localhost/"
|
19
|
+
c.server "amqp://dev.lan/"
|
20
|
+
}
|
21
|
+
|
22
|
+
# AMQ::Client.logging = false
|
23
|
+
AMQP.logging = false
|
24
|
+
|
25
|
+
RJ.logger = Logger.new($stdout)
|
26
|
+
|
27
|
+
worker = RJ::Worker.new
|
28
|
+
worker.work
|
data/lib/rabbit_jobs.rb
CHANGED
@@ -18,17 +18,101 @@ require 'logger'
|
|
18
18
|
module RabbitJobs
|
19
19
|
extend self
|
20
20
|
|
21
|
-
def
|
22
|
-
|
21
|
+
def start
|
22
|
+
raise unless block_given?
|
23
|
+
raise if EM.reactor_running?
|
24
|
+
|
25
|
+
EM.run {
|
26
|
+
AmqpHelper.prepare_connection
|
27
|
+
yield
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :run, :start
|
32
|
+
|
33
|
+
def stop
|
34
|
+
if AMQP.connection
|
35
|
+
AMQP.connection.disconnect {
|
36
|
+
AMQP.connection = nil
|
37
|
+
AMQP.channel = nil
|
38
|
+
EM.stop {
|
39
|
+
yield if block_given?
|
40
|
+
}
|
41
|
+
}
|
42
|
+
else
|
43
|
+
EM.stop {
|
44
|
+
yield if block_given?
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def running?
|
50
|
+
EM.reactor_running?
|
51
|
+
end
|
52
|
+
|
53
|
+
def publish(klass, *params, &block)
|
54
|
+
if RJ.running?
|
55
|
+
RJ::Publisher.publish(klass, *params, &block)
|
56
|
+
else
|
57
|
+
RJ.run {
|
58
|
+
RJ::Publisher.publish(klass, *params) {
|
59
|
+
RJ.stop {
|
60
|
+
yield if block_given?
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def publish_to(routing_key, klass, *params, &block)
|
68
|
+
if RJ.running?
|
69
|
+
RJ::Publisher.publish_to(routing_key, klass, *params, &block)
|
70
|
+
else
|
71
|
+
RJ.run {
|
72
|
+
RJ::Publisher.publish_to(routing_key, klass, *params) {
|
73
|
+
RJ.stop {
|
74
|
+
yield if block_given?
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
end
|
23
79
|
end
|
24
80
|
|
25
|
-
def
|
26
|
-
|
81
|
+
def purge_queue(*routing_keys, &block)
|
82
|
+
if RJ.running?
|
83
|
+
RJ::Publisher.purge_queue(*routing_keys, &block)
|
84
|
+
else
|
85
|
+
RJ.run {
|
86
|
+
RJ::Publisher.purge_queue(*routing_keys) { |count|
|
87
|
+
RJ.stop {
|
88
|
+
yield if block_given?
|
89
|
+
return count
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
end
|
27
94
|
end
|
28
95
|
|
29
96
|
attr_writer :logger
|
30
97
|
def logger
|
31
|
-
@logger
|
98
|
+
unless @logger
|
99
|
+
@logger = Logger.new($stdout)
|
100
|
+
# @logger.level = Logger::WARN
|
101
|
+
end
|
102
|
+
@logger
|
103
|
+
end
|
104
|
+
|
105
|
+
def after_fork(&block)
|
106
|
+
raise unless block_given?
|
107
|
+
@_after_fork_callbacks ||= []
|
108
|
+
@_after_fork_callbacks << block
|
109
|
+
end
|
110
|
+
|
111
|
+
def _run_after_fork_callbacks
|
112
|
+
@_after_fork_callbacks ||= []
|
113
|
+
@_after_fork_callbacks.each { |callback|
|
114
|
+
callback.call
|
115
|
+
}
|
32
116
|
end
|
33
117
|
end
|
34
118
|
|
@@ -5,56 +5,108 @@ require 'uri'
|
|
5
5
|
module RabbitJobs
|
6
6
|
class AmqpHelper
|
7
7
|
|
8
|
+
# Timeout to recover connection.
|
9
|
+
RECOVERY_TIMEOUT = 3
|
10
|
+
HOSTS_DEAD = []
|
11
|
+
HOSTS_FAILED = {}
|
12
|
+
|
8
13
|
class << self
|
9
|
-
# Calls given block with initialized amqp connection.
|
10
|
-
def with_amqp
|
11
|
-
raise ArgumentError unless block_given?
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
AMQP.
|
17
|
-
|
18
|
-
yield true
|
19
|
-
}
|
15
|
+
def prepare_connection
|
16
|
+
if !AMQP.connection || AMQP.connection.closed?
|
17
|
+
RJ.logger.info("rj[##{Process.pid}] Connecting to #{RJ.config.servers.first.to_s}...")
|
18
|
+
AMQP.connection = AMQP.connect(RJ.config.servers.first, auto_recovery: true)
|
19
|
+
init_auto_recovery
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
def prepare_channel
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
AMQP.channel ||= AMQP::Channel.new(AMQP.connection, auto_recovery: true)
|
25
|
+
end
|
26
|
+
|
27
|
+
def init_auto_recovery
|
28
|
+
unless $auto_recovery_initiated
|
29
|
+
$auto_recovery_initiated = true
|
30
|
+
|
31
|
+
AMQP.connection.on_recovery do |conn, opts|
|
32
|
+
HOSTS_DEAD.clear
|
33
|
+
HOSTS_FAILED.clear
|
34
|
+
url = url_from_opts opts
|
35
|
+
RJ.logger.warn "rj[##{Process.pid}] Connection to #{url} recovered."
|
36
|
+
end
|
37
|
+
|
38
|
+
AMQP.connection.on_open do |conn, opts|
|
39
|
+
RJ.logger.info "rj[##{Process.pid}] Connected."
|
40
|
+
end
|
41
|
+
|
42
|
+
# AMQP.connection.before_recovery do |conn, opts|
|
43
|
+
# RJ.logger.info "rj[##{Process.pid}] before_recovery"
|
44
|
+
# end
|
45
|
+
|
46
|
+
# AMQP.connection.on_possible_authentication_failure do |conn, opts|
|
47
|
+
# puts opts.inspect
|
48
|
+
# # restore_from_connection_failure(opts)
|
49
|
+
# end
|
50
|
+
|
51
|
+
AMQP.connection.on_tcp_connection_loss do |conn, opts|
|
52
|
+
sleep 2
|
53
|
+
restore_from_connection_failure(opts)
|
54
|
+
end
|
55
|
+
|
56
|
+
# AMQP.connection.on_connection_interruption do |conn|
|
57
|
+
# # restore_from_connection_failure(opts)
|
58
|
+
# end
|
59
|
+
|
60
|
+
AMQP.connection.on_tcp_connection_failure do |opts|
|
61
|
+
sleep 2
|
62
|
+
restore_from_connection_failure(opts)
|
63
|
+
end
|
28
64
|
end
|
29
65
|
end
|
30
66
|
|
31
67
|
private
|
32
68
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
69
|
+
def restore_from_connection_failure(opts)
|
70
|
+
url = opts.empty? ? RJ.config.servers.first : url_from_opts(opts)
|
71
|
+
HOSTS_FAILED[url] ||= Time.now
|
72
|
+
|
73
|
+
if HOSTS_FAILED[url] + RECOVERY_TIMEOUT < Time.now
|
74
|
+
# reconnect to another host
|
75
|
+
HOSTS_DEAD.push(url) unless HOSTS_DEAD.include?(url)
|
76
|
+
new_url = (RJ.config.servers.dup - HOSTS_DEAD.dup).first
|
77
|
+
if new_url
|
78
|
+
reconnect_to(new_url)
|
79
|
+
else
|
80
|
+
# all hosts is dead
|
81
|
+
end
|
82
|
+
else
|
83
|
+
# reconnect to the same host
|
84
|
+
reconnect_to(url)
|
37
85
|
end
|
86
|
+
end
|
38
87
|
|
39
|
-
|
40
|
-
|
41
|
-
RJ.logger.warn "[
|
42
|
-
|
88
|
+
def reconnect_to(url)
|
89
|
+
if AMQP.connection
|
90
|
+
RJ.logger.warn "rj[##{Process.pid}] Trying to reconnect to #{url}..."
|
91
|
+
AMQP.connection.reconnect_to(url, 2)
|
92
|
+
else
|
93
|
+
RJ.logger.warn "rj[##{Process.pid}] Trying to connect to #{url}..."
|
94
|
+
AMQP.connection = AMQP.connect(url, auto_recovery: true)
|
95
|
+
init_auto_recovery
|
43
96
|
end
|
44
97
|
end
|
45
98
|
|
46
99
|
def url_from_opts(opts = {})
|
100
|
+
return "" unless opts
|
101
|
+
return "" if opts.empty?
|
47
102
|
s = ""
|
48
|
-
s << opts[:scheme]
|
103
|
+
s << (opts[:scheme] || "amqp")
|
49
104
|
s << "://"
|
50
|
-
s << "#{opts[:user]}@" if opts[:user] && opts[:user] != 'guest'
|
105
|
+
s << "#{opts[:user]}@" if opts[:user] && !opts[:user].empty? && opts[:user] != 'guest'
|
51
106
|
s << opts[:host]
|
52
107
|
s << ":#{opts[:port]}" unless (opts[:scheme] == 'amqp' && opts[:port] == 5672) || (opts[:scheme] == 'amqps' && opts[:port] == 5673)
|
53
|
-
s << opts[:vhost]
|
54
|
-
|
55
|
-
|
56
|
-
def create_channel
|
57
|
-
AMQP.channel = AMQP::Channel.new(AMQP.connection, auto_recovery: true)
|
108
|
+
s << opts[:vhost] if opts[:vhost] && !opts[:vhost].empty? && opts[:vhost] != "/"
|
109
|
+
s
|
58
110
|
end
|
59
111
|
end
|
60
112
|
end
|
@@ -28,7 +28,6 @@ module RabbitJobs
|
|
28
28
|
|
29
29
|
unless @@configuration
|
30
30
|
self.configure do |c|
|
31
|
-
c.url 'amqp://localhost'
|
32
31
|
c.prefix 'rabbit_jobs'
|
33
32
|
end
|
34
33
|
end
|
@@ -41,6 +40,7 @@ module RabbitJobs
|
|
41
40
|
|
42
41
|
DEFAULT_QUEUE_PARAMS = {
|
43
42
|
auto_delete: false,
|
43
|
+
exclusive: false,
|
44
44
|
durable: true,
|
45
45
|
ack: true
|
46
46
|
}
|
@@ -58,7 +58,7 @@ module RabbitJobs
|
|
58
58
|
def initialize
|
59
59
|
@data = {
|
60
60
|
error_log: true,
|
61
|
-
|
61
|
+
servers: [],
|
62
62
|
prefix: 'rabbit_jobs',
|
63
63
|
queues: {}
|
64
64
|
}
|
@@ -92,14 +92,20 @@ module RabbitJobs
|
|
92
92
|
@data[:error_log] = false
|
93
93
|
end
|
94
94
|
|
95
|
-
def
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
else
|
101
|
-
@data[:url] || 'amqp://localhost'
|
95
|
+
def servers(*value)
|
96
|
+
unless value.empty?
|
97
|
+
@data[:servers] = value.map(&:to_s).map(&:strip).keep_if{|url|!url.empty?}.map {|url|
|
98
|
+
normalize_url(url)
|
99
|
+
}
|
102
100
|
end
|
101
|
+
@data[:servers]
|
102
|
+
end
|
103
|
+
|
104
|
+
def server(value = nil)
|
105
|
+
raise unless value && !value.to_s.empty?
|
106
|
+
value = normalize_url(value.to_s.strip)
|
107
|
+
@data[:servers] ||= []
|
108
|
+
@data[:servers] << value unless @data[:servers].include?(value)
|
103
109
|
end
|
104
110
|
|
105
111
|
def prefix(value = nil)
|
@@ -155,14 +161,25 @@ module RabbitJobs
|
|
155
161
|
elsif defined?(Rails) && yaml[Rails.env.to_s]
|
156
162
|
convert_yaml_config(yaml[Rails.env.to_s])
|
157
163
|
else
|
158
|
-
@data = {
|
159
|
-
%w(
|
164
|
+
@data = {prefix: nil, queues: {}}
|
165
|
+
%w(prefix mail_errors_to mail_errors_from).each do |m|
|
160
166
|
self.send(m, yaml[m])
|
161
167
|
end
|
168
|
+
yaml['servers'].split(",").each do |value|
|
169
|
+
server normalize_url(value)
|
170
|
+
end
|
162
171
|
yaml['queues'].each do |name, params|
|
163
172
|
queue name, symbolize_keys!(params) || {}
|
164
173
|
end
|
165
174
|
end
|
166
175
|
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def normalize_url(url_string)
|
180
|
+
uri = URI.parse(url_string)
|
181
|
+
uri.path = "" if uri.path.to_s == "/"
|
182
|
+
uri.to_s
|
183
|
+
end
|
167
184
|
end
|
168
185
|
end
|