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/lib/rabbit_jobs/job.rb
CHANGED
@@ -18,11 +18,11 @@ module RabbitJobs::Job
|
|
18
18
|
|
19
19
|
def run_perform
|
20
20
|
begin
|
21
|
-
RJ.logger.info "
|
21
|
+
RJ.logger.info "rj_worker[##{Process.pid}] performing #{self.to_ruby_string}"
|
22
22
|
self.class.perform(*params)
|
23
23
|
rescue
|
24
|
-
RJ.logger.warn(self.inspect)
|
25
24
|
RJ.logger.warn $!.message
|
25
|
+
RJ.logger.warn(self.to_ruby_string)
|
26
26
|
RJ.logger.warn RJ::Util.cleanup_backtrace($!.backtrace).join("\n")
|
27
27
|
run_on_error_hooks($!)
|
28
28
|
RabbitJobs::ErrorMailer.report_error(self, $!)
|
@@ -70,6 +70,19 @@ module RabbitJobs::Job
|
|
70
70
|
false
|
71
71
|
end
|
72
72
|
end
|
73
|
+
|
74
|
+
def to_ruby_string
|
75
|
+
rs = self.class.name
|
76
|
+
if params.count > 0
|
77
|
+
rs << "("
|
78
|
+
rs << params.map(&:to_s).join(", ")
|
79
|
+
rs << ")"
|
80
|
+
end
|
81
|
+
if opts.count > 0
|
82
|
+
rs << ", opts: "
|
83
|
+
rs << opts.inspect
|
84
|
+
end
|
85
|
+
end
|
73
86
|
end
|
74
87
|
|
75
88
|
module ClassMethods
|
@@ -99,12 +112,16 @@ module RabbitJobs::Job
|
|
99
112
|
rescue NameError
|
100
113
|
RJ.logger.error "Cannot find job class '#{encoded['class']}'"
|
101
114
|
:not_found
|
115
|
+
rescue JSON::ParserError
|
116
|
+
RJ.logger.error "rj[##{Process.pid}] Cannot initialize job. Json parsing error."
|
117
|
+
RJ.logger.error "rj[##{Process.pid}] Data received: #{payload.inspect}"
|
118
|
+
:parsing_error
|
102
119
|
rescue
|
103
|
-
RJ.logger.
|
120
|
+
RJ.logger.warn "rj[##{Process.pid}] Cannot initialize job."
|
104
121
|
RJ.logger.warn $!.message
|
105
122
|
RJ.logger.warn RJ::Util.cleanup_backtrace($!.backtrace).join("\n")
|
106
|
-
RJ.logger.
|
107
|
-
|
123
|
+
RJ.logger.warn "Data received: #{payload.inspect}"
|
124
|
+
:error
|
108
125
|
end
|
109
126
|
end
|
110
127
|
end
|
@@ -9,8 +9,8 @@ module RabbitJobs
|
|
9
9
|
module Publisher
|
10
10
|
extend self
|
11
11
|
|
12
|
-
def publish(klass, *params)
|
13
|
-
publish_to(RJ.config.default_queue, klass, *params)
|
12
|
+
def publish(klass, *params, &block)
|
13
|
+
publish_to(RJ.config.default_queue, klass, *params, &block)
|
14
14
|
end
|
15
15
|
|
16
16
|
def publish_to(routing_key, klass, *params)
|
@@ -18,55 +18,48 @@ module RabbitJobs
|
|
18
18
|
raise ArgumentError.new("routing_key=#{routing_key}") unless routing_key && (routing_key.is_a?(Symbol) || routing_key.is_a?(String)) && !!RJ.config[:queues][routing_key.to_s]
|
19
19
|
|
20
20
|
begin
|
21
|
-
|
22
|
-
|
21
|
+
payload = {
|
22
|
+
'class' => klass.to_s,
|
23
|
+
'opts' => {'created_at' => Time.now.to_i},
|
24
|
+
'params' => params
|
25
|
+
}.to_json
|
23
26
|
|
24
|
-
|
25
|
-
'class' => klass.to_s,
|
26
|
-
'opts' => {'created_at' => Time.now.to_i},
|
27
|
-
'params' => params
|
28
|
-
}.to_json
|
27
|
+
AmqpHelper.prepare_channel
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
AMQP.connection.disconnect { EM.stop }
|
33
|
-
end
|
34
|
-
end
|
29
|
+
AMQP.channel.default_exchange.publish(payload, Configuration::DEFAULT_MESSAGE_PARAMS.merge({key: RJ.config.queue_name(routing_key.to_s)})) do
|
30
|
+
yield if block_given?
|
35
31
|
end
|
36
32
|
rescue
|
37
|
-
|
38
|
-
RJ.logger.warn $!.inspect
|
33
|
+
RJ.logger.warn $!.message
|
39
34
|
RJ.logger.warn $!.backtrace.join("\n")
|
35
|
+
raise $!
|
40
36
|
end
|
41
37
|
|
42
38
|
true
|
43
39
|
end
|
44
40
|
|
45
|
-
def purge_queue(*routing_keys)
|
41
|
+
def purge_queue(*routing_keys, &block)
|
46
42
|
raise ArgumentError unless routing_keys && routing_keys.count > 0
|
47
43
|
|
48
44
|
messages_count = 0
|
49
|
-
|
50
45
|
count = routing_keys.count
|
51
46
|
|
52
|
-
AmqpHelper.
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
47
|
+
AmqpHelper.prepare_channel
|
48
|
+
|
49
|
+
routing_keys.each do |routing_key|
|
50
|
+
queue = AMQP.channel.queue(RJ.config.queue_name(routing_key), RJ.config[:queues][routing_key.to_s])
|
51
|
+
queue.status do |messages, consumers|
|
52
|
+
# messages_count += messages
|
53
|
+
queue.purge do |ret|
|
54
|
+
raise "Cannot purge queue #{routing_key.to_s}." unless ret.is_a?(AMQ::Protocol::Queue::PurgeOk)
|
55
|
+
messages_count += ret.message_count
|
56
|
+
count -= 1
|
57
|
+
if count == 0
|
58
|
+
yield messages_count if block_given?
|
64
59
|
end
|
65
60
|
end
|
66
61
|
end
|
67
62
|
end
|
68
|
-
|
69
|
-
return messages_count
|
70
63
|
end
|
71
64
|
end
|
72
65
|
end
|
@@ -70,6 +70,7 @@ module RabbitJobs
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def rufus_scheduler
|
73
|
+
raise "Cannot start without eventmachine running." unless EM.reactor_running?
|
73
74
|
@rufus_scheduler ||= Rufus::Scheduler.start_new
|
74
75
|
end
|
75
76
|
|
@@ -83,44 +84,48 @@ module RabbitJobs
|
|
83
84
|
|
84
85
|
# Subscribes to channel and working on jobs
|
85
86
|
def work(time = 0)
|
86
|
-
|
87
|
+
begin
|
88
|
+
return false unless startup
|
87
89
|
|
88
|
-
|
90
|
+
$0 = self.process_name || "rj_scheduler"
|
89
91
|
|
90
|
-
|
91
|
-
|
92
|
-
|
92
|
+
processed_count = 0
|
93
|
+
RJ.run do
|
94
|
+
AmqpHelper.prepare_channel
|
93
95
|
|
94
|
-
|
96
|
+
load_schedule!
|
95
97
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
98
|
+
check_shutdown = Proc.new {
|
99
|
+
if @shutdown
|
100
|
+
RJ.stop {
|
101
|
+
File.delete(self.pidfile) if self.pidfile
|
102
|
+
}
|
103
|
+
RJ.logger.info "rj_scheduler[##{Process.pid}] stopped."
|
104
|
+
end
|
105
|
+
}
|
104
106
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
107
|
+
if time > 0
|
108
|
+
EM.add_timer(time) do
|
109
|
+
self.shutdown
|
110
|
+
end
|
109
111
|
end
|
110
|
-
end
|
111
112
|
|
112
|
-
|
113
|
+
EM.add_periodic_timer(1) do
|
114
|
+
check_shutdown.call
|
115
|
+
end
|
113
116
|
|
114
|
-
|
115
|
-
check_shutdown.call
|
117
|
+
RJ.logger.info "rj_scheduler[##{Process.pid}] started."
|
116
118
|
end
|
119
|
+
rescue => e
|
120
|
+
RJ.logger.error e.message
|
121
|
+
RJ.logger.error e.backtrace.join("\r\n")
|
117
122
|
end
|
118
123
|
|
119
124
|
true
|
120
125
|
end
|
121
126
|
|
122
127
|
def shutdown
|
123
|
-
RJ.logger.
|
128
|
+
RJ.logger.info "rj_scheduler[##{Process.pid}] stopping..."
|
124
129
|
@shutdown = true
|
125
130
|
end
|
126
131
|
|
@@ -138,14 +143,14 @@ module RabbitJobs
|
|
138
143
|
end
|
139
144
|
end
|
140
145
|
|
141
|
-
if self.pidfile
|
142
|
-
File.open(self.pidfile, 'w') { |f| f << Process.pid }
|
143
|
-
end
|
144
|
-
|
145
146
|
# Fix buffering so we can `rake rj:work > resque.log` and
|
146
147
|
# get output from the child in there.
|
147
148
|
$stdout.sync = true
|
148
149
|
|
150
|
+
if self.pidfile
|
151
|
+
File.open(self.pidfile, 'w') { |f| f << Process.pid }
|
152
|
+
end
|
153
|
+
|
149
154
|
@shutdown = false
|
150
155
|
|
151
156
|
Signal.trap('TERM') { shutdown }
|
data/lib/rabbit_jobs/tasks.rb
CHANGED
@@ -1,46 +1,228 @@
|
|
1
|
-
# require '
|
1
|
+
# require 'rabbit_jobs/tasks'
|
2
2
|
# will give you the resque tasks
|
3
3
|
|
4
4
|
require 'rabbit_jobs'
|
5
5
|
require 'logger'
|
6
|
+
require 'rake'
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
def rails_env
|
9
|
+
$my_rails_env ||= defined?(Rails) ? Rails.env : (ENV['RAILS_ENV'] || 'development')
|
10
|
+
end
|
11
|
+
|
12
|
+
def app_root
|
13
|
+
$my_rails_root ||= Pathname.new(ENV['RAILS_ROOT'] || Rails.root)
|
14
|
+
end
|
13
15
|
|
14
|
-
|
16
|
+
def make_dirs
|
17
|
+
["log", "tmp", "tmp/pids"].each do |subdir|
|
18
|
+
dir = app_root.join(subdir)
|
19
|
+
Dir.mkdir(dir) unless File.directory?(dir)
|
15
20
|
end
|
21
|
+
end
|
16
22
|
|
17
|
-
|
18
|
-
task :
|
19
|
-
|
23
|
+
namespace :rj do
|
24
|
+
task :environment do
|
25
|
+
# Rails.application.eager_load!
|
26
|
+
Rails.application.require_environment!
|
27
|
+
end
|
20
28
|
|
21
|
-
|
22
|
-
|
29
|
+
# MULTIPROCESS
|
30
|
+
namespace :worker do
|
31
|
+
desc "Start a Rabbit Jobs workers from config/rj_workers.yml"
|
32
|
+
task :start => [:environment, :load_config] do
|
33
|
+
make_dirs
|
34
|
+
@rj_config.each do |worker_name, worker_props|
|
35
|
+
worker_num = 1
|
36
|
+
worker_props['instances'].to_i.times do
|
37
|
+
unless @do_only.count > 0 && !@do_only.include?("#{worker_name}-#{worker_num}")
|
38
|
+
queues = (worker_props['queue'] || worker_props['queues'] || "").split(' ')
|
23
39
|
|
24
|
-
|
25
|
-
|
40
|
+
worker = RJ::Worker.new(*queues)
|
41
|
+
worker.background = true
|
42
|
+
worker.process_name = "rj_worker #{worker_name}##{worker_num} #{rails_env} [#{queues.join(',')}]"
|
43
|
+
worker.pidfile = app_root.join("tmp/pids/rj_worker_#{rails_env}_#{worker_name}_#{worker_num}.pid")
|
44
|
+
RJ.logger = ::Logger.new(app_root.join("log/rj_worker_#{rails_env}_#{worker_name}_#{worker_num}.log"), 'daily')
|
45
|
+
# RJ.logger.level = ENV['VERBOSE'] ? Logger::INFO : Logger::WARN
|
46
|
+
puts "Starting #{worker_name}##{worker_num}"
|
47
|
+
|
48
|
+
# завершаем копию процесса, если воркер уже отработал
|
49
|
+
exit! if worker.work
|
50
|
+
end
|
51
|
+
worker_num += 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
task :stop => :load_config do
|
57
|
+
# получаем идентификаторы процессов
|
58
|
+
pids = {}
|
59
|
+
errors = []
|
60
|
+
|
61
|
+
@rj_config.each do |worker_name, worker_props|
|
62
|
+
worker_num = 1
|
63
|
+
worker_props['instances'].to_i.times do
|
64
|
+
unless (@do_only.count > 0) && !@do_only.include?("#{worker_name}-#{worker_num}")
|
65
|
+
pidfile = app_root.join("tmp/pids/rj_worker_#{rails_env}_#{worker_name}_#{worker_num}.pid")
|
66
|
+
|
67
|
+
unless File.exists?(pidfile)
|
68
|
+
msg = "Pidfile not found: #{pidfile}"
|
69
|
+
errors << msg
|
70
|
+
$stderr.puts msg
|
71
|
+
else
|
72
|
+
pid = open(pidfile).read.to_i
|
73
|
+
pids[pid] = pidfile
|
74
|
+
queues = (worker_props['queue'] || worker_props['queues'] || "").split(' ')
|
75
|
+
puts "Stopping rj_worker #{worker_name}##{worker_num} #{rails_env} [#{queues.join(',')}]"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
worker_num += 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# пытаемся их убить
|
83
|
+
killed_pids = []
|
84
|
+
pids.each do |pid, pidfile|
|
85
|
+
begin
|
86
|
+
puts "try killing ##{pid}"
|
87
|
+
Process.kill("TERM", pid)
|
88
|
+
rescue => e
|
89
|
+
errors << "Not found process: #{pid} from #{pidfile}"
|
90
|
+
$stderr.puts errors.last
|
91
|
+
$stderr.puts "Removing pidfile ..."
|
92
|
+
File.delete(pidfile) if File.exist?(pidfile)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
while killed_pids.count != pids.keys.count
|
97
|
+
pids.each_key do |pid|
|
98
|
+
begin
|
99
|
+
Process.kill(0, pid)
|
100
|
+
stopped = false
|
101
|
+
break
|
102
|
+
rescue
|
103
|
+
killed_pids.push(pid) unless killed_pids.include?(pid)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
print '.'
|
107
|
+
sleep 1
|
108
|
+
end
|
109
|
+
|
110
|
+
exit(1) if not errors.empty?
|
111
|
+
|
112
|
+
puts "\nrj_worker stopped."
|
113
|
+
exit(0)
|
114
|
+
end
|
115
|
+
|
116
|
+
task :status => :load_config do
|
117
|
+
|
118
|
+
errors ||= 0
|
26
119
|
|
27
|
-
|
28
|
-
|
29
|
-
|
120
|
+
@rj_config.each do |worker_name, worker_props|
|
121
|
+
worker_num = 1
|
122
|
+
worker_props['instances'].to_i.times do
|
123
|
+
pidfile = app_root.join("tmp/pids/rj_worker_#{rails_env}_#{worker_name}_#{worker_num}.pid")
|
30
124
|
|
31
|
-
|
125
|
+
unless File.exists?(pidfile)
|
126
|
+
puts "Pidfile not found: #{pidfile}"
|
127
|
+
errors += 1
|
128
|
+
else
|
129
|
+
pid = open(pidfile).read.to_i
|
130
|
+
begin
|
131
|
+
raise "must return 1" unless Process.kill(0, pid) == 1
|
132
|
+
rescue
|
133
|
+
puts "Pidfile found but process not respond: #{pidfile}\r\nRemoving pidfile."
|
134
|
+
File.delete(pidfile) if File.exist?(pidfile)
|
135
|
+
errors += 1
|
136
|
+
end
|
137
|
+
end
|
138
|
+
worker_num += 1
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
puts "ok" if errors == 0
|
143
|
+
exit errors
|
144
|
+
end
|
145
|
+
|
146
|
+
task :load_config do
|
147
|
+
@rj_config = YAML.load(open app_root.join("config/rj_workers.yml"))
|
148
|
+
@rj_config = @rj_config[rails_env]
|
149
|
+
|
150
|
+
@do_only = ENV["WORKERS"] ? ENV["WORKERS"].strip.split : []
|
151
|
+
@do_only.each do |worker|
|
152
|
+
worker_name, worker_num = worker.split('-')
|
153
|
+
unless @rj_config.keys.include?(worker_name) && @rj_config[worker_name]['instances'].to_i >= worker_num.to_i
|
154
|
+
raise "Worker #{worker} not found."
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
32
158
|
end
|
33
159
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
160
|
+
namespace :scheduler do
|
161
|
+
task :start do
|
162
|
+
make_dirs
|
163
|
+
|
164
|
+
scheduler = RabbitJobs::Scheduler.new
|
165
|
+
|
166
|
+
scheduler.background = true
|
167
|
+
scheduler.pidfile = app_root.join('tmp/pids/rj_scheduler.pid')
|
168
|
+
RJ.logger = ::Logger.new(app_root.join('log/rj_scheduler.log'), 'daily')
|
169
|
+
# RJ.logger.level = ENV['VERBOSE'] ? Logger::INFO : Logger::WARN
|
170
|
+
|
171
|
+
scheduler.work
|
172
|
+
puts "rj_scheduler started."
|
173
|
+
end
|
174
|
+
|
175
|
+
task :stop do
|
176
|
+
pidfile = app_root.join('tmp/pids/rj_scheduler.pid')
|
177
|
+
|
178
|
+
unless File.exists?(pidfile)
|
179
|
+
msg = "Pidfile not found: #{pidfile}"
|
180
|
+
$stderr.puts msg
|
181
|
+
exit(1)
|
182
|
+
else
|
183
|
+
pid = open(pidfile).read.to_i
|
184
|
+
begin
|
185
|
+
Process.kill("TERM", pid)
|
186
|
+
rescue => e
|
187
|
+
$stderr.puts "Not found process: #{pid} from #{pidfile}"
|
188
|
+
$stderr.puts "Removing pidfile ..."
|
189
|
+
File.delete(pidfile) if File.exist?(pidfile)
|
190
|
+
exit(1)
|
191
|
+
end
|
192
|
+
|
193
|
+
while true
|
194
|
+
begin
|
195
|
+
Process.kill(0, pid)
|
196
|
+
sleep 0.1
|
197
|
+
rescue
|
198
|
+
puts "rj_scheduler[##{pid}] stopped."
|
199
|
+
exit(0)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
task :status do
|
206
|
+
pidfile = app_root.join('tmp/pids/rj_scheduler.pid')
|
207
|
+
|
208
|
+
unless File.exists?(pidfile)
|
209
|
+
puts "Pidfile not found: #{pidfile}"
|
210
|
+
exit(1)
|
211
|
+
else
|
212
|
+
pid = open(pidfile).read.to_i
|
213
|
+
begin
|
214
|
+
raise "must return 1" unless Process.kill(0, pid) == 1
|
215
|
+
puts "ok"
|
216
|
+
rescue
|
217
|
+
puts "Pidfile found but process not respond: #{pidfile}"
|
218
|
+
puts "Removing pidfile."
|
219
|
+
File.delete(pidfile) if File.exist?(pidfile)
|
220
|
+
exit(1)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
exit(0)
|
44
224
|
end
|
45
225
|
end
|
226
|
+
|
227
|
+
# desc "Start a Rabbit Jobs scheduler"
|
46
228
|
end
|