promiscuous 0.90.0 → 0.91.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/lib/promiscuous/amqp/bunny.rb +63 -36
  3. data/lib/promiscuous/amqp/fake.rb +3 -1
  4. data/lib/promiscuous/amqp/hot_bunnies.rb +26 -16
  5. data/lib/promiscuous/amqp/null.rb +1 -0
  6. data/lib/promiscuous/amqp.rb +12 -12
  7. data/lib/promiscuous/cli.rb +70 -29
  8. data/lib/promiscuous/config.rb +54 -29
  9. data/lib/promiscuous/convenience.rb +1 -1
  10. data/lib/promiscuous/dependency.rb +25 -6
  11. data/lib/promiscuous/error/connection.rb +11 -9
  12. data/lib/promiscuous/error/dependency.rb +8 -1
  13. data/lib/promiscuous/loader.rb +4 -2
  14. data/lib/promiscuous/publisher/bootstrap/connection.rb +25 -0
  15. data/lib/promiscuous/publisher/bootstrap/data.rb +127 -0
  16. data/lib/promiscuous/publisher/bootstrap/mode.rb +19 -0
  17. data/lib/promiscuous/publisher/bootstrap/status.rb +40 -0
  18. data/lib/promiscuous/publisher/bootstrap/version.rb +46 -0
  19. data/lib/promiscuous/publisher/bootstrap.rb +27 -0
  20. data/lib/promiscuous/publisher/context/base.rb +67 -0
  21. data/lib/promiscuous/{middleware.rb → publisher/context/middleware.rb} +16 -13
  22. data/lib/promiscuous/publisher/context/transaction.rb +36 -0
  23. data/lib/promiscuous/publisher/context.rb +4 -88
  24. data/lib/promiscuous/publisher/mock_generator.rb +9 -9
  25. data/lib/promiscuous/publisher/model/active_record.rb +7 -7
  26. data/lib/promiscuous/publisher/model/base.rb +29 -29
  27. data/lib/promiscuous/publisher/model/ephemeral.rb +5 -3
  28. data/lib/promiscuous/publisher/model/mock.rb +9 -5
  29. data/lib/promiscuous/publisher/model/mongoid.rb +5 -22
  30. data/lib/promiscuous/publisher/operation/active_record.rb +360 -0
  31. data/lib/promiscuous/publisher/operation/atomic.rb +167 -0
  32. data/lib/promiscuous/publisher/operation/base.rb +279 -474
  33. data/lib/promiscuous/publisher/operation/mongoid.rb +153 -145
  34. data/lib/promiscuous/publisher/operation/non_persistent.rb +28 -0
  35. data/lib/promiscuous/publisher/operation/proxy_for_query.rb +42 -0
  36. data/lib/promiscuous/publisher/operation/transaction.rb +85 -0
  37. data/lib/promiscuous/publisher/operation.rb +1 -1
  38. data/lib/promiscuous/publisher/worker.rb +7 -7
  39. data/lib/promiscuous/publisher.rb +1 -1
  40. data/lib/promiscuous/railtie.rb +20 -5
  41. data/lib/promiscuous/redis.rb +104 -56
  42. data/lib/promiscuous/subscriber/message_processor/base.rb +38 -0
  43. data/lib/promiscuous/subscriber/message_processor/bootstrap.rb +17 -0
  44. data/lib/promiscuous/subscriber/message_processor/regular.rb +192 -0
  45. data/lib/promiscuous/subscriber/message_processor.rb +4 -0
  46. data/lib/promiscuous/subscriber/model/base.rb +20 -15
  47. data/lib/promiscuous/subscriber/model/mongoid.rb +4 -4
  48. data/lib/promiscuous/subscriber/model/observer.rb +16 -2
  49. data/lib/promiscuous/subscriber/operation/base.rb +68 -0
  50. data/lib/promiscuous/subscriber/operation/bootstrap.rb +54 -0
  51. data/lib/promiscuous/subscriber/operation/regular.rb +13 -0
  52. data/lib/promiscuous/subscriber/operation.rb +3 -166
  53. data/lib/promiscuous/subscriber/worker/message.rb +61 -35
  54. data/lib/promiscuous/subscriber/worker/message_synchronizer.rb +90 -59
  55. data/lib/promiscuous/subscriber/worker/pump.rb +17 -5
  56. data/lib/promiscuous/subscriber/worker/recorder.rb +4 -1
  57. data/lib/promiscuous/subscriber/worker/runner.rb +49 -9
  58. data/lib/promiscuous/subscriber/worker/stats.rb +2 -2
  59. data/lib/promiscuous/subscriber/worker.rb +6 -0
  60. data/lib/promiscuous/subscriber.rb +1 -1
  61. data/lib/promiscuous/timer.rb +31 -18
  62. data/lib/promiscuous/version.rb +1 -1
  63. data/lib/promiscuous.rb +23 -3
  64. metadata +104 -89
  65. data/lib/promiscuous/subscriber/payload.rb +0 -34
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b84ccb396c096ef9d2164d4400356606f90c3954
4
+ data.tar.gz: 31da27f6d1163623df3014dc15a0641d6e93c0c3
5
+ SHA512:
6
+ metadata.gz: 2cec057b926337d6940a8d3144b88ccc3dcf200a35dc74d88f69bf4ad388d034b6e68a04dd2f270f5c581382bef3ae5985b6e4ad9e7d9cfe44babf6cdacabd02
7
+ data.tar.gz: d84a82ecdcb22041d72c08e8c9fb443e77c2c9a74c49589a6d209079df7055f1adc484ca12b32d6f75f9a04e8f66bcfbaefeff020eb47b38c6fecbe3e1bc0c0b
@@ -6,7 +6,7 @@ class Promiscuous::AMQP::Bunny
6
6
 
7
7
  def handle_network_failure(e)
8
8
  Promiscuous.warn "[amqp] #{e}. Reconnecting..."
9
- Promiscuous::Config.error_notifier.try(:call, e)
9
+ Promiscuous::Config.error_notifier.call(e)
10
10
  handle_network_failure_without_promiscuous(e)
11
11
  end
12
12
  end
@@ -27,21 +27,40 @@ class Promiscuous::AMQP::Bunny
27
27
  @callback_mapping = {}
28
28
  end
29
29
 
30
- def connect
31
- @connection, @channel = new_connection
32
- @exchange = exchange(@channel, :pub)
33
- confirm_select(@channel, &method(:on_confirm))
30
+ def raw_new_connection(options={})
31
+ connection = ::Bunny.new(options[:url], :heartbeat_interval => Promiscuous::Config.heartbeat,
32
+ :socket_timeout => Promiscuous::Config.socket_timeout,
33
+ :connect_timeout => Promiscuous::Config.socket_timeout)
34
+ connection.start
35
+ connection
34
36
  end
35
37
 
36
- def new_connection
37
- connection = ::Bunny.new(Promiscuous::Config.amqp_url,
38
- :heartbeat_interval => Promiscuous::Config.heartbeat,
39
- :socket_timeout => Promiscuous::Config.socket_timeout,
40
- :connect_timeout => Promiscuous::Config.socket_timeout)
41
- connection.start
38
+ def raw_confirm_select(channel, &callback)
39
+ channel.confirm_select(callback)
40
+ end
42
41
 
42
+ def new_connection(options={})
43
+ connection = raw_new_connection(options)
43
44
  channel = connection.create_channel
44
- [connection, channel]
45
+ channel.basic_qos(options[:prefetch]) if options[:prefetch]
46
+ raw_confirm_select(channel, &method(:on_confirm)) if options[:confirm]
47
+
48
+ if options[:exchanges]
49
+ exchanges = options[:exchanges].map do |exchange_name|
50
+ channel.exchange(exchange_name, :type => :topic, :durable => true)
51
+ end
52
+ [connection, channel, exchanges]
53
+ else
54
+ exchange = channel.exchange(options[:exchange], :type => :topic, :durable => true)
55
+ [connection, channel, exchange]
56
+ end
57
+ end
58
+
59
+ def connect
60
+ connection_options = { :url => Promiscuous::Config.publisher_amqp_url,
61
+ :exchange => Promiscuous::Config.publisher_exchange,
62
+ :confirm => true }
63
+ @connection, @channel, @exchange = new_connection(connection_options)
45
64
  end
46
65
 
47
66
  def disconnect
@@ -57,16 +76,13 @@ class Promiscuous::AMQP::Bunny
57
76
  end
58
77
 
59
78
  def raw_publish(options)
60
- @exchange.publish(options[:payload], :key => options[:key], :persistent => true)
61
- end
62
-
63
- def exchange(channel, which)
64
- exchange_name = which == :pub ? Promiscuous::AMQP::PUB_EXCHANGE :
65
- Promiscuous::AMQP::SUB_EXCHANGE
66
- channel.exchange(exchange_name, :type => :topic, :durable => true)
79
+ options[:exchange].publish(options[:payload], :key => options[:key], :persistent => true)
67
80
  end
68
81
 
69
82
  def publish(options={})
83
+ options[:exchange] ||= @exchange
84
+ Promiscuous.debug "[publish] #{options[:exchange].name}/#{options[:key]} #{options[:payload]}"
85
+
70
86
  @connection_lock.synchronize do
71
87
  tag = @channel.next_publish_seq_no if options[:on_confirm]
72
88
  raw_publish(options)
@@ -74,12 +90,8 @@ class Promiscuous::AMQP::Bunny
74
90
  end
75
91
  rescue Exception => e
76
92
  e = Promiscuous::Error::Publisher.new(e, :payload => options[:payload])
77
- Promiscuous.warn "[publish] #{e} #{e.backtrace.join("\n")}"
78
- Promiscuous::Config.error_notifier.try(:call, e)
79
- end
80
-
81
- def confirm_select(channel, &callback)
82
- channel.confirm_select(callback)
93
+ Promiscuous.warn "[publish] #{e}\n#{e.backtrace.join("\n")}"
94
+ Promiscuous::Config.error_notifier.call(e)
83
95
  end
84
96
 
85
97
  def on_confirm(tag, multiple, nack=false)
@@ -96,18 +108,20 @@ class Promiscuous::AMQP::Bunny
96
108
 
97
109
  module Subscriber
98
110
  def subscribe(options={}, &block)
99
- queue_name = options[:queue_name]
100
- bindings = options[:bindings]
101
- Promiscuous::AMQP.ensure_connected
102
-
103
111
  @lock = Mutex.new
104
- @connection, @channel = Promiscuous::AMQP.backend.new_connection
105
- @channel.basic_qos(Promiscuous::Config.prefetch)
106
- exchange = Promiscuous::AMQP.backend.exchange(@channel, :sub)
107
- @queue = @channel.queue(queue_name, Promiscuous::Config.queue_options)
108
- bindings.each do |binding|
109
- @queue.bind(exchange, :routing_key => binding)
110
- Promiscuous.debug "[bind] #{queue_name} -> #{binding}"
112
+ @prefetch = Promiscuous::Config.prefetch
113
+
114
+ connection_options = { :url => Promiscuous::Config.subscriber_amqp_url,
115
+ :exchanges => options[:bindings].keys,
116
+ :prefetch => @prefetch }
117
+ @connection, @channel, exchanges = Promiscuous::AMQP.new_connection(connection_options)
118
+
119
+ @queue = @channel.queue(Promiscuous::Config.queue_name, Promiscuous::Config.queue_options)
120
+ exchanges.zip(options[:bindings].values).each do |exchange, bindings|
121
+ bindings.each do |binding|
122
+ @queue.bind(exchange, :routing_key => binding)
123
+ Promiscuous.debug "[bind] #{exchange.name}/#{binding}/#{Promiscuous::Config.queue_name}"
124
+ end
111
125
  end
112
126
 
113
127
  @subscription = subscribe_queue(@queue, &block)
@@ -128,12 +142,25 @@ class Promiscuous::AMQP::Bunny
128
142
  def ack
129
143
  @subscriber.ack_message(@delivery_info.delivery_tag)
130
144
  end
145
+
146
+ def postpone
147
+ @subscriber.postpone_message
148
+ end
131
149
  end
132
150
 
133
151
  def ack_message(tag)
134
152
  @lock.synchronize { @channel.ack(tag) } if @channel
135
153
  end
136
154
 
155
+ def postpone_message
156
+ # Not using nacks, because the message gets sent back right away so this
157
+ # is a no-op.
158
+
159
+ # TODO: Even though the prefetch window is set to 10mil we should still
160
+ # check that the unacked messages doesn't exceed this limit and increase
161
+ # the prefetch window.
162
+ end
163
+
137
164
  def recover
138
165
  @lock.synchronize { @channel.basic_recover(true) } if @channel
139
166
  end
@@ -20,6 +20,7 @@ class Promiscuous::AMQP::Fake
20
20
  end
21
21
 
22
22
  def publish(options={})
23
+ Promiscuous.debug "[publish (fake)] #{options[:exchange].try(:name) || "default"}/#{options[:key]} #{options[:payload]}"
23
24
  @messages << options
24
25
  options[:on_confirm].try(:call)
25
26
  end
@@ -33,7 +34,8 @@ class Promiscuous::AMQP::Fake
33
34
  end
34
35
 
35
36
  def get_next_payload
36
- JSON.parse(get_next_message[:payload])
37
+ msg = get_next_message
38
+ msg && JSON.parse(msg[:payload])
37
39
  end
38
40
 
39
41
  def open_queue(options={}, &block)
@@ -7,13 +7,19 @@ class Promiscuous::AMQP::HotBunnies < Promiscuous::AMQP::Bunny
7
7
 
8
8
  # TODO auto reconnect
9
9
 
10
- def new_connection
11
- connection = ::HotBunnies.connect(:uri => Promiscuous::Config.amqp_url,
12
- :heartbeat_interval => Promiscuous::Config.heartbeat,
13
- :connection_timeout => Promiscuous::Config.socket_timeout)
10
+ def raw_new_connection(options={})
11
+ ::HotBunnies.connect(:uri => options[:url],
12
+ :heartbeat_interval => Promiscuous::Config.heartbeat,
13
+ :connection_timeout => Promiscuous::Config.socket_timeout)
14
+ end
15
+
16
+ def raw_confirm_select(channel, &callback)
17
+ channel.add_confirm_listener(&callback)
18
+ channel.confirm_select
19
+ end
14
20
 
15
- channel = connection.create_channel
16
- [connection, channel]
21
+ def raw_publish(options={})
22
+ options[:exchange].publish(options[:payload], :routing_key => options[:key], :persistent => true)
17
23
  end
18
24
 
19
25
  def disconnect
@@ -29,20 +35,24 @@ class Promiscuous::AMQP::HotBunnies < Promiscuous::AMQP::Bunny
29
35
  !!@connection.try(:is_open)
30
36
  end
31
37
 
32
- def raw_publish(options={})
33
- @exchange.publish(options[:payload], :routing_key => options[:key], :persistent => true)
34
- end
35
-
36
- def confirm_select(channel, &callback)
37
- channel.add_confirm_listener(&callback)
38
- channel.confirm_select
39
- end
40
-
41
38
  module Subscriber
42
39
  include Promiscuous::AMQP::Bunny::Subscriber
43
40
 
44
41
  def subscribe_queue(queue, &block)
45
- queue.subscribe(:ack => true, :blocking => false, &block)
42
+ queue.subscribe(:ack => true, :blocking => false) do |metadata, payload|
43
+ block.call(MetaData.new(self, metadata), payload)
44
+ end
45
+ end
46
+
47
+ class MetaData < Promiscuous::AMQP::Bunny::Subscriber::MetaData
48
+ def initialize(subscriber, metadata)
49
+ @subscriber = subscriber
50
+ @metadata = metadata
51
+ end
52
+
53
+ def ack
54
+ @metadata.ack
55
+ end
46
56
  end
47
57
 
48
58
  def disconnect
@@ -10,6 +10,7 @@ class Promiscuous::AMQP::Null
10
10
  end
11
11
 
12
12
  def publish(options={})
13
+ options[:on_confirm].try(:call)
13
14
  end
14
15
 
15
16
  def open_queue(options={}, &block)
@@ -2,8 +2,8 @@ module Promiscuous::AMQP
2
2
  extend Promiscuous::Autoload
3
3
  autoload :HotBunnies, :Bunny, :Null, :Fake
4
4
 
5
- PUB_EXCHANGE = ENV['PUB_EXCHANGE'] || 'promiscuous'
6
- SUB_EXCHANGE = ENV['SUB_EXCHANGE'] || 'promiscuous'
5
+ LIVE_EXCHANGE = 'promiscuous'
6
+ BOOTSTRAP_EXCHANGE = 'promiscuous.bootstrap'
7
7
 
8
8
  class << self
9
9
  attr_accessor :backend
@@ -12,21 +12,16 @@ module Promiscuous::AMQP
12
12
  def backend=(value)
13
13
  disconnect
14
14
  @backend_class = value.nil? ? nil : "Promiscuous::AMQP::#{value.to_s.camelize.gsub(/amqp/, 'AMQP')}".constantize
15
- connect if @backend_class
16
15
  end
17
16
 
18
- def lost_connection_exception
19
- Promiscuous::Error::Connection.new(:service => :amqp)
17
+ def lost_connection_exception(options={})
18
+ Promiscuous::Error::Connection.new(Promiscuous::Config.publisher_amqp_url, options)
20
19
  end
21
20
 
22
21
  def ensure_connected
23
- raise lost_connection_exception unless connected?
24
- end
22
+ Promiscuous.ensure_connected
25
23
 
26
- def publish(options={})
27
- ensure_connected
28
- Promiscuous.debug "[publish] #{options[:key]} -> #{options[:payload]}"
29
- backend.respond_to?(:async) ? backend.async.publish(options) : backend.publish(options)
24
+ raise lost_connection_exception unless connected?
30
25
  end
31
26
 
32
27
  def connect
@@ -42,7 +37,12 @@ module Promiscuous::AMQP
42
37
  @backend = nil
43
38
  end
44
39
 
45
- delegate :connected?, :to => :backend
40
+ def new_connection(*args)
41
+ ensure_connected
42
+ backend.new_connection(*args)
43
+ end
44
+
45
+ delegate :publish, :connected?, :to => :backend
46
46
 
47
47
  def const_missing(sym)
48
48
  backend_class.const_get(sym)
@@ -18,7 +18,7 @@ class Promiscuous::CLI
18
18
  print_status '----[ Pending Dependencies ]----' + '-' * (100-32)
19
19
  blocked_messages.reverse_each { |msg| print_status msg }
20
20
  end
21
- print_status '-' * 80
21
+ print_status '-' * 100
22
22
  end
23
23
  end
24
24
  end
@@ -26,11 +26,14 @@ class Promiscuous::CLI
26
26
  def trap_exit_signals
27
27
  %w(SIGTERM SIGINT).each do |signal|
28
28
  Signal.trap(signal) do
29
- exit 1 if @stop
30
29
  print_status "Exiting..."
31
- @worker.try(:stop)
32
- @worker = nil
33
- @stop = true
30
+ if @stop
31
+ @worker.try(:show_stop_status)
32
+ else
33
+ @stop = true
34
+ @worker.try(:stop)
35
+ @worker = nil
36
+ end
34
37
  end
35
38
  end
36
39
  end
@@ -46,7 +49,7 @@ class Promiscuous::CLI
46
49
  title = criteria.name
47
50
  title = "#{title}#{' ' * [0, 20 - title.size].max}"
48
51
  bar = ProgressBar.create(:format => '%t |%b>%i| %c/%C %e', :title => title, :total => criteria.count)
49
- criteria.unscoped.each do |doc|
52
+ criteria.each do |doc|
50
53
  break if @stop
51
54
  Promiscuous.context("cli/sync") { doc.promiscuous.sync }
52
55
  bar.increment
@@ -84,13 +87,25 @@ class Promiscuous::CLI
84
87
  end
85
88
  end
86
89
 
87
- puts "Replayed #{@num_msg} messages"
90
+ print_status "Replayed #{@num_msg} messages"
91
+ end
92
+
93
+ def bootstrap
94
+ phase = options[:criterias][0].to_sym
95
+ raise "Subscriber bootstrap must be one of [setup|run|finalize|status]" unless [:setup, :run, :finalize, :status].include?(phase)
96
+ if phase == :setup && criteria = options[:bootstrap_criteria]
97
+ Promiscuous::Publisher::Bootstrap.setup(:models => eval(criteria).to_a)
98
+ else
99
+ Promiscuous::Publisher::Bootstrap.__send__(phase)
100
+ end
88
101
  end
89
102
 
90
103
  def subscribe
91
104
  @worker = Promiscuous::Subscriber::Worker.new
92
105
  @worker.start
93
- print_status "Replicating..."
106
+ Promiscuous::Config.subscriber_threads.tap do |threads|
107
+ print_status "Replicating [#{threads} thread#{'s' if threads > 1}]..."
108
+ end
94
109
  sleep 0.2 until !@worker
95
110
  end
96
111
 
@@ -115,17 +130,18 @@ class Promiscuous::CLI
115
130
 
116
131
  opts.separator ""
117
132
  opts.separator "Actions:"
118
- opts.separator " promiscuous publish \"Model1.where(:updated_at.gt => 1.day.ago)\" Model2 Model3..."
133
+ opts.separator " promiscuous publish \"Model1.where(:updated_at.gt => 1.day.ago)\" [Model2 Model3...]"
134
+ opts.separator " promiscuous publisher_recovery"
119
135
  opts.separator " promiscuous subscribe"
136
+ opts.separator " promiscuous bootstrap phase"
120
137
  opts.separator " promiscuous mocks"
121
138
  opts.separator " promiscuous record logfile"
122
139
  opts.separator " promiscuous replay logfile"
123
- opts.separator " promiscuous publisher_recovery"
124
140
  opts.separator ""
125
141
  opts.separator "Options:"
126
142
 
127
- opts.on "-b", "--bareback", "Bareback mode aka no dependencies. Use with extreme caution" do
128
- Promiscuous::Config.bareback = true
143
+ opts.on "-d", "--no-deps", "Skip dependency tracking (subscribe only option)" do
144
+ Promiscuous::Config.no_deps = true
129
145
  end
130
146
 
131
147
  opts.on "-l", "--require FILE", "File to require to load your app. Don't worry about it with rails" do |file|
@@ -149,9 +165,26 @@ class Promiscuous::CLI
149
165
  Promiscuous::Config.stats_interval = duration.to_f
150
166
  end
151
167
 
152
- opts.on("-h", "--help", "Show this message") do
153
- puts opts
154
- exit
168
+ opts.on "-b", "--bootstrap [pass1|pass2]", "Run subscriber in bootstrap mode" do |mode|
169
+ mode = mode.to_sym
170
+ raise "Subscriber bootstrap must be run in pass1 or pass2 mode" unless [:pass1, :pass2].include?(mode)
171
+ Promiscuous::Config.bootstrap = mode.to_sym
172
+ end
173
+
174
+ opts.on "-t", "--threads [NUM]", "Number of subscriber worker threads to run. Defaults to 10." do |threads|
175
+ Promiscuous::Config.subscriber_threads = threads.to_i
176
+ end
177
+
178
+ opts.on "-c", "--criteria FILE", "Criteria to bootstrap a subset of data" do |criteria|
179
+ options[:bootstrap_criteria] = criteria
180
+ end
181
+
182
+ opts.on "-D", "--daemonize", "Daemonize process" do
183
+ options[:daemonize] = true
184
+ end
185
+
186
+ opts.on "-P", "--pid-file [pid_file]", "Set a pid-file" do |pid_file|
187
+ options[:pid_file] = pid_file
155
188
  end
156
189
 
157
190
  opts.on("-V", "--version", "Show version") do
@@ -167,12 +200,15 @@ class Promiscuous::CLI
167
200
  options[:action] = args.shift.try(:to_sym)
168
201
  options[:criterias] = args
169
202
  options[:log_file] = args.first
203
+ options[:publisher_bootstrap] = args.first
170
204
 
171
205
  case options[:action]
172
- when :publish then raise "Please specify one or more criterias" unless options[:criterias].present?
173
- when :subscribe then raise "Why are you specifying a criteria?" if options[:criterias].present?
174
- when :record then raise "Please specify a log file to record" unless options[:log_file].present?
175
- when :replay then raise "Please specify a log file to replay" unless options[:log_file].present?
206
+ when :publish then raise "Please specify one or more criterias" unless options[:criterias].present?
207
+ when :subscribe then raise "Why are you specifying a criteria?" if options[:criterias].present?
208
+ when :bootstrap then raise "You must specify one of [setup|start|finalize]" unless options[:criterias].present?
209
+ when :record then raise "Please specify a log file to record" unless options[:log_file].present?
210
+ when :replay then raise "Please specify a log file to replay" unless options[:log_file].present?
211
+ when :publisher_bootstrap then raise "Please specify 'on' or 'off'" unless options[:publisher_bootstrap].present?
176
212
  when :publisher_recovery
177
213
  when :mocks
178
214
  else puts parser; exit 1
@@ -203,28 +239,33 @@ class Promiscuous::CLI
203
239
 
204
240
  def boot
205
241
  self.options = parse_args(ARGV)
242
+ daemonize if options[:daemonize]
243
+ write_pid if options[:pid_file]
206
244
  load_app
207
- show_bareback_warnings
208
245
  run
209
246
  end
210
247
 
248
+ def daemonize
249
+ Process.daemon(true)
250
+ end
251
+
252
+ def write_pid
253
+ File.open(options[:pid_file], 'w') do |f|
254
+ f.puts Process.pid
255
+ end
256
+ end
257
+
211
258
  def run
212
259
  trap_signals
213
260
  case options[:action]
214
261
  when :publish then publish
215
262
  when :subscribe then subscribe
263
+ when :bootstrap then bootstrap
216
264
  when :record then record
217
265
  when :replay then replay
218
266
  when :mocks then generate_mocks
219
- when :publisher_recovery then publisher_recovery
220
- end
221
- end
222
-
223
- def show_bareback_warnings
224
- if Promiscuous::Config.bareback == true
225
- print_status "WARNING: --- BAREBACK MODE ----"
226
- print_status "WARNING: You are replicating without protection, you can get out of sync in no time"
227
- print_status "WARNING: --- BAREBACK MODE ----"
267
+ when :publisher_recovery then publisher_recovery
268
+ when :publisher_bootstrap then set_publisher_bootstrap
228
269
  end
229
270
  end
230
271
 
@@ -1,9 +1,11 @@
1
1
  module Promiscuous::Config
2
- mattr_accessor :app, :logger, :error_notifier, :backend, :amqp_url,
3
- :redis_url, :redis_urls, :redis_slave_url, :redis_stats_url,
4
- :stats_interval, :queue_options, :heartbeat, :bareback,
5
- :hash_size, :recovery, :prefetch, :recovery_timeout,
6
- :socket_timeout, :subscriber_threads
2
+ mattr_accessor :app, :bootstrap, :bootstrap_chunk_size, :backend, :amqp_url,
3
+ :publisher_amqp_url, :subscriber_amqp_url, :publisher_exchange,
4
+ :subscriber_exchanges, :queue_name, :queue_options, :redis_url,
5
+ :redis_urls, :redis_stats_url, :stats_interval,
6
+ :socket_timeout, :heartbeat, :no_deps, :hash_size, :recovery,
7
+ :prefetch, :recovery_timeout, :logger, :subscriber_threads,
8
+ :version_field, :error_notifier, :recovery_on_boot
7
9
 
8
10
  def self.backend=(value)
9
11
  @@backend = value
@@ -31,36 +33,45 @@ module Promiscuous::Config
31
33
  def self._configure(&block)
32
34
  block.call(self) if block
33
35
 
34
- self.app ||= Rails.application.class.parent_name.underscore rescue nil if defined?(Rails)
35
- self.amqp_url ||= 'amqp://guest:guest@localhost:5672'
36
- self.redis_url ||= 'redis://localhost/'
37
- self.redis_urls ||= [self.redis_url]
38
- #self.redis_slave_url ||= nil
39
- self.redis_stats_url ||= self.redis_urls.first
40
- self.stats_interval ||= 0
41
- self.socket_timeout ||= 10
42
- self.backend ||= best_amqp_backend
43
- self.queue_options ||= {:durable => true, :arguments => {'x-ha-policy' => 'all'}}
44
- self.heartbeat ||= 60
45
- self.bareback ||= false
46
- self.hash_size ||= 2**20 # one million keys ~ 200Mb.
47
- self.recovery ||= false
48
- self.prefetch ||= 1000
49
- self.recovery_timeout ||= 10
50
- self.logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
51
- self.subscriber_threads ||= 10
36
+ self.app ||= Rails.application.class.parent_name.underscore rescue nil if defined?(Rails)
37
+ self.bootstrap ||= false
38
+ self.bootstrap_chunk_size ||= 10000
39
+ self.backend ||= best_amqp_backend
40
+ self.amqp_url ||= 'amqp://guest:guest@localhost:5672'
41
+ self.publisher_amqp_url ||= self.amqp_url
42
+ self.subscriber_amqp_url ||= self.amqp_url
43
+ self.publisher_exchange ||= Promiscuous::AMQP::LIVE_EXCHANGE
44
+ self.subscriber_exchanges ||= [Promiscuous::AMQP::LIVE_EXCHANGE]
45
+ self.queue_name ||= "#{self.app}.promiscuous"
46
+ self.queue_options ||= {:durable => true, :arguments => {'x-ha-policy' => 'all'}}
47
+ self.redis_url ||= 'redis://localhost/'
48
+ self.redis_urls ||= [self.redis_url]
49
+ # TODO self.redis_slave_url ||= nil
50
+ self.redis_stats_url ||= self.redis_urls.first
51
+ self.stats_interval ||= 0
52
+ self.socket_timeout ||= 10
53
+ self.heartbeat ||= 60
54
+ self.no_deps ||= false
55
+ self.hash_size ||= 2**20 # one million keys ~ 200Mb.
56
+ self.recovery ||= false
57
+ self.prefetch ||= self.bootstrap ? 10000000 : 1000
58
+ self.recovery_timeout ||= 10
59
+ self.logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
60
+ self.subscriber_threads ||= 10
61
+ self.error_notifier ||= proc {}
62
+ self.version_field ||= '_v'
63
+ self.recovery_on_boot = true if self.recovery_on_boot.nil?
52
64
  end
53
65
 
54
66
  def self.configure(&block)
55
- self._configure(&block)
67
+ reconnect_if_connected do
68
+ self._configure(&block)
56
69
 
57
- unless self.app
58
- raise "Promiscuous.configure: please give a name to your app with \"config.app = 'your_app_name'\""
70
+ unless self.app
71
+ raise "Promiscuous.configure: please give a name to your app with \"config.app = 'your_app_name'\""
72
+ end
59
73
  end
60
74
 
61
- # amqp connection is done in when setting the backend
62
- Promiscuous::Redis.connect
63
-
64
75
  hook_fork
65
76
  end
66
77
 
@@ -71,6 +82,8 @@ module Promiscuous::Config
71
82
  alias_method :fork_without_promiscuous, :fork
72
83
 
73
84
  def fork(&block)
85
+ return fork_without_promiscuous(&block) unless Promiscuous.should_be_connected?
86
+
74
87
  Promiscuous.disconnect
75
88
  pid = if block
76
89
  fork_without_promiscuous do
@@ -97,4 +110,16 @@ module Promiscuous::Config
97
110
  def self.configured?
98
111
  self.app != nil
99
112
  end
113
+
114
+ private
115
+
116
+ def self.reconnect_if_connected(&block)
117
+ if Promiscuous.should_be_connected?
118
+ Promiscuous.disconnect
119
+ yield
120
+ Promiscuous.connect
121
+ else
122
+ yield
123
+ end
124
+ end
100
125
  end
@@ -3,7 +3,7 @@ module Promiscuous::Convenience
3
3
 
4
4
  def without_promiscuous
5
5
  raise "No block given" unless block_given?
6
- old_disabled, Promiscuous.disabled = Promiscuous.disabled, true
6
+ old_disabled, Promiscuous.disabled = Promiscuous.disabled?, true
7
7
  yield
8
8
  ensure
9
9
  Promiscuous.disabled = old_disabled
@@ -1,9 +1,14 @@
1
1
  require 'fnv'
2
2
 
3
3
  class Promiscuous::Dependency
4
- attr_accessor :internal_key, :version
4
+ attr_accessor :internal_key, :version, :type
5
5
 
6
6
  def initialize(*args)
7
+ options = args.extract_options!
8
+ @type = options[:type]
9
+ @owner = options[:owner]
10
+ @dont_hash = options[:dont_hash]
11
+
7
12
  @internal_key = args.join('/')
8
13
 
9
14
  if @internal_key =~ /^[0-9]+$/
@@ -16,10 +21,24 @@ class Promiscuous::Dependency
16
21
  # We hash dependencies to have a O(1) memory footprint in Redis.
17
22
  # The hashing needs to be deterministic across instances in order to
18
23
  # function properly.
19
- @internal_key = @hash % Promiscuous::Config.hash_size.to_i
20
- @hash = @internal_key
24
+ @hash = @hash % Promiscuous::Config.hash_size.to_i
25
+ @internal_key = @hash unless @dont_hash
21
26
  end
22
27
  end
28
+
29
+ if @owner
30
+ @internal_key = "#{@owner}:#{@internal_key}"
31
+ end
32
+ end
33
+
34
+ def read?
35
+ raise "Type not set" unless @type
36
+ @type == :read
37
+ end
38
+
39
+ def write?
40
+ raise "Type not set" unless @type
41
+ @type == :write
23
42
  end
24
43
 
25
44
  def key(role)
@@ -35,10 +54,10 @@ class Promiscuous::Dependency
35
54
  @version ? [@internal_key, @version].join(':') : @internal_key
36
55
  end
37
56
 
38
- def self.parse(payload)
57
+ def self.parse(payload, options={})
39
58
  case payload
40
- when /^(.+):([0-9]+)$/ then new($1).tap { |d| d.version = $2.to_i }
41
- when /^(.+)$/ then new($1)
59
+ when /^(.+):([0-9]+)$/ then new($1, options).tap { |d| d.version = $2.to_i }
60
+ when /^(.+)$/ then new($1, options)
42
61
  end
43
62
  end
44
63