beetle 0.1 → 0.2.1
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/README.rdoc +18 -8
- data/beetle.gemspec +37 -121
- data/bin/beetle +9 -0
- data/examples/README.rdoc +0 -2
- data/examples/rpc.rb +3 -2
- data/ext/mkrf_conf.rb +19 -0
- data/lib/beetle.rb +2 -2
- data/lib/beetle/base.rb +1 -8
- data/lib/beetle/client.rb +16 -14
- data/lib/beetle/commands.rb +30 -0
- data/lib/beetle/commands/configuration_client.rb +73 -0
- data/lib/beetle/commands/configuration_server.rb +85 -0
- data/lib/beetle/configuration.rb +70 -7
- data/lib/beetle/deduplication_store.rb +50 -38
- data/lib/beetle/handler.rb +2 -5
- data/lib/beetle/logging.rb +7 -0
- data/lib/beetle/message.rb +11 -13
- data/lib/beetle/publisher.rb +12 -4
- data/lib/beetle/r_c.rb +2 -1
- data/lib/beetle/redis_configuration_client.rb +136 -0
- data/lib/beetle/redis_configuration_server.rb +301 -0
- data/lib/beetle/redis_ext.rb +79 -0
- data/lib/beetle/redis_master_file.rb +35 -0
- data/lib/beetle/redis_server_info.rb +65 -0
- data/lib/beetle/subscriber.rb +4 -1
- data/test/beetle/configuration_test.rb +14 -2
- data/test/beetle/deduplication_store_test.rb +61 -43
- data/test/beetle/message_test.rb +28 -4
- data/test/beetle/publisher_test.rb +17 -3
- data/test/beetle/redis_configuration_client_test.rb +97 -0
- data/test/beetle/redis_configuration_server_test.rb +278 -0
- data/test/beetle/redis_ext_test.rb +71 -0
- data/test/beetle/redis_master_file_test.rb +39 -0
- data/test/test_helper.rb +13 -1
- metadata +162 -69
- data/.gitignore +0 -5
- data/MIT-LICENSE +0 -20
- data/Rakefile +0 -114
- data/TODO +0 -7
- data/etc/redis-master.conf +0 -189
- data/etc/redis-slave.conf +0 -189
- data/examples/redis_failover.rb +0 -65
- data/script/start_rabbit +0 -29
- data/snafu.rb +0 -55
- data/test/beetle.yml +0 -81
- data/test/beetle/bla.rb +0 -0
- data/tmp/master/.gitignore +0 -2
- data/tmp/slave/.gitignore +0 -3
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'daemons'
|
3
|
+
require 'beetle'
|
4
|
+
|
5
|
+
module Beetle
|
6
|
+
module Commands
|
7
|
+
# Command to start a RedisConfigurationServer daemon.
|
8
|
+
#
|
9
|
+
# Usage: beetle configuration_server [options] -- [server options]
|
10
|
+
#
|
11
|
+
# server options:
|
12
|
+
# --redis-servers LIST Required for start command (e.g. 192.168.0.1:6379,192.168.0.2:6379)
|
13
|
+
# --client-ids LIST Clients that have to acknowledge on master switch (e.g. client-id1,client-id2)
|
14
|
+
# --redis-master-file FILE Write redis master server string to FILE
|
15
|
+
# --redis-retry-interval SEC Number of seconds to wait between master checks
|
16
|
+
# --amqp-servers LIST AMQP server list (e.g. 192.168.0.1:5672,192.168.0.2:5672)
|
17
|
+
# --config-file PATH Path to an external yaml config file
|
18
|
+
# --pid-dir DIR Write pid and log to DIR
|
19
|
+
# -v, --verbose
|
20
|
+
# -h, --help Show this message
|
21
|
+
#
|
22
|
+
class ConfigurationServer
|
23
|
+
# parses command line options and starts Beetle::RedisConfigurationServer as a daemon
|
24
|
+
def self.execute
|
25
|
+
command, controller_options, app_options = Daemons::Controller.split_argv(ARGV)
|
26
|
+
|
27
|
+
opts = OptionParser.new
|
28
|
+
opts.banner = "Usage: beetle configuration_server #{command} [options] -- [server options]"
|
29
|
+
opts.separator ""
|
30
|
+
opts.separator "server options:"
|
31
|
+
|
32
|
+
opts.on("--redis-servers LIST", Array, "Required for start command (e.g. 192.168.0.1:6379,192.168.0.2:6379)") do |val|
|
33
|
+
Beetle.config.redis_servers = val.join(",")
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("--client-ids LIST", "Clients that have to acknowledge on master switch (e.g. client-id1,client-id2)") do |val|
|
37
|
+
Beetle.config.redis_configuration_client_ids = val
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on("--redis-master-file FILE", String, "Write redis master server string to FILE") do |val|
|
41
|
+
Beetle.config.redis_server = val
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on("--redis-retry-interval SEC", Integer, "Number of seconds to wait between master checks") do |val|
|
45
|
+
Beetle.config.redis_configuration_master_retry_interval = val
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("--amqp-servers LIST", String, "AMQP server list (e.g. 192.168.0.1:5672,192.168.0.2:5672)") do |val|
|
49
|
+
Beetle.config.servers = val
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on("--config-file PATH", String, "Path to an external yaml config file") do |val|
|
53
|
+
Beetle.config.config_file = val
|
54
|
+
end
|
55
|
+
|
56
|
+
dir_mode = nil
|
57
|
+
dir = nil
|
58
|
+
opts.on("--pid-dir DIR", String, "Write pid and log to DIR") do |val|
|
59
|
+
dir_mode = :normal
|
60
|
+
dir = val
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on("-v", "--verbose") do |val|
|
64
|
+
Beetle.config.logger.level = Logger::DEBUG
|
65
|
+
end
|
66
|
+
|
67
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
68
|
+
puts opts
|
69
|
+
exit
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.parse!(app_options)
|
73
|
+
|
74
|
+
if command =~ /start|run/ && Beetle.config.redis_servers.blank?
|
75
|
+
puts opts
|
76
|
+
exit
|
77
|
+
end
|
78
|
+
|
79
|
+
Daemons.run_proc("redis_configuration_server", :log_output => true, :dir_mode => dir_mode, :dir => dir) do
|
80
|
+
Beetle::RedisConfigurationServer.new.start
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/beetle/configuration.rb
CHANGED
@@ -1,31 +1,94 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
1
3
|
module Beetle
|
2
4
|
class Configuration
|
5
|
+
# system name (used for redis cluster partitioning) (defaults to <tt>system</tt>)
|
6
|
+
attr_accessor :system_name
|
3
7
|
# default logger (defaults to <tt>Logger.new(STDOUT)</tt>)
|
4
8
|
attr_accessor :logger
|
5
9
|
# number of seconds after which keys are removed form the message deduplication store (defaults to <tt>3.days</tt>)
|
6
10
|
attr_accessor :gc_threshold
|
7
|
-
# the
|
8
|
-
|
11
|
+
# the redis server to use for deduplication
|
12
|
+
# either a string like <tt>"localhost:6379"</tt> (default) or a file that contains the string.
|
13
|
+
# use a file if you are using a beetle configuration_client process to update it for automatic redis failover.
|
14
|
+
attr_accessor :redis_server
|
15
|
+
# comma separated list of redis servers available for master/slave switching
|
16
|
+
# e.g. "192.168.1.2:6379,192.168.1.3:6379"
|
17
|
+
attr_accessor :redis_servers
|
9
18
|
# redis database number to use for the message deduplication store (defaults to <tt>4</tt>)
|
10
19
|
attr_accessor :redis_db
|
20
|
+
|
21
|
+
# how long we should repeatedly retry a redis operation before giving up, with a one
|
22
|
+
# second sleep between retries (defaults to <tt>180.seconds</tt>). this value needs to be
|
23
|
+
# somewehere between the maximum time it takes to auto-switch redis and the smallest
|
24
|
+
# handler timeout.
|
25
|
+
attr_accessor :redis_failover_timeout
|
26
|
+
|
27
|
+
## redis configuration server options
|
28
|
+
# how often should the redis configuration server try to reach the redis master before nominating a new one (defaults to <tt>3</tt>)
|
29
|
+
attr_accessor :redis_configuration_master_retries
|
30
|
+
# number of seconds to wait between retries (defaults to <tt>10</tt>)
|
31
|
+
attr_accessor :redis_configuration_master_retry_interval
|
32
|
+
# number of seconds the redis configuration server waits for answers from clients (defaults to <tt>5</tt>)
|
33
|
+
attr_accessor :redis_configuration_client_timeout
|
34
|
+
# the redis configuration client ids living on the worker machines taking part in the redis failover, separated by comma (defaults to <tt>""</tt>)
|
35
|
+
attr_accessor :redis_configuration_client_ids
|
36
|
+
|
11
37
|
# list of amqp servers to use (defaults to <tt>"localhost:5672"</tt>)
|
12
38
|
attr_accessor :servers
|
13
|
-
# the virtual host to use on the AMQP servers
|
39
|
+
# the virtual host to use on the AMQP servers (defaults to <tt>"/"</tt>)
|
14
40
|
attr_accessor :vhost
|
15
|
-
# the AMQP user to use when connecting to the AMQP servers
|
41
|
+
# the AMQP user to use when connecting to the AMQP servers (defaults to <tt>"guest"</tt>)
|
16
42
|
attr_accessor :user
|
17
|
-
# the password to use when connectiong to the AMQP servers
|
43
|
+
# the password to use when connectiong to the AMQP servers (defaults to <tt>"guest"</tt>)
|
18
44
|
attr_accessor :password
|
19
45
|
|
46
|
+
# external config file (defaults to <tt>no file</tt>)
|
47
|
+
attr_reader :config_file
|
48
|
+
|
20
49
|
def initialize #:nodoc:
|
21
|
-
self.
|
50
|
+
self.system_name = "system"
|
51
|
+
|
52
|
+
self.logger = begin
|
53
|
+
logger = Logger.new(STDOUT)
|
54
|
+
logger.formatter = Logger::Formatter.new
|
55
|
+
logger.level = Logger::INFO
|
56
|
+
logger.datetime_format = "%Y-%m-%d %H:%M:%S"
|
57
|
+
logger
|
58
|
+
end
|
59
|
+
|
22
60
|
self.gc_threshold = 3.days
|
23
|
-
self.
|
61
|
+
self.redis_server = "localhost:6379"
|
62
|
+
self.redis_servers = ""
|
24
63
|
self.redis_db = 4
|
64
|
+
self.redis_failover_timeout = 180.seconds
|
65
|
+
|
66
|
+
self.redis_configuration_master_retries = 3
|
67
|
+
self.redis_configuration_master_retry_interval = 10.seconds
|
68
|
+
self.redis_configuration_client_timeout = 5.seconds
|
69
|
+
self.redis_configuration_client_ids = ""
|
70
|
+
|
25
71
|
self.servers = "localhost:5672"
|
26
72
|
self.vhost = "/"
|
27
73
|
self.user = "guest"
|
28
74
|
self.password = "guest"
|
29
75
|
end
|
76
|
+
|
77
|
+
# setting the external config file will load it on assignment
|
78
|
+
def config_file=(file_name) #:nodoc:
|
79
|
+
@config_file = file_name
|
80
|
+
load_config
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def load_config
|
85
|
+
hash = YAML::load(ERB.new(IO.read(config_file)).result)
|
86
|
+
hash.each do |key, value|
|
87
|
+
send("#{key}=", value)
|
88
|
+
end
|
89
|
+
rescue Exception
|
90
|
+
logger.error "Error loading beetle config file '#{config_file}': #{$!}"
|
91
|
+
raise
|
92
|
+
end
|
30
93
|
end
|
31
94
|
end
|
@@ -11,15 +11,23 @@ module Beetle
|
|
11
11
|
#
|
12
12
|
# It also provides a method to garbage collect keys for expired messages.
|
13
13
|
class DeduplicationStore
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@
|
14
|
+
include Logging
|
15
|
+
|
16
|
+
def initialize(config = Beetle.config)
|
17
|
+
@config = config
|
18
|
+
@current_master = nil
|
19
|
+
@last_time_master_file_changed = nil
|
18
20
|
end
|
19
21
|
|
20
22
|
# get the Redis instance
|
21
23
|
def redis
|
22
|
-
@
|
24
|
+
redis_master_source = @config.redis_server =~ /^\S+\:\d+$/ ? "server_string" : "master_file"
|
25
|
+
_eigenclass_.class_eval <<-EVALS, __FILE__, __LINE__
|
26
|
+
def redis
|
27
|
+
redis_master_from_#{redis_master_source}
|
28
|
+
end
|
29
|
+
EVALS
|
30
|
+
redis
|
23
31
|
end
|
24
32
|
|
25
33
|
# list of key suffixes to use for storing values in Redis.
|
@@ -43,7 +51,7 @@ module Beetle
|
|
43
51
|
# garbage collect keys in Redis (always assume the worst!)
|
44
52
|
def garbage_collect_keys(now = Time.now.to_i)
|
45
53
|
keys = redis.keys("msgid:*:expires")
|
46
|
-
threshold = now +
|
54
|
+
threshold = now + @config.gc_threshold
|
47
55
|
keys.each do |key|
|
48
56
|
expires_at = redis.get key
|
49
57
|
if expires_at && expires_at.to_i < threshold
|
@@ -53,20 +61,20 @@ module Beetle
|
|
53
61
|
end
|
54
62
|
end
|
55
63
|
|
56
|
-
# unconditionally store a key
|
64
|
+
# unconditionally store a (key,value) pair with given <tt>suffix</tt> for given <tt>msg_id</tt>.
|
57
65
|
def set(msg_id, suffix, value)
|
58
66
|
with_failover { redis.set(key(msg_id, suffix), value) }
|
59
67
|
end
|
60
68
|
|
61
|
-
# store a key
|
69
|
+
# store a (key,value) pair with given <tt>suffix</tt> for given <tt>msg_id</tt> if it doesn't exists yet.
|
62
70
|
def setnx(msg_id, suffix, value)
|
63
71
|
with_failover { redis.setnx(key(msg_id, suffix), value) }
|
64
72
|
end
|
65
73
|
|
66
74
|
# store some key/value pairs if none of the given keys exist.
|
67
75
|
def msetnx(msg_id, values)
|
68
|
-
values = values.inject(
|
69
|
-
with_failover { redis.msetnx(values) }
|
76
|
+
values = values.inject([]){|a,(k,v)| a.concat([key(msg_id, k), v])}
|
77
|
+
with_failover { redis.msetnx(*values) }
|
70
78
|
end
|
71
79
|
|
72
80
|
# increment counter for key with given <tt>suffix</tt> for given <tt>msg_id</tt>. returns an integer.
|
@@ -86,7 +94,7 @@ module Beetle
|
|
86
94
|
|
87
95
|
# delete all keys associated with the given <tt>msg_id</tt>.
|
88
96
|
def del_keys(msg_id)
|
89
|
-
with_failover { redis.del(keys(msg_id)) }
|
97
|
+
with_failover { redis.del(*keys(msg_id)) }
|
90
98
|
end
|
91
99
|
|
92
100
|
# check whether key with given suffix exists for a given <tt>msg_id</tt>.
|
@@ -101,16 +109,15 @@ module Beetle
|
|
101
109
|
|
102
110
|
# performs redis operations by yielding a passed in block, waiting for a new master to
|
103
111
|
# show up on the network if the operation throws an exception. if a new master doesn't
|
104
|
-
# appear after
|
112
|
+
# appear after the configured timeout interval, we raise an exception.
|
105
113
|
def with_failover #:nodoc:
|
106
|
-
|
114
|
+
end_time = Time.now.to_i + @config.redis_failover_timeout.to_i
|
107
115
|
begin
|
108
116
|
yield
|
109
117
|
rescue Exception => e
|
110
118
|
Beetle::reraise_expectation_errors!
|
111
|
-
logger.error "Beetle: redis connection error
|
112
|
-
if
|
113
|
-
@redis = nil
|
119
|
+
logger.error "Beetle: redis connection error #{e} #{@config.redis_server} (#{e.backtrace[0]})"
|
120
|
+
if Time.now.to_i < end_time
|
114
121
|
sleep 1
|
115
122
|
logger.info "Beetle: retrying redis operation"
|
116
123
|
retry
|
@@ -120,33 +127,38 @@ module Beetle
|
|
120
127
|
end
|
121
128
|
end
|
122
129
|
|
123
|
-
#
|
124
|
-
def
|
125
|
-
|
126
|
-
redis_instances.each do |redis|
|
127
|
-
begin
|
128
|
-
masters << redis if redis.info[:role] == "master"
|
129
|
-
rescue Exception => e
|
130
|
-
logger.error "Beetle: could not determine status of redis instance #{redis.server}"
|
131
|
-
end
|
132
|
-
end
|
133
|
-
raise NoRedisMaster.new("unable to determine a new master redis instance") if masters.empty?
|
134
|
-
raise TwoRedisMasters.new("more than one redis master instances") if masters.size > 1
|
135
|
-
logger.info "Beetle: configured new redis master #{masters.first.server}"
|
136
|
-
masters.first
|
130
|
+
# set current redis master instance (as specified in the Beetle::Configuration)
|
131
|
+
def redis_master_from_server_string
|
132
|
+
@current_master ||= Redis.from_server_string(@config.redis_server, :db => @config.redis_db)
|
137
133
|
end
|
138
134
|
|
139
|
-
#
|
140
|
-
def
|
141
|
-
|
142
|
-
|
143
|
-
|
135
|
+
# set current redis master from master file
|
136
|
+
def redis_master_from_master_file
|
137
|
+
set_current_redis_master_from_master_file if redis_master_file_changed?
|
138
|
+
@current_master
|
139
|
+
rescue Errno::ENOENT
|
140
|
+
nil
|
144
141
|
end
|
145
142
|
|
146
|
-
#
|
147
|
-
def
|
148
|
-
|
143
|
+
# redis master file changed outside the running process?
|
144
|
+
def redis_master_file_changed?
|
145
|
+
@last_time_master_file_changed != File.mtime(@config.redis_server)
|
149
146
|
end
|
150
147
|
|
148
|
+
# set current redis master from server:port string contained in the redis master file
|
149
|
+
def set_current_redis_master_from_master_file
|
150
|
+
@last_time_master_file_changed = File.mtime(@config.redis_server)
|
151
|
+
server_string = read_master_file
|
152
|
+
@current_master = !server_string.blank? ? Redis.from_server_string(server_string, :db => @config.redis_db) : nil
|
153
|
+
end
|
154
|
+
|
155
|
+
# server:port string from the redis master file
|
156
|
+
def read_master_file
|
157
|
+
File.read(@config.redis_server).chomp
|
158
|
+
end
|
159
|
+
|
160
|
+
def _eigenclass_ #:nodoc:
|
161
|
+
class << self; self; end
|
162
|
+
end
|
151
163
|
end
|
152
164
|
end
|
data/lib/beetle/handler.rb
CHANGED
@@ -6,6 +6,8 @@ module Beetle
|
|
6
6
|
# Most applications will define Handler subclasses and override the process, error and
|
7
7
|
# failure methods.
|
8
8
|
class Handler
|
9
|
+
include Logging
|
10
|
+
|
9
11
|
# the Message instance which caused the handler to be created
|
10
12
|
attr_reader :message
|
11
13
|
|
@@ -81,11 +83,6 @@ module Beetle
|
|
81
83
|
logger.error "Beetle: handler has finally failed"
|
82
84
|
end
|
83
85
|
|
84
|
-
# returns the configured Beetle logger
|
85
|
-
def logger
|
86
|
-
Beetle.config.logger
|
87
|
-
end
|
88
|
-
|
89
86
|
# returns the configured Beetle logger
|
90
87
|
def self.logger
|
91
88
|
Beetle.config.logger
|
data/lib/beetle/message.rb
CHANGED
@@ -6,6 +6,8 @@ module Beetle
|
|
6
6
|
# should retry executing the message handler after a handler has crashed (or forcefully
|
7
7
|
# aborted).
|
8
8
|
class Message
|
9
|
+
include Logging
|
10
|
+
|
9
11
|
# current message format version
|
10
12
|
FORMAT_VERSION = 1
|
11
13
|
# flag for encoding redundant messages
|
@@ -14,7 +16,7 @@ module Beetle
|
|
14
16
|
DEFAULT_TTL = 1.day
|
15
17
|
# forcefully abort a running handler after this many seconds.
|
16
18
|
# can be overriden when registering a handler.
|
17
|
-
DEFAULT_HANDLER_TIMEOUT =
|
19
|
+
DEFAULT_HANDLER_TIMEOUT = 600.seconds
|
18
20
|
# how many times we should try to run a handler before giving up
|
19
21
|
DEFAULT_HANDLER_EXECUTION_ATTEMPTS = 1
|
20
22
|
# how many seconds we should wait before retrying handler execution
|
@@ -77,6 +79,8 @@ module Beetle
|
|
77
79
|
@format_version = headers[:format_version].to_i
|
78
80
|
@flags = headers[:flags].to_i
|
79
81
|
@expires_at = headers[:expires_at].to_i
|
82
|
+
rescue Exception => @exception
|
83
|
+
logger.error "Could not decode message. #{self.inspect}"
|
80
84
|
end
|
81
85
|
|
82
86
|
# build hash with options for the publisher
|
@@ -193,7 +197,7 @@ module Beetle
|
|
193
197
|
# have we already seen this message? if not, set the status to "incomplete" and store
|
194
198
|
# the message exipration timestamp in the deduplication store.
|
195
199
|
def key_exists?
|
196
|
-
old_message = 0 == @store.msetnx(msg_id, :status =>"incomplete", :expires => @expires_at)
|
200
|
+
old_message = 0 == @store.msetnx(msg_id, :status =>"incomplete", :expires => @expires_at, :timeout => now + timeout)
|
197
201
|
if old_message
|
198
202
|
logger.debug "Beetle: received duplicate message: #{msg_id} on queue: #{@queue}"
|
199
203
|
end
|
@@ -236,7 +240,10 @@ module Beetle
|
|
236
240
|
private
|
237
241
|
|
238
242
|
def process_internal(handler)
|
239
|
-
if
|
243
|
+
if @exception
|
244
|
+
ack!
|
245
|
+
RC::DecodingError
|
246
|
+
elsif expired?
|
240
247
|
logger.warn "Beetle: ignored expired message (#{msg_id})!"
|
241
248
|
ack!
|
242
249
|
RC::Ancient
|
@@ -244,7 +251,6 @@ module Beetle
|
|
244
251
|
ack!
|
245
252
|
run_handler(handler) == RC::HandlerCrash ? RC::AttemptsLimitReached : RC::OK
|
246
253
|
elsif !key_exists?
|
247
|
-
set_timeout!
|
248
254
|
run_handler!(handler)
|
249
255
|
elsif completed?
|
250
256
|
ack!
|
@@ -273,7 +279,7 @@ module Beetle
|
|
273
279
|
end
|
274
280
|
|
275
281
|
def run_handler(handler)
|
276
|
-
|
282
|
+
Redis::Timer.timeout(@timeout.to_f) { @handler_result = handler.call(self) }
|
277
283
|
RC::OK
|
278
284
|
rescue Exception => @exception
|
279
285
|
Beetle::reraise_expectation_errors!
|
@@ -313,14 +319,6 @@ module Beetle
|
|
313
319
|
end
|
314
320
|
end
|
315
321
|
|
316
|
-
def logger
|
317
|
-
@logger ||= self.class.logger
|
318
|
-
end
|
319
|
-
|
320
|
-
def self.logger
|
321
|
-
Beetle.config.logger
|
322
|
-
end
|
323
|
-
|
324
322
|
# ack the message for rabbit. deletes all keys associated with this message in the
|
325
323
|
# deduplication store if we are sure this is the last message with the given msg_id.
|
326
324
|
def ack!
|
data/lib/beetle/publisher.rb
CHANGED
@@ -132,11 +132,16 @@ module Beetle
|
|
132
132
|
b
|
133
133
|
end
|
134
134
|
|
135
|
+
# retry dead servers after ignoring them for 10.seconds
|
136
|
+
# if all servers are dead, retry the one which has been dead for the longest time
|
135
137
|
def recycle_dead_servers
|
136
138
|
recycle = []
|
137
139
|
@dead_servers.each do |s, dead_since|
|
138
140
|
recycle << s if dead_since < 10.seconds.ago
|
139
141
|
end
|
142
|
+
if recycle.empty? && @servers.empty?
|
143
|
+
recycle << @dead_servers.keys.sort_by{|k| @dead_servers[k]}.first
|
144
|
+
end
|
140
145
|
@servers.concat recycle
|
141
146
|
recycle.each {|s| @dead_servers.delete(s)}
|
142
147
|
end
|
@@ -149,8 +154,11 @@ module Beetle
|
|
149
154
|
end
|
150
155
|
|
151
156
|
def select_next_server
|
152
|
-
|
153
|
-
|
157
|
+
if @servers.empty?
|
158
|
+
logger.error("Beetle: no server available")
|
159
|
+
else
|
160
|
+
set_current_server(@servers[((@servers.index(@server) || 0)+1) % @servers.size])
|
161
|
+
end
|
154
162
|
end
|
155
163
|
|
156
164
|
def create_exchange!(name, opts)
|
@@ -165,9 +173,9 @@ module Beetle
|
|
165
173
|
|
166
174
|
# TODO: Refactor, fethch the keys and stuff itself
|
167
175
|
def bind_queue!(queue_name, creation_keys, exchange_name, binding_keys)
|
168
|
-
logger.debug("
|
176
|
+
logger.debug("Beetle: creating queue with opts: #{creation_keys.inspect}")
|
169
177
|
queue = bunny.queue(queue_name, creation_keys)
|
170
|
-
logger.debug("
|
178
|
+
logger.debug("Beetle: binding queue #{queue_name} to #{exchange_name} with opts: #{binding_keys.inspect}")
|
171
179
|
queue.bind(exchange(exchange_name), binding_keys)
|
172
180
|
queue
|
173
181
|
end
|