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 CHANGED
@@ -12,4 +12,5 @@ group :development do
12
12
  gem 'autotest-fsevent'
13
13
  gem 'autotest-growl'
14
14
  gem 'simplecov', require: false
15
+ gem 'pry'
15
16
  end
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
+ # }
@@ -4,7 +4,7 @@ require 'rabbit_jobs'
4
4
  require 'json'
5
5
 
6
6
  RabbitJobs.configure do |c|
7
- c.url "amqp://localhost/"
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 publish(klass, *params)
22
- RabbitJobs::Publisher.publish(klass, *params)
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 publish_to(routing_key, klass, *params)
26
- RabbitJobs::Publisher.publish_to(routing_key, klass, *params)
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 ||= Logger.new $stdout
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
- if EM.reactor_running?
14
- yield false
15
- else
16
- AMQP.start(RJ.config.url) {
17
- init_auto_recovery
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
- unless AMQP.channel
25
- create_channel
26
- else
27
- create_channel unless AMQP.channel.open?
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 init_auto_recovery
34
- AMQP.connection.on_recovery do |conn, opts|
35
- url = url_from_opts opts
36
- RJ.logger.warn "[network failure] Connection to #{url} established."
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
- AMQP.connection.on_tcp_connection_loss do |conn, opts|
40
- url = url_from_opts opts
41
- RJ.logger.warn "[network failure] Trying to reconnect to #{url}..."
42
- conn.reconnect(false, 2)
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
- end
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
- url: 'amqp://localhost',
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 url(value = nil)
96
- if value
97
- raise ArgumentError unless value.is_a?(String) && value != ""
98
- @data[:url] = value.to_s
99
- @data.delete :connection_options
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 = {url: nil, prefix: nil, queues: {}}
159
- %w(url prefix mail_errors_to mail_errors_from).each do |m|
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