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.
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