rabbit_jobs 0.2.0.pre4 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,11 +18,11 @@ module RabbitJobs::Job
18
18
 
19
19
  def run_perform
20
20
  begin
21
- RJ.logger.info "Running perform on #{self.inspect}"
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.error "JOB INIT ERROR at #{Time.now.to_s}:"
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.error "message: #{payload.inspect}"
107
- nil
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
- AmqpHelper.with_amqp do |stop_em|
22
- AmqpHelper.prepare_channel
21
+ payload = {
22
+ 'class' => klass.to_s,
23
+ 'opts' => {'created_at' => Time.now.to_i},
24
+ 'params' => params
25
+ }.to_json
23
26
 
24
- payload = {
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
- AMQP::Exchange.default.publish(payload, Configuration::DEFAULT_MESSAGE_PARAMS.merge({key: RJ.config.queue_name(routing_key.to_s)})) do
31
- if stop_em
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
- raise $!
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.with_amqp do |stop_em|
53
- routing_keys.each do |routing_key|
54
- queue = AMQP.channel.queue(RJ.config.queue_name(routing_key), RJ.config[:queues][routing_key.to_s])
55
- queue.status do |messages, consumers|
56
- # messages_count += messages
57
- queue.purge do |ret|
58
- raise "Cannot purge queue #{routing_key.to_s}." unless ret.is_a?(AMQ::Protocol::Queue::PurgeOk)
59
- messages_count += ret.message_count
60
- count -= 1
61
- if count == 0 && stop_em
62
- AMQP.connection.disconnect { EM.stop }
63
- end
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
- return false unless startup
87
+ begin
88
+ return false unless startup
87
89
 
88
- $0 = self.process_name || "rj_scheduler"
90
+ $0 = self.process_name || "rj_scheduler"
89
91
 
90
- processed_count = 0
91
- AmqpHelper.with_amqp do |stop_em|
92
- AmqpHelper.prepare_channel
92
+ processed_count = 0
93
+ RJ.run do
94
+ AmqpHelper.prepare_channel
93
95
 
94
- load_schedule!
96
+ load_schedule!
95
97
 
96
- check_shutdown = Proc.new {
97
- if @shutdown
98
- AMQP.connection.disconnect {
99
- File.delete(self.pidfile) if self.pidfile
100
- EM.stop { exit! }
101
- }
102
- end
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
- if time > 0
106
- # for debugging
107
- EM.add_timer(time) do
108
- self.shutdown
107
+ if time > 0
108
+ EM.add_timer(time) do
109
+ self.shutdown
110
+ end
109
111
  end
110
- end
111
112
 
112
- RJ.logger.info "Scheduler started."
113
+ EM.add_periodic_timer(1) do
114
+ check_shutdown.call
115
+ end
113
116
 
114
- EM.add_periodic_timer(1) do
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.warn "Stopping scheduler..."
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 }
@@ -1,46 +1,228 @@
1
- # require 'resque/tasks'
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
- namespace :rj do
8
- def initialize_rj_daemon(daemon)
9
- daemon.pidfile = ENV['PIDFILE']
10
- daemon.background = %w(yes true).include? ENV['BACKGROUND']
11
- RJ.logger = ::Logger.new(ENV['LOGFILE'] || $stdout)
12
- RJ.logger.level = ENV['VERBOSE'] ? Logger::INFO : Logger::WARN
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
- daemon
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
- desc "Start a Rabbit Jobs worker"
18
- task :worker => :environment do
19
- require 'rabbit_jobs'
23
+ namespace :rj do
24
+ task :environment do
25
+ # Rails.application.eager_load!
26
+ Rails.application.require_environment!
27
+ end
20
28
 
21
- queues = (ENV['QUEUES'] || ENV['QUEUE']).to_s.split(',')
22
- worker = initialize_rj_daemon(RJ::Worker.new(*queues))
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
- exit(0) if worker.work
25
- end
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
- desc "Start a Rabbit Jobs scheduler"
28
- task :scheduler => :environment do
29
- scheduler = initialize_rj_daemon(RabbitJobs::Scheduler.new)
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
- scheduler.work
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
- # Preload app files if this is Rails
35
- task :environment do
36
- if defined?(Rails) && Rails.respond_to?(:application)
37
- # Rails 3
38
- # Rails.application.eager_load!
39
- Rails.application.require_environment!
40
- elsif defined?(Rails::Initializer)
41
- # Rails 2.3
42
- $rails_rake_task = false
43
- Rails::Initializer.run :load_application_classes
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