promiscuous 0.90.0 → 0.91.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.
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