promiscuous 0.33.1 → 0.50.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -27,20 +27,12 @@ module Promiscuous::AMQP::RubyAMQP
27
27
  e = Promiscuous::AMQP.lost_connection_exception
28
28
  Promiscuous.warn "[amqp] #{e}. Reconnecting..."
29
29
  Promiscuous::Config.error_notifier.try(:call, e)
30
-
31
- worker = Promiscuous::Worker.workers.first
32
- worker.message_synchronizer.disconnect if worker
33
-
34
30
  conn.periodically_reconnect(2.seconds)
35
31
  end
36
32
  end
37
33
 
38
34
  connection.on_recovery do |conn|
39
35
  Promiscuous.warn "[amqp] Reconnected"
40
-
41
- worker = Promiscuous::Worker.workers.first
42
- worker.message_synchronizer.reconnect if worker
43
-
44
36
  Promiscuous::AMQP::RubyAMQP.channel.recover
45
37
  end
46
38
 
@@ -1,31 +1,5 @@
1
1
  class Promiscuous::CLI
2
- def replicate(config_options={}, &block)
3
- require 'eventmachine'
4
- require 'em-synchrony'
5
-
6
- EM.synchrony do
7
- trap_signals
8
- Promiscuous::Loader.load_descriptors if defined?(Rails)
9
- force_backend :rubyamqp
10
- block.call
11
- end
12
- end
13
-
14
- def force_backend(backend)
15
- Promiscuous.disconnect
16
- Promiscuous::Config.backend = backend
17
- Promiscuous.connect
18
- end
19
-
20
- def trap_signals
21
- %w(SIGTERM SIGINT).each do |signal|
22
- Signal.trap(signal) do
23
- print_status "Exiting..."
24
- Promiscuous::Worker.kill
25
- EM.stop
26
- end
27
- end
28
-
2
+ def self.trap_signals
29
3
  Signal.trap 'SIGUSR2' do
30
4
  Thread.list.each do |thread|
31
5
  print_status '-' * 80
@@ -38,38 +12,25 @@ class Promiscuous::CLI
38
12
  end
39
13
  end
40
14
  end
15
+ trap_signals
41
16
 
42
- def publish_sync(options={})
43
- print_status "Replicating #{options[:criteria]}..."
17
+ def publish(options={})
18
+ print_status "Publishing #{options[:criteria]}..."
44
19
  criteria = eval(options[:criteria])
45
20
 
46
21
  bar = ProgressBar.create(:format => '%t |%b>%i| %c/%C %e', :title => 'Publishing', :total => criteria.count)
47
22
  criteria.each do |doc|
48
- doc.promiscuous_sync(options)
23
+ doc.promiscuous_sync
49
24
  bar.increment
50
25
  end
51
-
52
- print_status "Done. You may switch your subscriber worker back to regular mode, and delete the sync queues"
53
26
  end
54
27
 
55
28
  def subscribe(options={})
56
- replicate do
57
- Promiscuous::Worker.replicate
58
- print_status "Replicating with #{Promiscuous::Subscriber::AMQP.subscribers.count} subscribers"
59
- end
60
- end
61
-
62
- def subscribe_sync(options={})
63
- replicate do
64
- # Create the regular queue if needed, so we don't lose messages.
65
- Promiscuous::AMQP.open_queue(Promiscuous::Subscriber::Worker.new.queue_bindings)
66
-
67
- print_status "WARNING: --- SYNC MODE ----"
68
- print_status "WARNING: Make sure you are not running the regular subscriber worker (it's racy)"
69
- print_status "WARNING: --- SYNC MODE ----"
70
- Promiscuous::Worker.replicate
71
- print_status "Replicating with #{Promiscuous::Subscriber::AMQP.subscribers.count} subscribers"
72
- end
29
+ Promiscuous::Loader.load_descriptors if defined?(Rails)
30
+ print_status "Replicating with #{Promiscuous::Subscriber::AMQP.subscribers.count} subscribers"
31
+ Promiscuous::Subscriber::Worker.run
32
+ rescue Interrupt
33
+ # SIGINT
73
34
  end
74
35
 
75
36
  def parse_args(args)
@@ -81,20 +42,12 @@ class Promiscuous::CLI
81
42
 
82
43
  opts.separator ""
83
44
  opts.separator "Actions:"
84
- opts.separator " publish (sync only)"
45
+ opts.separator " publish \"Model.where(:shard => 123)\""
85
46
  opts.separator " subscribe"
86
47
  opts.separator ""
87
48
  opts.separator "Options:"
88
49
 
89
- opts.on "-s", "--sync", "Use a separate queue for sychronizing databases" do
90
- options[:personality] = :sync
91
- end
92
-
93
- opts.on "-c", "--criteria CRITERIA", "Published criteria in sync mode. e.g. Member.where(:created_at.gt => 1.day.ago)" do |criteria|
94
- options[:criteria] = criteria
95
- end
96
-
97
- opts.on "-b", "--bareback", "Bareback mode aka continue on error. Use with extreme caution" do
50
+ opts.on "-b", "--bareback", "Bareback mode aka no dependencies. Use with extreme caution" do
98
51
  options[:bareback] = true
99
52
  end
100
53
 
@@ -118,15 +71,23 @@ class Promiscuous::CLI
118
71
  parser.parse!(args)
119
72
 
120
73
  options[:action] = args.shift.try(:to_sym)
121
- raise "Please specify an action (publish or subscribe)" unless options[:action].in? [:publish, :subscribe]
74
+ options[:criteria] = args.shift
122
75
 
123
- if options[:action] == :publish && options[:personality] == :sync
76
+ unless options[:action].in? [:publish, :subscribe]
77
+ puts parser
78
+ exit
79
+ end
80
+
81
+ if options[:action] == :publish
124
82
  raise "Please specify a criteria" unless options[:criteria]
125
83
  else
126
84
  raise "Why are you specifying a criteria?" if options[:criteria]
127
85
  end
128
86
 
129
87
  options
88
+ rescue Exception => e
89
+ puts e
90
+ exit
130
91
  end
131
92
 
132
93
  def load_app(options={})
@@ -142,14 +103,17 @@ class Promiscuous::CLI
142
103
  def run
143
104
  options = parse_args(ARGV)
144
105
  load_app(options)
145
- maybe_warn_bareback(options)
106
+ maybe_run_bareback(options)
146
107
 
147
- # calls publish, publish_sync, subscribe, subscribe_sync
148
- __send__([options[:action], options[:personality]].compact.join('_'), options)
108
+ case options[:action]
109
+ when :publish then publish(options)
110
+ when :subscribe then subscribe(options)
111
+ end
149
112
  end
150
113
 
151
- def maybe_warn_bareback(options)
114
+ def maybe_run_bareback(options)
152
115
  if options[:bareback]
116
+ Promiscuous::Config.bareback = true
153
117
  print_status "WARNING: --- BAREBACK MODE ----"
154
118
  print_status "WARNING: You are replicating without protection, you can get corrupted in no time"
155
119
  print_status "WARNING: --- BAREBACK MODE ----"
@@ -1,5 +1,5 @@
1
1
  module Promiscuous::Config
2
- mattr_accessor :app, :logger, :error_notifier, :backend, :amqp_url, :redis_url, :queue_options, :heartbeat
2
+ mattr_accessor :app, :logger, :error_notifier, :backend, :amqp_url, :redis_url, :queue_options, :heartbeat, :bareback
3
3
 
4
4
  def self.backend=(value)
5
5
  @@backend = value
@@ -43,6 +43,27 @@ module Promiscuous::Publisher::Model::Mongoid
43
43
  end
44
44
  end
45
45
 
46
+ module ModelInstanceMethods
47
+ extend ActiveSupport::Concern
48
+
49
+ def promiscuous_sync
50
+ publisher = self.class.promiscuous_publisher
51
+
52
+ fetch_proc = proc { self.class.with(:consistency => :strong).where(atomic_selector).first }
53
+
54
+ publisher.new(:operation => :update,
55
+ :instance => self,
56
+ :fetch_proc => fetch_proc).commit
57
+ end
58
+ end
59
+
60
+ module ClassMethods
61
+ def setup_class_binding
62
+ super
63
+ klass.__send__(:include, ModelInstanceMethods) if klass
64
+ end
65
+ end
66
+
46
67
  def self.hook_mongoid
47
68
  Moped::Collection.class_eval do
48
69
  alias_method :insert_orig, :insert
@@ -60,7 +60,7 @@ module Promiscuous::Publisher::Model
60
60
  with_lock do
61
61
  update_dependencies
62
62
  begin
63
- ret = yield
63
+ ret = yield if block_given?
64
64
  rescue Exception => e
65
65
  # we must publish something so the subscriber can sync
66
66
  # with the updated dependencies
@@ -81,11 +81,10 @@ module Promiscuous::Publisher::Model
81
81
  ret
82
82
  end
83
83
 
84
-
85
84
  module ClassMethods
86
85
  def setup_class_binding
87
86
  super
88
- Promiscuous::Publisher::Model.klasses << klass
87
+ Promiscuous::Publisher::Model.klasses << klass if klass
89
88
  end
90
89
  end
91
90
  end
@@ -1,8 +1,7 @@
1
1
  class Promiscuous::Subscriber::Worker::Message
2
- attr_accessor :worker, :metadata, :payload, :parsed_payload
2
+ attr_accessor :metadata, :payload, :parsed_payload
3
3
 
4
- def initialize(worker, metadata, payload)
5
- self.worker = worker
4
+ def initialize(metadata, payload)
6
5
  self.metadata = metadata
7
6
  self.payload = payload
8
7
  end
@@ -24,6 +23,7 @@ class Promiscuous::Subscriber::Worker::Message
24
23
  end
25
24
 
26
25
  def has_dependencies?
26
+ return false if Promiscuous::Config.bareback
27
27
  !!global_version
28
28
  end
29
29
 
@@ -37,11 +37,25 @@ class Promiscuous::Subscriber::Worker::Message
37
37
  end
38
38
  end
39
39
 
40
+ def unit_of_work(type, &block)
41
+ # type is used by the new relic agent, by monkey patching.
42
+ # middleware?
43
+ if defined?(Mongoid)
44
+ Mongoid.unit_of_work { yield }
45
+ else
46
+ yield
47
+ end
48
+ ensure
49
+ if defined?(ActiveRecord)
50
+ ActiveRecord::Base.clear_active_connections!
51
+ end
52
+ end
53
+
40
54
  def process
41
- return if worker.stopped?
55
+ #return if worker.stopped?
42
56
 
43
57
  Promiscuous.debug "[receive] #{payload}"
44
- worker.unit_of_work(queue_name) do
58
+ unit_of_work(queue_name) do
45
59
  Promiscuous::Subscriber.process(parsed_payload, :message => self)
46
60
  end
47
61
 
@@ -1,13 +1,9 @@
1
1
  class Promiscuous::Subscriber::Worker::MessageSynchronizer
2
2
  include Celluloid::IO
3
3
 
4
- attr_accessor :worker, :redis
4
+ attr_accessor :redis
5
5
 
6
- def initialize(worker)
7
- self.worker = worker
8
- end
9
-
10
- def start
6
+ def initialize
11
7
  connect
12
8
  main_loop!
13
9
  end
@@ -36,7 +32,7 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer
36
32
  Promiscuous.warn "[redis] #{e}. Reconnecting..."
37
33
  Promiscuous::Config.error_notifier.try(:call, e)
38
34
 
39
- EM.next_tick { worker.pump.stop }
35
+ # TODO stop the pump to unack all messages
40
36
  reconnect_later
41
37
  end
42
38
 
@@ -56,7 +52,7 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer
56
52
  main_loop!
57
53
 
58
54
  Promiscuous.warn "[redis] Reconnected"
59
- EM.next_tick { worker.pump.start }
55
+ # TODO restart the pump
60
56
  end
61
57
  rescue
62
58
  reconnect_later
@@ -94,7 +90,7 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer
94
90
  rescue Exception => e
95
91
  Promiscuous.warn "[redis] #{e} #{e.backtrace.join("\n")}"
96
92
 
97
- Promiscuous::Worker.stop
93
+ #Promiscuous::Worker.stop TODO
98
94
  Promiscuous::Config.error_notifier.try(:call, e)
99
95
  end
100
96
 
@@ -110,15 +106,19 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer
110
106
  # when calling worker.pump.start
111
107
  return unless self.redis
112
108
 
113
- return worker.runners.process!(msg) unless msg.has_dependencies?
109
+ return process_message!(msg) unless msg.has_dependencies?
114
110
 
115
111
  # The message synchronizer only takes care of happens before (>=) dependencies.
116
112
  # The message will handle the skip logic in case of duplicates.
117
113
  on_version Promiscuous::Redis.sub_key('global'), msg.global_version do
118
- worker.runners.process!(msg)
114
+ process_message!(msg)
119
115
  end
120
116
  end
121
117
 
118
+ def process_message!(msg)
119
+ Celluloid::Actor[:runners].process!(msg)
120
+ end
121
+
122
122
  def on_version(key, version, &callback)
123
123
  return unless @subscriptions
124
124
  cb = Subscription::Callback.new(version, callback)
@@ -1,42 +1,71 @@
1
+ require 'eventmachine'
2
+
1
3
  class Promiscuous::Subscriber::Worker::Pump
2
- # TODO Make this celluloid happy
3
- attr_accessor :worker
4
+ include Celluloid
5
+
6
+ def initialize
7
+ # signals do not work when initializing with Celluloid
8
+ # I wish ruby had semaphores, it would make much more sense.
9
+ @initialize_mutex = Mutex.new
10
+ @initialization_done = ConditionVariable.new
11
+
12
+ @em_thread = Thread.new { EM.run { start } }
13
+
14
+ # The event machine thread will unlock us
15
+ wait_for_initialization
16
+ raise @exception if @exception
17
+ end
18
+
19
+ def wait_for_initialization
20
+ @initialize_mutex.synchronize do
21
+ @initialization_done.wait(@initialize_mutex)
22
+ end
23
+ end
24
+
25
+ def finalize_initialization
26
+ @initialize_mutex.synchronize do
27
+ @initialization_done.signal
28
+ end
29
+ end
30
+
31
+ def finalize
32
+ @dont_reconnect = true
33
+ EM.next_tick do
34
+ Promiscuous::AMQP.disconnect
35
+ EM.stop
36
+ end
37
+ @em_thread.join
38
+ rescue
39
+ # Let amqp die like a pro
40
+ end
4
41
 
5
- def initialize(worker)
6
- self.worker = worker
42
+ def force_use_ruby_amqp
43
+ Promiscuous::AMQP.disconnect
44
+ Promiscuous::Config.backend = :rubyamqp
45
+ Promiscuous::AMQP.connect
7
46
  end
8
47
 
9
48
  def start
10
- return if @queue
49
+ force_use_ruby_amqp
11
50
  Promiscuous::AMQP.open_queue(queue_bindings) do |queue|
12
- @queue = queue
13
- @queue.subscribe :ack => true do |metadata, payload|
14
- # we drop the payload if we switched to another queue,
15
- # duplicate messages could hurt us.
16
- process_payload(metadata, payload) if queue == @queue
51
+ queue.subscribe :ack => true do |metadata, payload|
52
+ process_payload(metadata, payload)
17
53
  end
18
54
  end
19
- end
20
-
21
- def stop
22
- queue, @queue = @queue, nil
23
- queue.unsubscribe if queue rescue nil
55
+ rescue Exception => @exception
56
+ ensure
57
+ finalize_initialization
24
58
  end
25
59
 
26
60
  def process_payload(metadata, payload)
27
- msg = Promiscuous::Subscriber::Worker::Message.new(worker, metadata, payload)
28
- worker.message_synchronizer.process_when_ready(msg)
61
+ msg = Promiscuous::Subscriber::Worker::Message.new(metadata, payload)
62
+ Celluloid::Actor[:message_synchronizer].process_when_ready(msg)
29
63
  end
30
64
 
31
65
  def queue_bindings
32
66
  queue_name = "#{Promiscuous::Config.app}.promiscuous"
33
67
  exchange_name = Promiscuous::AMQP::EXCHANGE
34
68
 
35
- if worker.options[:personality]
36
- queue_name += ".#{worker.options[:personality]}"
37
- exchange_name += ".#{worker.options[:personality]}"
38
- end
39
-
40
69
  # We need to subscribe to everything to keep up with the version tracking
41
70
  bindings = ['*']
42
71
  {:exchange_name => exchange_name, :queue_name => queue_name, :bindings => bindings}
@@ -1,59 +1,13 @@
1
- class Promiscuous::Subscriber::Worker
2
- require 'celluloid'
3
- require 'celluloid/io'
1
+ require 'celluloid'
2
+ require 'celluloid/io'
4
3
 
4
+ class Promiscuous::Subscriber::Worker < Celluloid::SupervisionGroup
5
5
  extend Promiscuous::Autoload
6
6
  autoload :Message, :Pump, :MessageSynchronizer, :Runner
7
7
 
8
- attr_accessor :options, :stopped, :pump, :message_synchronizer, :runners
9
- alias_method :stopped?, :stopped
10
-
11
- def initialize(options={})
12
- Celluloid.exception_handler { |e| Promiscuous::Config.error_notifier.try(:call, e) }
13
-
14
- self.options = options
15
- self.stopped = true
16
-
17
- self.pump = Pump.new(self)
18
- end
19
-
20
- def start
21
- return unless self.stopped
22
- self.stopped = false
23
- self.runners = Runner.pool
24
- self.message_synchronizer = MessageSynchronizer.new(self)
25
- self.message_synchronizer.start
26
- self.pump.start
27
- end
28
-
29
- def stop
30
- return if self.stopped
31
- self.pump.stop
32
- if self.message_synchronizer
33
- self.message_synchronizer.stop rescue Celluloid::Task::TerminatedError
34
- self.message_synchronizer = nil
35
- end
36
- if self.runners
37
- self.runners.terminate
38
- self.runners = nil
39
- end
40
- self.stopped = true
41
-
42
- # TODO wait for the runners to finish
43
- sleep 1
44
- end
45
-
46
- def unit_of_work(type, &block)
47
- # type is used by the new relic agent, by monkey patching.
48
- # middleware?
49
- if defined?(Mongoid)
50
- Mongoid.unit_of_work { yield }
51
- else
52
- yield
53
- end
54
- ensure
55
- if defined?(ActiveRecord)
56
- ActiveRecord::Base.clear_active_connections!
57
- end
58
- end
8
+ pool Runner, :as => :runners, :size => 10
9
+ supervise MessageSynchronizer, :as => :message_synchronizer
10
+ supervise Pump, :as => :pump
59
11
  end
12
+
13
+ Celluloid.exception_handler { |e| Promiscuous::Config.error_notifier.try(:call, e) }
@@ -1,3 +1,3 @@
1
1
  module Promiscuous
2
- VERSION = '0.33.1'
2
+ VERSION = '0.50.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.33.1
4
+ version: 0.50.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-03 00:00:00.000000000 Z
13
+ date: 2013-02-04 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -76,22 +76,6 @@ dependencies:
76
76
  - - ~>
77
77
  - !ruby/object:Gem::Version
78
78
  version: 0.9.8
79
- - !ruby/object:Gem::Dependency
80
- name: em-synchrony
81
- requirement: !ruby/object:Gem::Requirement
82
- none: false
83
- requirements:
84
- - - ~>
85
- - !ruby/object:Gem::Version
86
- version: 1.0.3
87
- type: :runtime
88
- prerelease: false
89
- version_requirements: !ruby/object:Gem::Requirement
90
- none: false
91
- requirements:
92
- - - ~>
93
- - !ruby/object:Gem::Version
94
- version: 1.0.3
95
79
  - !ruby/object:Gem::Dependency
96
80
  name: ruby-progressbar
97
81
  requirement: !ruby/object:Gem::Requirement
@@ -182,8 +166,8 @@ extensions: []
182
166
  extra_rdoc_files: []
183
167
  files:
184
168
  - lib/promiscuous/amqp/null.rb
185
- - lib/promiscuous/amqp/ruby_amqp.rb
186
169
  - lib/promiscuous/amqp/bunny.rb
170
+ - lib/promiscuous/amqp/ruby_amqp.rb
187
171
  - lib/promiscuous/common/lint/base.rb
188
172
  - lib/promiscuous/common/class_helpers.rb
189
173
  - lib/promiscuous/common/options.rb
@@ -230,10 +214,10 @@ files:
230
214
  - lib/promiscuous/subscriber/lint.rb
231
215
  - lib/promiscuous/subscriber/mongoid.rb
232
216
  - lib/promiscuous/subscriber/observer.rb
233
- - lib/promiscuous/subscriber/model.rb
234
217
  - lib/promiscuous/subscriber/amqp.rb
235
218
  - lib/promiscuous/subscriber/dummy.rb
236
219
  - lib/promiscuous/subscriber/worker.rb
220
+ - lib/promiscuous/subscriber/model.rb
237
221
  - lib/promiscuous/error/subscriber.rb
238
222
  - lib/promiscuous/error/publisher.rb
239
223
  - lib/promiscuous/error/connection.rb
@@ -245,12 +229,11 @@ files:
245
229
  - lib/promiscuous/railtie.rb
246
230
  - lib/promiscuous/common.rb
247
231
  - lib/promiscuous/publisher.rb
248
- - lib/promiscuous/worker.rb
249
232
  - lib/promiscuous/amqp.rb
250
233
  - lib/promiscuous/subscriber.rb
234
+ - lib/promiscuous/redis.rb
251
235
  - lib/promiscuous/config.rb
252
236
  - lib/promiscuous/cli.rb
253
- - lib/promiscuous/redis.rb
254
237
  - lib/promiscuous/version.rb
255
238
  - lib/promiscuous.rb
256
239
  - bin/promiscuous
@@ -1,22 +0,0 @@
1
- module Promiscuous::Worker
2
- mattr_accessor :workers
3
- self.workers = []
4
-
5
- def self.replicate(options={})
6
- self.workers << Promiscuous::Subscriber::Worker.new(options).tap { |w| w.start }
7
- end
8
-
9
- def self.kill
10
- stop
11
- # TODO FIXME We should wait for them to be idle
12
- workers.clear
13
- end
14
-
15
- def self.stop
16
- workers.each(&:stop)
17
- end
18
-
19
- def self.start
20
- workers.each(&:start)
21
- end
22
- end