promiscuous 0.50.4 → 0.51.0

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