rabbit_jobs 0.7.8 → 0.7.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,4 @@
1
1
  # -*- encoding : utf-8 -*-
2
- require 'bunny'
3
- require 'uri'
4
2
 
5
3
  module RabbitJobs
6
4
  class AmqpHelper
@@ -8,7 +6,7 @@ module RabbitJobs
8
6
  class << self
9
7
 
10
8
  def prepare_connection
11
- conn = Bunny.new(RJ.config.server, :heartbeat_interval => 5)
9
+ conn = Bunny.new(RabbitJobs.config.server, :heartbeat_interval => 5)
12
10
  conn.start unless conn.connected? || conn.connecting?
13
11
  conn
14
12
  end
@@ -1,7 +1,4 @@
1
1
  # -*- encoding : utf-8 -*-
2
- require 'yaml'
3
- require 'uri'
4
-
5
2
  module RabbitJobs
6
3
 
7
4
  extend self
@@ -28,7 +25,7 @@ module RabbitJobs
28
25
 
29
26
  unless @@configuration
30
27
  self.configure do |c|
31
- c.prefix 'rabbit_jobs'
28
+ c.server "amqp://localhost"
32
29
  end
33
30
  end
34
31
 
@@ -63,8 +60,7 @@ module RabbitJobs
63
60
  def initialize
64
61
  @data = {
65
62
  error_log: true,
66
- server: 'amqp://localhost/',
67
- prefix: 'rabbit_jobs',
63
+ server: 'amqp://localhost',
68
64
  queues: {}
69
65
  }
70
66
  end
@@ -102,15 +98,6 @@ module RabbitJobs
102
98
  @data[:server]
103
99
  end
104
100
 
105
- def prefix(value = nil)
106
- if value
107
- raise ArgumentError unless value.is_a?(String) && value != ""
108
- @data[:prefix] = value.downcase
109
- else
110
- @data[:prefix]
111
- end
112
- end
113
-
114
101
  def queue(name, params = {})
115
102
  raise ArgumentError.new("name is #{name.inspect}") unless name && name.is_a?(String) && name != ""
116
103
  raise ArgumentError.new("params is #{params.inspect}") unless params && params.is_a?(Hash)
@@ -128,11 +115,6 @@ module RabbitJobs
128
115
  @data[:queues].keys
129
116
  end
130
117
 
131
- def queue_name(routing_key)
132
- routing_key = routing_key.to_sym
133
- @data[:queues][routing_key][:ignore_prefix] ? routing_key : [@data[:prefix], routing_key].compact.join('#')
134
- end
135
-
136
118
  def load_file(filename)
137
119
  load_yaml(File.read(filename))
138
120
  end
@@ -142,19 +124,23 @@ module RabbitJobs
142
124
  end
143
125
 
144
126
  def convert_yaml_config(yaml)
145
- if yaml['rabbit_jobs']
146
- convert_yaml_config(yaml['rabbit_jobs'])
147
- elsif defined?(Rails) && yaml[Rails.env.to_s]
148
- convert_yaml_config(yaml[Rails.env.to_s])
149
- else
150
- @data = {prefix: nil, queues: {}}
151
- %w(server prefix mail_errors_to mail_errors_from).each do |m|
152
- self.send(m, yaml[m])
153
- end
154
- yaml['queues'].each do |name, params|
155
- queue name, symbolize_keys!(params) || {}
156
- end
127
+ yaml = parse_environment(yaml)
128
+
129
+ @data = {queues: {}}
130
+ %w(server mail_errors_to mail_errors_from).each do |m|
131
+ self.send(m, yaml[m])
132
+ end
133
+ yaml['queues'].each do |name, params|
134
+ queue name, symbolize_keys!(params) || {}
157
135
  end
158
136
  end
137
+
138
+ private
139
+
140
+ def parse_environment(yaml)
141
+ yaml['rabbit_jobs'] ||
142
+ (defined?(Rails) && yaml[Rails.env.to_s]) ||
143
+ yaml
144
+ end
159
145
  end
160
146
  end
@@ -3,9 +3,10 @@ module RabbitJobs
3
3
  module Consumer
4
4
  class JobConsumer
5
5
  def process_message(delivery_info, properties, payload)
6
- job = RJ::Job.parse(payload)
6
+ job, *error_args = RJ::Job.parse(payload)
7
7
 
8
8
  if job.is_a?(Symbol)
9
+ report_error(job, *error_args)
9
10
  # case @job
10
11
  # when :not_found
11
12
  # when :parsing_error
@@ -23,5 +24,25 @@ module RabbitJobs
23
24
  true
24
25
  end
25
26
  end
27
+
28
+ def log_error(msg)
29
+ RJ.logger.error msg
30
+ end
31
+
32
+ def report_error(error_type, *args)
33
+ case error_type
34
+ when :not_found
35
+ log_error "Cannot find job class '#{args.first}'"
36
+ when :parsing_error
37
+ log_error "Cannot initialize job. Json parsing error."
38
+ log_error "Data received: #{args.first.inspect}"
39
+ when :error
40
+ ex, payload = args
41
+ log_error "Cannot initialize job."
42
+ log_error ex.message
43
+ log_error _cleanup_backtrace(ex.backtrace).join("\n")
44
+ log_error "Data received: #{payload.inspect}"
45
+ end
46
+ end
26
47
  end
27
48
  end
@@ -1,35 +1,47 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module RabbitJobs
3
3
  class ErrorMailer
4
- def self.enabled?
5
- !!(
6
- defined?(ActionMailer) && \
7
- RabbitJobs.config.mail_errors_from && !RabbitJobs.config.mail_errors_from.empty? && \
8
- RabbitJobs.config.mail_errors_to && !RabbitJobs.config.mail_errors_to.empty?
9
- )
10
- end
4
+ class << self
5
+ def enabled?
6
+ defined?(ActionMailer) && config_present?
7
+ end
8
+
9
+ def send_letter(subject, body)
10
+ letter = ActionMailer::Base.mail
11
+ letter.from = RabbitJobs.config.mail_errors_from
12
+ letter.to = RabbitJobs.config.mail_errors_to
13
+ letter.subject = subject
14
+ letter.body = body
15
+
16
+ letter.deliver
17
+ end
11
18
 
12
- def self.report_error(job, error = $!)
13
- return unless enabled?
19
+ def report_error(job, error = $!)
20
+ return unless enabled?
14
21
 
15
- params ||= []
22
+ begin
23
+ subject, text = build_error_message(job, error)
24
+ send_letter(subject, text)
25
+ rescue
26
+ RabbitJobs.logger.error [$!.message, $!.backtrace].flatten.join("\n")
27
+ end
28
+ end
16
29
 
17
- params_str = job.params == [] ? '' : job.params.map { |p| p.inspect }.join(', ')
18
- subject = "RJ:Worker: #{error.class} on #{job.class}"
19
- text = "\n#{job.class}.perform(#{params_str})\n"
20
- text += "\n#{error.inspect}\n"
21
- text += "\nBacktrace:\n#{error.backtrace.join("\n")}" if error.backtrace
30
+ private
22
31
 
23
- letter = ActionMailer::Base.mail
24
- letter.from = RabbitJobs.config.mail_errors_from
25
- letter.to = RabbitJobs.config.mail_errors_to
26
- letter.subject = subject
27
- letter.body = text
32
+ def build_error_message(job, error)
33
+ params = job.params || []
28
34
 
29
- begin
30
- letter.deliver
31
- rescue
32
- RJ.logger.error [$!.message, $!.backtrace].flatten.join("\n")
35
+ params_str = params.map { |p| p.inspect }.join(', ')
36
+ subject = "RJ:Worker: #{error.class} on #{job.class}"
37
+ text = "\n#{job.class}.perform(#{params_str})\n"
38
+ text += "\n#{error.inspect}\n"
39
+ text += "\nBacktrace:\n#{error.backtrace.join("\n")}" if error.backtrace
40
+ end
41
+
42
+ def config_present?
43
+ config = RabbitJobs.config
44
+ config.mail_errors_from.present? && config.mail_errors_to.present?
33
45
  end
34
46
  end
35
47
  end
@@ -1,130 +1,138 @@
1
1
  # -*- encoding : utf-8 -*-
2
- require 'json'
3
- require 'digest/md5'
2
+ module RabbitJobs
3
+ module Job
4
+ include RabbitJobs::Helpers
5
+ extend self
4
6
 
5
- module RabbitJobs::Job
6
- extend RabbitJobs::Helpers
7
- extend self
7
+ def self.included(base)
8
+ base.extend (ClassMethods)
8
9
 
9
- def self.included(base)
10
- base.extend (ClassMethods)
10
+ def initialize(*perform_params)
11
+ self.params = perform_params
12
+ self.opts = {}
13
+ end
11
14
 
12
- def initialize(*perform_params)
13
- self.params = perform_params
14
- self.opts = {}
15
- end
15
+ attr_accessor :params, :opts
16
+
17
+ def run_perform
18
+ begin
19
+ start_time = Time.now
20
+ RabbitJobs.logger.info "Started to perform #{self.to_ruby_string}"
21
+ self.class.perform(*params)
22
+ execution_time = Time.now - start_time
23
+ RabbitJobs.logger.info " Job completed #{self.to_ruby_string} in #{execution_time} seconds."
24
+ rescue
25
+ log_job_error($!)
26
+ run_on_error_hooks($!)
27
+ end
28
+ end
16
29
 
17
- attr_accessor :params, :opts
30
+ def run_on_error_hooks(error)
31
+ if self.class.rj_on_error_hooks
32
+ self.class.rj_on_error_hooks.each do |proc_or_symbol|
33
+ proc = proc_or_symbol
34
+ if proc_or_symbol.is_a?(Symbol)
35
+ proc = self.method(proc_or_symbol)
36
+ end
37
+
38
+ case proc.arity
39
+ when 0
40
+ proc.call()
41
+ when 1
42
+ proc.call(error)
43
+ else
44
+ proc.call(error, *params)
45
+ end
46
+ end
47
+ end
48
+ end
18
49
 
19
- def run_perform
20
- begin
21
- start_time = Time.now
22
- RJ.logger.info "Started to perform #{self.to_ruby_string}"
23
- self.class.perform(*params)
24
- execution_time = Time.now - start_time
25
- RJ.logger.info " Job completed #{self.to_ruby_string} in #{execution_time} seconds."
26
- rescue
27
- RJ.logger.warn $!.message
28
- RJ.logger.warn(self.to_ruby_string)
29
- RJ.logger.warn _cleanup_backtrace($!.backtrace).join("\n")
30
- run_on_error_hooks($!)
31
- RabbitJobs::ErrorMailer.report_error(self, $!)
50
+ def payload
51
+ {'class' => self.class.to_s, 'opts' => (self.opts || {}), 'params' => params}.to_json
32
52
  end
33
- end
34
53
 
35
- def run_on_error_hooks(error)
36
- if self.class.rj_on_error_hooks
37
- self.class.rj_on_error_hooks.each do |proc_or_symbol|
38
- proc = proc_or_symbol
39
- if proc_or_symbol.is_a?(Symbol)
40
- proc = self.method(proc_or_symbol)
41
- end
54
+ def expires_in
55
+ self.class.rj_expires_in
56
+ end
42
57
 
43
- case proc.arity
44
- when 0
45
- proc.call()
46
- when 1
47
- proc.call(error)
48
- else
49
- proc.call(error, *params)
50
- end
51
- end
58
+ def expires?
59
+ self.expires_in && self.expires_in > 0
52
60
  end
53
- end
54
61
 
55
- def payload
56
- {'class' => self.class.to_s, 'opts' => (self.opts || {}), 'params' => params}.to_json
57
- end
62
+ def expires_at_expired?
63
+ expires_at = opts['expires_at'].to_i
64
+ return false if expires_at == 0
65
+ Time.now.to_i > expires_at
66
+ end
58
67
 
59
- def expires_in
60
- self.class.rj_expires_in
61
- end
68
+ def expires_in_expired?
69
+ exp_in = self.expires_in.to_i
70
+ created_at = opts['created_at'].to_i
71
+ return false if exp_in == 0 || created_at == 0
62
72
 
63
- def expires?
64
- self.expires_in && self.expires_in > 0
65
- end
73
+ Time.now.to_i > created_at + exp_in
74
+ end
66
75
 
67
- def expired?
68
- if self.opts['expires_at']
69
- Time.now.to_i > opts['expires_at'].to_i
70
- elsif expires? && opts['created_at']
71
- Time.now.to_i > (opts['created_at'].to_i + expires_in.to_i)
72
- else
73
- false
76
+ def expired?
77
+ expires_at_expired? || expires_in_expired?
74
78
  end
75
- end
76
79
 
77
- def to_ruby_string
78
- rs = self.class.name
79
- if params.count > 0
80
- rs << "("
81
- rs << params.map(&:to_s).join(", ")
82
- rs << ")"
80
+ def to_ruby_string
81
+ rs = self.class.name
82
+ rs << params_string
83
+ if opts.count > 0
84
+ rs << ", opts: "
85
+ rs << opts.inspect
86
+ end
83
87
  end
84
- if opts.count > 0
85
- rs << ", opts: "
86
- rs << opts.inspect
88
+
89
+ def params_string
90
+ if params.count > 0
91
+ "(#{params.map(&:to_s).join(", ")})"
92
+ else
93
+ ""
94
+ end
95
+ end
96
+
97
+ def log_job_error(error)
98
+ RabbitJobs.logger.warn error.message
99
+ RabbitJobs.logger.warn(self.to_ruby_string)
100
+ RabbitJobs.logger.warn _cleanup_backtrace(error.backtrace).join("\n")
101
+ RabbitJobs::ErrorMailer.report_error(self, error) rescue nil
87
102
  end
88
103
  end
89
- end
90
104
 
91
- module ClassMethods
92
- attr_accessor :rj_expires_in, :rj_on_error_hooks
105
+ module ClassMethods
106
+ attr_accessor :rj_expires_in, :rj_on_error_hooks
93
107
 
94
- # DSL method for jobs
95
- def expires_in(seconds)
96
- @rj_expires_in = seconds.to_i
97
- end
108
+ # DSL method for jobs
109
+ def expires_in(seconds)
110
+ @rj_expires_in = seconds.to_i
111
+ end
98
112
 
99
- def on_error(*hooks)
100
- hooks.each do |proc_or_symbol|
101
- raise ArgumentError unless proc_or_symbol && ( proc_or_symbol.is_a?(Proc) || proc_or_symbol.is_a?(Symbol) )
102
- @rj_on_error_hooks ||= []
103
- @rj_on_error_hooks << proc_or_symbol
113
+ def on_error(*hooks)
114
+ hooks.each do |proc_or_symbol|
115
+ raise ArgumentError unless proc_or_symbol && ( proc_or_symbol.is_a?(Proc) || proc_or_symbol.is_a?(Symbol) )
116
+ @rj_on_error_hooks ||= []
117
+ @rj_on_error_hooks << proc_or_symbol
118
+ end
104
119
  end
105
120
  end
106
- end
107
121
 
108
- def self.parse(payload)
109
- begin
110
- encoded = JSON.parse(payload)
111
- job_klass = constantize(encoded['class'])
112
- job = job_klass.new(*encoded['params'])
113
- job.opts = encoded['opts']
114
- job
115
- rescue NameError
116
- RJ.logger.error "Cannot find job class '#{encoded['class']}'"
117
- :not_found
118
- rescue JSON::ParserError
119
- RJ.logger.error "Cannot initialize job. Json parsing error."
120
- RJ.logger.error "Data received: #{payload.inspect}"
121
- :parsing_error
122
- rescue
123
- RJ.logger.warn "Cannot initialize job."
124
- RJ.logger.warn $!.message
125
- RJ.logger.warn _cleanup_backtrace($!.backtrace).join("\n")
126
- RJ.logger.warn "Data received: #{payload.inspect}"
127
- :error
122
+ def self.parse(payload)
123
+ begin
124
+ encoded = JSON.parse(payload)
125
+ job_klass = constantize(encoded['class'])
126
+ job = job_klass.new(*encoded['params'])
127
+ job.opts = encoded['opts']
128
+ job
129
+ rescue NameError
130
+ [:not_found, encoded['class']]
131
+ rescue JSON::ParserError
132
+ [:parsing_error, payload]
133
+ rescue
134
+ [:error, $!, payload]
135
+ end
128
136
  end
129
137
  end
130
- end
138
+ end
@@ -0,0 +1,40 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module RabbitJobs
3
+ module MainLoop
4
+ def shutdown
5
+ @shutdown = true
6
+ end
7
+
8
+ def shutdown!
9
+ shutdown
10
+ end
11
+
12
+ def main_loop(time)
13
+ while true
14
+ sleep 1
15
+ if time > 0
16
+ time -= 1
17
+ if time == 0
18
+ shutdown
19
+ end
20
+ end
21
+
22
+ if @shutdown
23
+ RabbitJobs.logger.info "Stopping."
24
+ yield if block_given?
25
+ return true
26
+ end
27
+ end
28
+ end
29
+
30
+ def log_daemon_error(error)
31
+ if RabbitJobs.logger
32
+ begin
33
+ RabbitJobs.logger.error [error.message, error.backtrace].flatten.join("\n")
34
+ ensure
35
+ abort(error.message)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,11 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
- require 'json'
4
- require 'bunny'
5
- require 'uri'
6
- require 'active_support'
7
- require 'active_support/core_ext/module'
8
-
9
3
  module RabbitJobs
10
4
  module Publisher
11
5
  extend self
@@ -17,8 +11,9 @@ module RabbitJobs
17
11
  end
18
12
 
19
13
  def publish_to(routing_key, klass, *params)
20
- raise ArgumentError.new("klass=#{klass.inspect}") unless klass && (klass.is_a?(Class) || klass.is_a?(String))
21
- 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_sym]
14
+ raise ArgumentError.new("klass=#{klass.inspect}") unless klass.is_a?(Class) || klass.is_a?(String)
15
+ routing_key = routing_key.to_sym unless routing_key.is_a?(Symbol)
16
+ raise ArgumentError.new("routing_key=#{routing_key}") unless RabbitJobs.config[:queues][routing_key]
22
17
 
23
18
  payload = {
24
19
  'class' => klass.to_s,
@@ -26,35 +21,43 @@ module RabbitJobs
26
21
  'params' => params
27
22
  }.to_json
28
23
 
29
- direct_publish_to(RJ.config.queue_name(routing_key.to_sym), payload)
24
+ direct_publish_to(routing_key, payload)
30
25
  end
31
26
 
32
27
  def direct_publish_to(routing_key, payload, ex = {})
33
28
  check_connection
34
29
  begin
35
- exchange = get_exchange(ex)
36
- exchange.publish(payload, Configuration::DEFAULT_MESSAGE_PARAMS.merge({key: routing_key.to_sym}))
37
- # channel.wait_for_confirms
30
+ exchange_name = ex.delete(:name).to_s
31
+ exchange_opts = Configuration::DEFAULT_MESSAGE_PARAMS.merge(ex || {})
32
+ connection.default_channel.basic_publish(payload, exchange_name, routing_key, exchange_opts)
38
33
  rescue
39
- RJ.logger.warn $!.message
40
- RJ.logger.warn $!.backtrace.join("\n")
34
+ RabbitJobs.logger.warn $!.message
35
+ RabbitJobs.logger.warn $!.backtrace.join("\n")
41
36
  raise $!
42
37
  end
43
38
 
44
39
  true
45
40
  end
46
41
 
42
+ def queue_status(routing_key)
43
+ raise ArgumentError unless routing_key.present?
44
+
45
+ routing_key = routing_key.to_sym
46
+ queue_declare_ok = connection.default_channel.queue_declare(routing_key, RabbitJobs.config[:queues][routing_key].merge(passive: true))
47
+ {
48
+ message_count: queue_declare_ok.message_count,
49
+ consumer_count: queue_declare_ok.consumer_count
50
+ }
51
+ end
52
+
47
53
  def purge_queue(*routing_keys)
48
- raise ArgumentError unless routing_keys && routing_keys.count > 0
54
+ raise ArgumentError unless routing_keys.present?
49
55
 
50
56
  messages_count = 0
51
- count = routing_keys.count
52
57
 
53
58
  routing_keys.map(&:to_sym).each do |routing_key|
54
- queue_name = RJ.config.queue_name(routing_key)
55
- queue = connection.queue(queue_name, RJ.config[:queues][routing_key])
56
- messages_count += queue.status[:message_count]
57
- queue.purge
59
+ messages_count += queue_status(routing_key)[:message_count].to_i
60
+ connection.default_channel.queue_purge(routing_key)
58
61
  end
59
62
 
60
63
  messages_count
@@ -68,29 +71,13 @@ module RabbitJobs
68
71
 
69
72
  def connection
70
73
  unless settings[:connection]
71
- settings[:connection] = Bunny.new(RJ.config.server, :heartbeat_interval => 5)
74
+ settings[:connection] = Bunny.new(RabbitJobs.config.server, :heartbeat_interval => 5)
72
75
  settings[:connection].start
73
76
  # settings[:channel].confirm_select
74
77
  end
75
78
  settings[:connection]
76
79
  end
77
80
 
78
- def exchanges
79
- settings[:exchanges] ||= {}
80
- end
81
-
82
- def get_exchange(ex = {})
83
- ex = {name: ex} if ex.is_a?(String)
84
- raise ArgumentError.new("Need to pass exchange name") if ex.size > 0 && ex[:name].to_s.empty?
85
-
86
- if ex.size > 0
87
- exchange_opts = Configuration::DEFAULT_EXCHANGE_PARAMS.merge(ex[:params] || {}).merge({type: (ex[:type] || :direct)})
88
- exchanges[ex[:name]] ||= connection.default_channel.exchange(ex[:name].to_s, exchange_opts)
89
- else
90
- exchanges[ex[:name]] ||= connection.default_channel.default_exchange
91
- end
92
- end
93
-
94
81
  def check_connection
95
82
  raise unless connection.connected?
96
83
  end
@@ -1,19 +1,20 @@
1
1
  # -*- encoding : utf-8 -*-
2
-
3
- require 'rufus/scheduler'
4
- require 'thwait'
5
- require 'yaml'
6
-
7
2
  module RabbitJobs
8
3
  class Scheduler
4
+ include MainLoop
9
5
 
10
- attr_accessor :schedule, :process_name
6
+ attr_reader :schedule, :process_name
7
+ attr_writer :process_name
8
+
9
+ def schedule=(value)
10
+ @schedule = HashWithIndifferentAccess.new(value)
11
+ end
11
12
 
12
13
  def load_default_schedule
13
14
  if defined?(Rails)
14
15
  file = Rails.root.join('config/schedule.yml')
15
16
  if file.file?
16
- @schedule = YAML.load_file(file)
17
+ @schedule = HashWithIndifferentAccess.new(YAML.load_file(file))
17
18
  end
18
19
  end
19
20
  end
@@ -31,21 +32,7 @@ module RabbitJobs
31
32
  # job should be scheduled regardless of what ENV['RAILS_ENV'] is set
32
33
  # to.
33
34
  if config['rails_env'].nil? || rails_env_matches?(config)
34
- interval_defined = false
35
- interval_types = %w{cron every}
36
- interval_types.each do |interval_type|
37
- if !config[interval_type].nil? && config[interval_type].length > 0
38
- RJ.logger.info "queueing #{config['class']} (#{name})"
39
- rufus_scheduler.send(interval_type, config[interval_type], blocking: true) do
40
- publish_from_config(config)
41
- end
42
- interval_defined = true
43
- break
44
- end
45
- end
46
- unless interval_defined
47
- RJ.logger.warn "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
48
- end
35
+ setup_job_schedule(name, config)
49
36
  end
50
37
  end
51
38
  end
@@ -57,16 +44,15 @@ module RabbitJobs
57
44
 
58
45
  # Publish a job based on a config hash
59
46
  def publish_from_config(config)
60
- args = config['args'] || config[:args] || []
61
- klass_name = config['class'] || config[:class]
62
- params = args.is_a?(Hash) ? [args] : Array(args)
63
- queue = config['queue'] || config[:queue] || RJ.config.routing_keys.first
47
+ args = config[:args] || []
48
+ klass_name = config[:class]
49
+ params = [args].flatten
64
50
 
65
- RJ.logger.info "publishing #{config} at #{Time.now}"
66
- RJ.publish_to(queue, klass_name, *params)
51
+ RabbitJobs.logger.info "publishing #{config} at #{Time.now}"
52
+ RabbitJobs.publish_to(config[:queue], klass_name, *params)
67
53
  rescue
68
- RJ.logger.warn "Failed to publish #{klass_name}:\n #{$!}\n params = #{params.inspect}"
69
- RJ.logger.warn $!.inspect
54
+ RabbitJobs.logger.warn "Failed to publish #{klass_name}:\n #{$!}\n params = #{params.inspect}"
55
+ RabbitJobs.logger.warn $!.inspect
70
56
  end
71
57
 
72
58
  def rufus_scheduler
@@ -88,46 +74,18 @@ module RabbitJobs
88
74
 
89
75
  $0 = self.process_name || "rj_scheduler"
90
76
 
91
- RJ.logger.info "Started."
77
+ RabbitJobs.logger.info "Started."
92
78
 
93
- processed_count = 0
94
79
  load_schedule!
95
80
 
96
- while true
97
- sleep 1
98
- if time > 0
99
- time -= 1
100
- if time == 0
101
- shutdown
102
- end
103
- end
104
-
105
- if @shutdown
106
- RJ.logger.info "Processed jobs: #{processed_count}."
107
- RJ.logger.info "Stopped."
108
-
109
- return true
110
- end
111
- end
112
- rescue => e
113
- error = $!
114
- if RJ.logger
115
- begin
116
- RJ.logger.error [error.message, error.backtrace].flatten.join("\n")
117
- ensure
118
- abort(error.message)
119
- end
120
- end
81
+ return main_loop(time)
82
+ rescue
83
+ log_daemon_error($!)
121
84
  end
122
85
 
123
86
  true
124
87
  end
125
88
 
126
- def shutdown
127
- RJ.logger.info "Stopping..."
128
- @shutdown = true
129
- end
130
-
131
89
  def startup
132
90
  # Fix buffering so we can `rake rj:work > resque.log` and
133
91
  # get output from the child in there.
@@ -141,8 +99,20 @@ module RabbitJobs
141
99
  true
142
100
  end
143
101
 
144
- def shutdown!
145
- shutdown
102
+ def setup_job_schedule(name, config)
103
+ interval_defined = false
104
+ %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
111
+ end
112
+ end
113
+ unless interval_defined
114
+ RabbitJobs.logger.warn "no #{interval_types.join(' / ')} found for #{config['class']} (#{name}) - skipping"
115
+ end
146
116
  end
147
117
  end
148
118
  end
@@ -1,6 +1,4 @@
1
1
  require 'rabbit_jobs'
2
- require 'logger'
3
- require 'rake'
4
2
 
5
3
  def rails_env
6
4
  $my_rails_env ||= defined?(Rails) ? Rails.env : (ENV['RAILS_ENV'] || 'development')
@@ -1,5 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
3
  module RabbitJobs
4
- VERSION = "0.7.8"
4
+ VERSION = "0.7.9"
5
5
  end
@@ -2,12 +2,14 @@
2
2
 
3
3
  module RabbitJobs
4
4
  class Worker
5
+ include MainLoop
6
+
5
7
  attr_accessor :process_name
6
8
  attr_reader :consumer
7
9
 
8
- def consumer=(consumer)
9
- raise ArgumentError.new("consumer=#{consumer.inspect}") unless consumer.respond_to?(:process_message)
10
- @consumer = consumer
10
+ def consumer=(value)
11
+ raise ArgumentError.new("value=#{value.inspect}") unless value.respond_to?(:process_message)
12
+ @consumer = value
11
13
  end
12
14
 
13
15
  def amqp_connection
@@ -20,12 +22,8 @@ module RabbitJobs
20
22
  Thread.current[:rj_worker_connection] = nil
21
23
  end
22
24
 
23
- def queue_name(routing_key)
24
- RJ.config.queue_name(routing_key)
25
- end
26
-
27
25
  def queue_params(routing_key)
28
- RJ.config[:queues][routing_key]
26
+ RJ.config[:queues][routing_key.to_sym]
29
27
  end
30
28
 
31
29
  # Workers should be initialized with an array of string queue
@@ -58,79 +56,28 @@ module RabbitJobs
58
56
 
59
57
  $0 = self.process_name || "rj_worker (#{queues.join(', ')})"
60
58
 
61
- processed_count = 0
59
+ @processed_count = 0
62
60
 
63
61
  begin
64
- # amqp_channel.prefetch(1)
65
-
66
62
  amqp_channel = amqp_connection.create_channel
63
+ amqp_channel.prefetch(1)
67
64
 
68
- queue_objects = []
69
65
  queues.each do |routing_key|
70
- RJ.logger.info "Subscribing to #{queue_name(routing_key)}"
71
-
72
- routing_key = routing_key.to_sym
73
- queue = amqp_channel.queue(queue_name(routing_key), queue_params(routing_key))
74
- queue_objects << queue
75
- explicit_ack = !!queue_params(routing_key)[:ack]
76
-
77
- queue.subscribe(ack: explicit_ack) do |delivery_info, properties, payload|
78
- if RJ.run_before_process_message_callbacks
79
- begin
80
- @consumer.process_message(delivery_info, properties, payload)
81
- processed_count += 1
82
- rescue
83
- RJ.logger.warn "process_message failed. payload: #{payload.inspect}"
84
- RJ.logger.warn $!.inspect
85
- $!.backtrace.each {|l| RJ.logger.warn l}
86
- end
87
- amqp_channel.ack(delivery_info.delivery_tag, false) if explicit_ack
88
- else
89
- RJ.logger.warn "before_process_message hook failed, requeuing payload: #{payload.inspect}"
90
- amqp_channel.nack(delivery_info.delivery_tag, true) if explicit_ack
91
- end
92
-
93
- if @shutdown
94
- queue_objects.each {|q| q.unsubscribe}
95
- end
96
- end
66
+ consume_queue(amqp_channel, routing_key)
97
67
  end
98
68
 
99
69
  RJ.logger.info "Started."
100
70
 
101
- while true
102
- sleep 1
103
- if time > 0
104
- time -= 1
105
- if time == 0
106
- shutdown
107
- end
108
- end
109
-
110
- if @shutdown
111
- RJ.logger.info "Processed jobs: #{processed_count}."
112
- RJ.logger.info "Stopped."
113
- return true
114
- end
115
- end
71
+ return main_loop(time) {
72
+ RJ.logger.info "Processed jobs: #{@processed_count}."
73
+ }
116
74
  rescue
117
- error = $!
118
- if RJ.logger
119
- begin
120
- RJ.logger.error [error.message, error.backtrace].flatten.join("\n")
121
- ensure
122
- abort(error.message)
123
- end
124
- end
75
+ log_daemon_error($!)
125
76
  end
126
77
 
127
78
  true
128
79
  end
129
80
 
130
- def shutdown
131
- @shutdown = true
132
- end
133
-
134
81
  def startup
135
82
  count = RJ._run_after_fork_callbacks
136
83
 
@@ -144,8 +91,38 @@ module RabbitJobs
144
91
  true
145
92
  end
146
93
 
147
- def shutdown!
148
- shutdown
94
+ private
95
+
96
+ def consume_message(delivery_info, properties, payload)
97
+ if RJ.run_before_process_message_callbacks
98
+ begin
99
+ @consumer.process_message(delivery_info, properties, payload)
100
+ @processed_count += 1
101
+ rescue
102
+ RJ.logger.warn "process_message failed. payload: #{payload.inspect}"
103
+ RJ.logger.warn $!.inspect
104
+ $!.backtrace.each {|l| RJ.logger.warn l}
105
+ end
106
+ true
107
+ else
108
+ RJ.logger.warn "before_process_message hook failed, requeuing payload: #{payload.inspect}"
109
+ false
110
+ end
111
+ end
112
+
113
+ def consume_queue(amqp_channel, routing_key)
114
+ RJ.logger.info "Subscribing to #{routing_key}"
115
+ routing_key = routing_key.to_sym
116
+ queue = amqp_channel.queue(routing_key, queue_params(routing_key))
117
+ explicit_ack = !!queue_params(routing_key)[:ack]
118
+
119
+ queue.subscribe(ack: explicit_ack) do |delivery_info, properties, payload|
120
+ if consume_message(delivery_info, properties, payload)
121
+ amqp_channel.ack(delivery_info.delivery_tag, false) if explicit_ack
122
+ else
123
+ amqp_channel.nack(delivery_info.delivery_tag, true) if explicit_ack
124
+ end
125
+ end
149
126
  end
150
127
  end
151
128
  end
data/lib/rabbit_jobs.rb CHANGED
@@ -1,6 +1,15 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  require 'logger'
3
3
  require 'rake'
4
+ require 'json'
5
+ require 'digest/md5'
6
+ require 'bunny'
7
+ require 'uri'
8
+ require 'rufus/scheduler'
9
+ require 'thwait'
10
+ require 'yaml'
11
+ require 'active_support'
12
+ require 'active_support/core_ext'
4
13
 
5
14
  require 'rabbit_jobs/version'
6
15
 
@@ -12,6 +21,7 @@ require 'rabbit_jobs/error_mailer'
12
21
  require 'rabbit_jobs/consumer/job_consumer'
13
22
  require 'rabbit_jobs/job'
14
23
  require 'rabbit_jobs/publisher'
24
+ require 'rabbit_jobs/main_loop'
15
25
  require 'rabbit_jobs/worker'
16
26
  require 'rabbit_jobs/scheduler'
17
27
  require 'rabbit_jobs/tasks'
@@ -20,15 +30,15 @@ module RabbitJobs
20
30
  extend self
21
31
 
22
32
  def publish_to(routing_key, klass, *params)
23
- RJ::Publisher.publish_to(routing_key, klass, *params)
33
+ Publisher.publish_to(routing_key, klass, *params)
24
34
  end
25
35
 
26
36
  def direct_publish_to(routing_key, payload, ex = {})
27
- RJ::Publisher.direct_publish_to(routing_key, payload, ex)
37
+ Publisher.direct_publish_to(routing_key, payload, ex)
28
38
  end
29
39
 
30
40
  def purge_queue(*routing_keys)
31
- RJ::Publisher.purge_queue(*routing_keys)
41
+ Publisher.purge_queue(*routing_keys)
32
42
  end
33
43
 
34
44
  attr_writer :logger
@@ -1,6 +1,5 @@
1
1
  rabbit_jobs:
2
2
  server: amqp://example.com/vhost
3
- prefix: 'rabbit_jobs'
4
3
  queues:
5
4
  durable_queue:
6
5
  durable: true
@@ -34,7 +34,7 @@ class JobWithPublish
34
34
  def self.perform(param = 0)
35
35
  if param < 5
36
36
  puts "publishing job #{param}"
37
- RJ.publish JobWithPublish, param + 1
37
+ RJ.publish_to :rspec_durable_queue, JobWithPublish, param + 1
38
38
  else
39
39
  puts "processing job #{param}"
40
40
  end
@@ -64,4 +64,9 @@ class JobWithArgsArray
64
64
  def perform(first_param, *other_args)
65
65
  puts "#{self.class.name}.perform called with first_param: #{first_param.inspect} and other_args: #{other_args.inspect}"
66
66
  end
67
+ end
68
+
69
+ class TestConsumer
70
+ def process_message *args
71
+ end
67
72
  end
@@ -43,7 +43,7 @@ describe RabbitJobs::Publisher do
43
43
  end
44
44
 
45
45
  it 'should publish 1000 messages in one second' do
46
- count = 100
46
+ count = 1000
47
47
  published = 0
48
48
  time = Benchmark.measure {
49
49
  count.times {
@@ -51,7 +51,7 @@ describe RabbitJobs::Publisher do
51
51
  }
52
52
  # sleep 0.1
53
53
  removed = RJ.purge_queue(:rspec_queue, :rspec_queue2, :rspec_queue3)
54
- removed.should == 100
54
+ removed.should == 1000
55
55
  }
56
56
  puts time
57
57
  end
@@ -4,39 +4,43 @@ require 'spec_helper'
4
4
  describe RabbitJobs::Worker do
5
5
  it 'should listen for messages' do
6
6
  RabbitJobs.configure do |c|
7
- c.prefix 'test_durable'
7
+ c.server 'amqp://localhost/rj'
8
8
  c.queue 'rspec_durable_queue', auto_delete: false, durable: true, ack: true
9
9
  end
10
10
 
11
11
  RJ::Publisher.purge_queue('rspec_durable_queue')
12
12
  count = 5
13
13
  5.times {
14
- RabbitJobs.publish(PrintTimeJob, Time.now)
14
+ RabbitJobs.publish_to(:rspec_durable_queue, PrintTimeJob, Time.now)
15
15
  }
16
16
 
17
17
  Timecop.freeze(Time.now - 4600) {
18
- 5.times { RabbitJobs.publish(JobWithExpire) }
18
+ 5.times { RabbitJobs.publish_to(:rspec_durable_queue, JobWithExpire) }
19
19
  }
20
20
 
21
- RabbitJobs.publish(JobWithErrorHook)
21
+ RabbitJobs.publish_to(:rspec_durable_queue, JobWithErrorHook)
22
22
 
23
23
  worker = RabbitJobs::Worker.new
24
+ RJ.logger.level = Logger::FATAL
24
25
 
26
+ mock(PrintTimeJob).perform(anything).times(5)
27
+ mock(JobWithErrorHook).perform
28
+ dont_allow(JobWithExpire).perform
25
29
  worker.work(1) # work for 1 second
26
30
  RJ::Publisher.purge_queue('rspec_durable_queue')
27
31
  end
28
32
 
29
33
  it 'should allow to publish jobs from worker' do
30
34
  RabbitJobs.configure do |c|
31
- c.prefix 'test_durable'
35
+ c.server 'amqp://localhost/rj'
32
36
  c.queue 'rspec_durable_queue', auto_delete: false, durable: true, ack: true
33
37
  end
34
38
 
35
39
  RJ::Publisher.purge_queue('rspec_durable_queue')
36
- RabbitJobs.publish(JobWithPublish)
40
+ RabbitJobs.publish_to(:rspec_durable_queue, JobWithPublish, 1)
37
41
 
38
42
  worker = RabbitJobs::Worker.new
39
-
40
- worker.work(1) # work for 1 second
43
+ # mock(RJ).publish_to(:rspec_durable_queue, JobWithPublish, 5)
44
+ worker.work(3) # work for 1 second
41
45
  end
42
46
  end
data/spec/spec_helper.rb CHANGED
@@ -20,4 +20,12 @@ RSpec.configure do |config|
20
20
  # clear config options
21
21
  RabbitJobs.class_variable_set '@@configuration', nil
22
22
  end
23
+
24
+ if ENV['CC_BUILD_ARTIFACTS']
25
+ # "-c -f p -f h -o #{ENV['CC_BUILD_ARTIFACTS']}/rspec_report.html"
26
+ config.out = File.open "#{ENV['CC_BUILD_ARTIFACTS']}/rspec_report.html", 'w'
27
+ # config.color_enabled = true
28
+ # config.formatter = :progress
29
+ config.formatter = :html
30
+ end
23
31
  end
@@ -8,8 +8,6 @@ describe RabbitJobs::Configuration do
8
8
 
9
9
  c.server "amqp://somehost.lan"
10
10
 
11
- c.prefix 'my_prefix'
12
-
13
11
  c.queue 'durable_queue', durable: true, auto_delete: false, ack: true, arguments: {'x-ha-policy' => 'all'}
14
12
  c.queue 'fast_queue', durable: false, auto_delete: true, ack: false
15
13
  end
@@ -17,7 +15,6 @@ describe RabbitJobs::Configuration do
17
15
  RabbitJobs.config.to_hash.should == {
18
16
  error_log: false,
19
17
  server: "amqp://somehost.lan",
20
- prefix: "my_prefix",
21
18
  queues: {
22
19
  durable_queue: {
23
20
  auto_delete: false,
@@ -40,7 +37,6 @@ describe RabbitJobs::Configuration do
40
37
  RabbitJobs.config.load_file(File.expand_path('../../fixtures/config.yml', __FILE__))
41
38
 
42
39
  RabbitJobs.config.to_hash.should == {
43
- prefix: "rabbit_jobs",
44
40
  server: "amqp://example.com/vhost",
45
41
  queues: {
46
42
  durable_queue: {
@@ -60,19 +56,17 @@ describe RabbitJobs::Configuration do
60
56
  }
61
57
  end
62
58
 
63
- it 'use default values for #server and #prefix' do
59
+ it 'use default value for #server' do
64
60
  RabbitJobs.config.to_hash.should == {
65
61
  error_log: true,
66
- server: "amqp://localhost/",
67
- prefix: "rabbit_jobs",
62
+ server: "amqp://localhost",
68
63
  queues: {}
69
64
  }
70
65
  end
71
66
 
72
67
  it 'returns settings on some methods' do
73
68
  RabbitJobs.config.error_log == true
74
- RabbitJobs.config.server.should == 'amqp://localhost/'
69
+ RabbitJobs.config.server.should == 'amqp://localhost'
75
70
  RabbitJobs.config.routing_keys.should == []
76
- RabbitJobs.config.prefix.should == 'rabbit_jobs'
77
71
  end
78
72
  end
@@ -2,30 +2,25 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  describe RabbitJobs::Worker do
5
- describe 'methods' do
6
- before :each do
7
- @worker = RabbitJobs::Worker.new
8
- end
9
5
 
10
- it '#initialize with default options' do
11
- @worker.queues.should == [:default]
6
+ before(:each) do
7
+ RJ.configure do |c|
8
+ c.server 'amqp://localhost'
9
+ c.queue "default"
12
10
  end
11
+ end
13
12
 
14
- it '#startup should set @shutdown to false' do
15
- @worker.instance_variable_get('@shutdown').should_not == true
16
-
17
- mock(Signal).trap('TERM')
18
- mock(Signal).trap('INT')
19
-
20
- @worker.startup
13
+ let(:worker) { RJ::Worker.new(:default) }
21
14
 
22
- @worker.instance_variable_get('@shutdown').should_not == true
23
- end
15
+ describe '#consumer' do
16
+ it 'validates consumer type' do
17
+ old_consumer = worker.consumer
18
+ lambda { worker.consumer = 123 }.should raise_error
19
+ worker.consumer.should == old_consumer
24
20
 
25
- it '#shutdown should set @shutdown to true' do
26
- @worker.instance_variable_get('@shutdown').should_not == true
27
- @worker.shutdown
28
- @worker.instance_variable_get('@shutdown').should == true
21
+ new_consumer = TestConsumer.new
22
+ lambda { worker.consumer = new_consumer }.should_not raise_error
23
+ worker.consumer.should == new_consumer
29
24
  end
30
25
  end
31
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rabbit_jobs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.8
4
+ version: 0.7.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-22 00:00:00.000000000 Z
12
+ date: 2013-03-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bunny
16
- requirement: &9230940 !ruby/object:Gem::Requirement
16
+ requirement: &17412060 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - =
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.9.0.pre8
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *9230940
24
+ version_requirements: *17412060
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &9229700 !ruby/object:Gem::Requirement
27
+ requirement: &17409580 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *9229700
35
+ version_requirements: *17409580
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rufus-scheduler
38
- requirement: &9349960 !ruby/object:Gem::Requirement
38
+ requirement: &17800900 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '2.0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *9349960
46
+ version_requirements: *17800900
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rails
49
- requirement: &9697740 !ruby/object:Gem::Requirement
49
+ requirement: &17799580 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '3.0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *9697740
57
+ version_requirements: *17799580
58
58
  description: Background jobs on RabbitMQ
59
59
  email:
60
60
  - lazureykis@gmail.com
@@ -81,6 +81,7 @@ files:
81
81
  - lib/rabbit_jobs/error_mailer.rb
82
82
  - lib/rabbit_jobs/helpers.rb
83
83
  - lib/rabbit_jobs/job.rb
84
+ - lib/rabbit_jobs/main_loop.rb
84
85
  - lib/rabbit_jobs/publisher.rb
85
86
  - lib/rabbit_jobs/scheduler.rb
86
87
  - lib/rabbit_jobs/tasks.rb
@@ -114,7 +115,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
114
115
  version: '0'
115
116
  segments:
116
117
  - 0
117
- hash: -1729098954943910884
118
+ hash: -1527066833663791632
118
119
  required_rubygems_version: !ruby/object:Gem::Requirement
119
120
  none: false
120
121
  requirements:
@@ -123,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
124
  version: '0'
124
125
  segments:
125
126
  - 0
126
- hash: -1729098954943910884
127
+ hash: -1527066833663791632
127
128
  requirements: []
128
129
  rubyforge_project:
129
130
  rubygems_version: 1.8.11