promiscuous 0.50.4 → 0.51.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/promiscuous.rb CHANGED
@@ -46,5 +46,29 @@ module Promiscuous
46
46
  end
47
47
  end
48
48
 
49
+ module ConsoleHelpers
50
+ # These are just for the subscriber, some helpers to debug in production
51
+ def global_key
52
+ Promiscuous::Redis.sub_key('global')
53
+ end
54
+
55
+ def global_version
56
+ Promiscuous::Redis.get(global_key).to_i
57
+ end
58
+
59
+ def global_version=(value)
60
+ Promiscuous::Redis.set(global_key, value)
61
+ Promiscuous::Redis.publish(global_key, value)
62
+ value
63
+ end
64
+
65
+ def global_version!
66
+ version = Promiscuous::Redis.incr(global_key)
67
+ Promiscuous::Redis.publish(global_key, version)
68
+ version
69
+ end
70
+ end
71
+ extend ConsoleHelpers
72
+
49
73
  at_exit { self.disconnect rescue nil }
50
74
  end
@@ -20,7 +20,9 @@ module Promiscuous::AMQP::RubyAMQP
20
20
  end
21
21
 
22
22
  connection = ::AMQP.connect(amqp_options)
23
- self.channel = ::AMQP::Channel.new(connection, :auto_recovery => true, :prefetch => 1000)
23
+ self.channel = ::AMQP::Channel.new(connection,
24
+ :auto_recovery => true,
25
+ :prefetch => Promiscuous::Config.prefetch)
24
26
 
25
27
  connection.on_tcp_connection_loss do |conn|
26
28
  unless conn.reconnecting?
@@ -51,13 +51,22 @@ class Promiscuous::CLI
51
51
  opts.separator "Options:"
52
52
 
53
53
  opts.on "-b", "--bareback", "Bareback mode aka no dependencies. Use with extreme caution" do
54
- options[:bareback] = true
54
+ Promiscuous::Config.bareback = true
55
55
  end
56
56
 
57
- opts.on "-r", "--require FILE", "File to require to load your app. Don't worry about it with rails" do |file|
57
+ opts.on "-l", "--require FILE", "File to require to load your app. Don't worry about it with rails" do |file|
58
58
  options[:require] = file
59
59
  end
60
60
 
61
+ opts.on "-r", "--recovery [TIMEOUT]", "Run in recovery mode. Defaults to 10 seconds before recovering" do |timeout|
62
+ Promiscuous::Config.recovery_timeout = (timeout || 10).to_i
63
+ end
64
+
65
+ opts.on "-p", "--prefetch [NUM]", "Number of messages to prefetch" do |prefetch|
66
+ exit 1 if prefetch.to_i == 0
67
+ Promiscuous::Config.prefetch = prefetch.to_i
68
+ end
69
+
61
70
  opts.on("-h", "--help", "Show this message") do
62
71
  puts opts
63
72
  exit
@@ -106,7 +115,7 @@ class Promiscuous::CLI
106
115
  def boot
107
116
  self.options = parse_args(ARGV)
108
117
  load_app
109
- maybe_run_bareback
118
+ show_bareback_warnings
110
119
  run
111
120
  end
112
121
 
@@ -117,9 +126,8 @@ class Promiscuous::CLI
117
126
  end
118
127
  end
119
128
 
120
- def maybe_run_bareback
121
- if options[:bareback]
122
- Promiscuous::Config.bareback = true
129
+ def show_bareback_warnings
130
+ if Promiscuous::Config.bareback == true
123
131
  print_status "WARNING: --- BAREBACK MODE ----"
124
132
  print_status "WARNING: You are replicating without protection, you can get out of sync in no time"
125
133
  print_status "WARNING: --- BAREBACK MODE ----"
@@ -1,20 +1,25 @@
1
1
  module Promiscuous::Config
2
- mattr_accessor :app, :logger, :error_notifier, :backend, :amqp_url, :redis_url, :queue_options, :heartbeat, :bareback
2
+ mattr_accessor :app, :logger, :error_notifier, :backend, :amqp_url,
3
+ :redis_url, :queue_options, :heartbeat, :bareback,
4
+ :recovery_timeout, :prefetch
3
5
 
4
6
  def self.backend=(value)
5
7
  @@backend = value
6
8
  Promiscuous::AMQP.backend = value unless value.nil?
7
9
  end
8
10
 
9
- def self.configure(&block)
11
+ def self.reset
10
12
  class_variables.each { |var| class_variable_set(var, nil) }
13
+ end
11
14
 
15
+ def self.configure(&block)
12
16
  block.call(self)
13
17
 
14
18
  self.backend ||= defined?(EventMachine) && EventMachine.reactor_running? ? :rubyamqp : :bunny
15
19
  self.logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
16
20
  self.queue_options ||= {:durable => true, :arguments => {'x-ha-policy' => 'all'}}
17
21
  self.heartbeat ||= 60
22
+ self.prefetch ||= 1000
18
23
 
19
24
  Promiscuous.connect
20
25
  end
@@ -1,4 +1,4 @@
1
1
  module Promiscuous::Error
2
2
  extend Promiscuous::Autoload
3
- autoload :Connection, :Publisher, :Subscriber
3
+ autoload :Connection, :Publisher, :Subscriber, :Recover
4
4
  end
@@ -0,0 +1 @@
1
+ class Promiscuous::Error::Recover < RuntimeError; end
@@ -17,6 +17,7 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer
17
17
  end
18
18
 
19
19
  def connect
20
+ @queued_messages = 0
20
21
  @subscriptions = {}
21
22
  self.redis = Promiscuous::Redis.new_celluloid_connection
22
23
  end
@@ -106,6 +107,8 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer
106
107
  # when calling worker.pump.start
107
108
  return unless self.redis
108
109
 
110
+ bump_message_counter!
111
+
109
112
  return process_message!(msg) unless msg.has_dependencies?
110
113
 
111
114
  # The message synchronizer only takes care of happens before (>=) dependencies.
@@ -115,7 +118,76 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer
115
118
  end
116
119
  end
117
120
 
121
+ def bump_message_counter!
122
+ @queued_messages += 1
123
+ maybe_recover
124
+ end
125
+
126
+ def maybe_recover
127
+ recovery_timeout = Promiscuous::Config.recovery_timeout
128
+ return unless recovery_timeout
129
+
130
+ reset_recovery_timer
131
+ if should_recover?
132
+ # We've reached the amount of messages the amqp queue is willing to give us.
133
+ # We also know that we are not processing messages (@queued_messages is
134
+ # decremented before we send the message to the runners).
135
+
136
+ Promiscuous.warn "[receive] Recovering in #{recovery_timeout} seconds..."
137
+ @recover_timer = after(recovery_timeout) { reset_recovery_timer; recover }
138
+ end
139
+ end
140
+
141
+ def reset_recovery_timer
142
+ @recover_timer.try(:reset)
143
+ @recover_timer = nil
144
+ end
145
+
146
+ def should_recover?
147
+ @queued_messages == Promiscuous::Config.prefetch
148
+ end
149
+
150
+ def recover
151
+ return unless should_recover?
152
+
153
+ global_key = Promiscuous::Redis.sub_key('global')
154
+ current_version = Promiscuous::Redis.get(global_key).to_i
155
+
156
+ global_callbacks = get_subscription(global_key).callbacks
157
+ expected_next_msg_version = global_callbacks.values.map(&:version).min
158
+ version_to_allow_progress = expected_next_msg_version - 1
159
+
160
+ num_messages_to_skip = version_to_allow_progress - current_version
161
+
162
+ if num_messages_to_skip > 0
163
+ recovery_msg = "Recovering. Moving current version from #{current_version} " +
164
+ "to #{version_to_allow_progress}. " +
165
+ "Skipping #{num_messages_to_skip} messages..."
166
+ else
167
+ recovery_msg = "Not recovering. current version is #{current_version}, " +
168
+ "while we just need #{version_to_allow_progress}. " +
169
+ "Offset is #{num_messages_to_skip} message."
170
+ end
171
+
172
+ e = Promiscuous::Error::Recover.new(recovery_msg)
173
+ if current_version > 0
174
+ Promiscuous.error "[receive] #{e}"
175
+ Promiscuous::Config.error_notifier.try(:call, e)
176
+ else
177
+ Promiscuous.info "[receive] #{e}"
178
+ # Initial sync, nothing to worry about
179
+ end
180
+
181
+ if num_messages_to_skip > 0
182
+ Promiscuous::Redis.set(global_key, version_to_allow_progress)
183
+ Promiscuous::Redis.publish(global_key, version_to_allow_progress)
184
+ end
185
+ ensure
186
+ reset_recovery_timer
187
+ end
188
+
118
189
  def process_message!(msg)
190
+ @queued_messages -= 1
119
191
  Celluloid::Actor[:runners].process!(msg)
120
192
  end
121
193
 
@@ -136,7 +208,7 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer
136
208
  end
137
209
 
138
210
  class Subscription
139
- attr_accessor :parent, :key
211
+ attr_accessor :parent, :key, :callbacks
140
212
 
141
213
  def initialize(parent, key)
142
214
  self.parent = parent
@@ -1,3 +1,3 @@
1
1
  module Promiscuous
2
- VERSION = '0.50.4'
2
+ VERSION = '0.51.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: promiscuous
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.50.4
4
+ version: 0.51.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-02-04 00:00:00.000000000 Z
13
+ date: 2013-02-05 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -221,19 +221,20 @@ files:
221
221
  - lib/promiscuous/error/subscriber.rb
222
222
  - lib/promiscuous/error/publisher.rb
223
223
  - lib/promiscuous/error/connection.rb
224
+ - lib/promiscuous/error/recover.rb
224
225
  - lib/promiscuous/ephemeral.rb
225
226
  - lib/promiscuous/autoload.rb
226
- - lib/promiscuous/error.rb
227
227
  - lib/promiscuous/loader.rb
228
228
  - lib/promiscuous/railtie.rb
229
229
  - lib/promiscuous/common.rb
230
230
  - lib/promiscuous/publisher.rb
231
231
  - lib/promiscuous/amqp.rb
232
232
  - lib/promiscuous/subscriber.rb
233
- - lib/promiscuous/config.rb
234
- - lib/promiscuous/cli.rb
235
233
  - lib/promiscuous/redis.rb
236
234
  - lib/promiscuous/observer.rb
235
+ - lib/promiscuous/cli.rb
236
+ - lib/promiscuous/error.rb
237
+ - lib/promiscuous/config.rb
237
238
  - lib/promiscuous/version.rb
238
239
  - lib/promiscuous.rb
239
240
  - bin/promiscuous