promiscuous 0.33.1 → 0.50.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.
@@ -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