promiscuous 0.92.0 → 0.100.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/promiscuous.rb +2 -5
  3. data/lib/promiscuous/amqp.rb +1 -2
  4. data/lib/promiscuous/cli.rb +3 -43
  5. data/lib/promiscuous/config.rb +5 -7
  6. data/lib/promiscuous/error/dependency.rb +1 -3
  7. data/lib/promiscuous/publisher/context.rb +1 -1
  8. data/lib/promiscuous/publisher/context/base.rb +3 -34
  9. data/lib/promiscuous/publisher/model/base.rb +5 -25
  10. data/lib/promiscuous/publisher/model/mock.rb +5 -7
  11. data/lib/promiscuous/publisher/operation/active_record.rb +4 -69
  12. data/lib/promiscuous/publisher/operation/atomic.rb +1 -3
  13. data/lib/promiscuous/publisher/operation/base.rb +33 -123
  14. data/lib/promiscuous/publisher/operation/mongoid.rb +0 -67
  15. data/lib/promiscuous/publisher/operation/non_persistent.rb +0 -1
  16. data/lib/promiscuous/publisher/operation/transaction.rb +1 -3
  17. data/lib/promiscuous/railtie.rb +0 -31
  18. data/lib/promiscuous/subscriber.rb +1 -1
  19. data/lib/promiscuous/subscriber/{worker/message.rb → message.rb} +12 -40
  20. data/lib/promiscuous/subscriber/model/active_record.rb +1 -1
  21. data/lib/promiscuous/subscriber/model/base.rb +4 -4
  22. data/lib/promiscuous/subscriber/model/mongoid.rb +3 -3
  23. data/lib/promiscuous/subscriber/operation.rb +74 -3
  24. data/lib/promiscuous/subscriber/unit_of_work.rb +110 -0
  25. data/lib/promiscuous/subscriber/worker.rb +3 -7
  26. data/lib/promiscuous/subscriber/worker/eventual_destroyer.rb +2 -6
  27. data/lib/promiscuous/subscriber/worker/pump.rb +2 -11
  28. data/lib/promiscuous/version.rb +1 -1
  29. metadata +18 -36
  30. data/lib/promiscuous/error/missing_context.rb +0 -29
  31. data/lib/promiscuous/publisher/bootstrap.rb +0 -27
  32. data/lib/promiscuous/publisher/bootstrap/connection.rb +0 -25
  33. data/lib/promiscuous/publisher/bootstrap/data.rb +0 -127
  34. data/lib/promiscuous/publisher/bootstrap/mode.rb +0 -19
  35. data/lib/promiscuous/publisher/bootstrap/status.rb +0 -40
  36. data/lib/promiscuous/publisher/bootstrap/version.rb +0 -46
  37. data/lib/promiscuous/publisher/context/middleware.rb +0 -94
  38. data/lib/promiscuous/resque.rb +0 -12
  39. data/lib/promiscuous/sidekiq.rb +0 -15
  40. data/lib/promiscuous/subscriber/message_processor.rb +0 -4
  41. data/lib/promiscuous/subscriber/message_processor/base.rb +0 -54
  42. data/lib/promiscuous/subscriber/message_processor/bootstrap.rb +0 -17
  43. data/lib/promiscuous/subscriber/message_processor/regular.rb +0 -238
  44. data/lib/promiscuous/subscriber/operation/base.rb +0 -66
  45. data/lib/promiscuous/subscriber/operation/bootstrap.rb +0 -60
  46. data/lib/promiscuous/subscriber/operation/regular.rb +0 -19
  47. data/lib/promiscuous/subscriber/worker/message_synchronizer.rb +0 -333
@@ -1,9 +1,5 @@
1
1
  class Promiscuous::Railtie < Rails::Railtie
2
2
  initializer 'load promiscuous' do
3
- ActiveSupport.on_load(:action_controller) do
4
- include Promiscuous::Middleware::Controller
5
- end
6
-
7
3
  config.after_initialize do
8
4
  Promiscuous::Config.configure unless Promiscuous::Config.configured?
9
5
  Promiscuous::Loader.prepare
@@ -16,31 +12,4 @@ class Promiscuous::Railtie < Rails::Railtie
16
12
  end
17
13
  end
18
14
  end
19
-
20
- # XXX Only Rails 3.x support
21
- console do
22
- class << IRB
23
- alias_method :start_without_promiscuous, :start
24
-
25
- def start(*args, &block)
26
- return start_without_promiscuous(*args, &block) if Promiscuous::Publisher::Context.current
27
- Promiscuous::Middleware.with_context 'rails/console' do
28
- start_without_promiscuous(*args, &block)
29
- end
30
- end
31
- end
32
-
33
- if defined?(Pry)
34
- class << Pry
35
- alias_method :start_without_promiscuous, :start
36
-
37
- def start(*args, &block)
38
- return start_without_promiscuous(*args, &block) if Promiscuous::Publisher::Context.current
39
- Promiscuous::Middleware.with_context 'rails/console' do
40
- start_without_promiscuous(*args, &block)
41
- end
42
- end
43
- end
44
- end
45
- end
46
15
  end
@@ -1,6 +1,6 @@
1
1
  module Promiscuous::Subscriber
2
2
  extend Promiscuous::Autoload
3
- autoload :Worker, :MessageProcessor, :Model, :Operation
3
+ autoload :Worker, :UnitOfWork, :Message, :Model, :Operation, :Message
4
4
 
5
5
  extend ActiveSupport::Concern
6
6
 
@@ -1,4 +1,4 @@
1
- class Promiscuous::Subscriber::Worker::Message
1
+ class Promiscuous::Subscriber::Message
2
2
  attr_accessor :payload, :parsed_payload
3
3
 
4
4
  def initialize(payload, options={})
@@ -11,10 +11,6 @@ class Promiscuous::Subscriber::Worker::Message
11
11
  @parsed_payload ||= MultiJson.load(payload)
12
12
  end
13
13
 
14
- def context
15
- parsed_payload['context']
16
- end
17
-
18
14
  def app
19
15
  parsed_payload['app']
20
16
  end
@@ -30,19 +26,18 @@ class Promiscuous::Subscriber::Worker::Message
30
26
  def dependencies
31
27
  @dependencies ||= begin
32
28
  dependencies = parsed_payload['dependencies'] || {}
33
- deps = dependencies['read'].to_a.map { |dep| Promiscuous::Dependency.parse(dep, :type => :read, :owner => app) } +
34
- dependencies['write'].to_a.map { |dep| Promiscuous::Dependency.parse(dep, :type => :write, :owner => app) }
29
+ deps = dependencies['write'].to_a.map { |dep| Promiscuous::Dependency.parse(dep, :type => :write, :owner => app) }
35
30
 
36
31
  deps
37
32
  end
38
33
  end
39
34
 
40
- def write_dependencies
41
- @write_dependencies ||= dependencies.select(&:write?)
35
+ def types
36
+ @parsed_payload['types']
42
37
  end
43
38
 
44
- def read_dependencies
45
- @read_dependencies ||= dependencies.select(&:read?)
39
+ def write_dependencies
40
+ @write_dependencies ||= dependencies.select(&:write?)
46
41
  end
47
42
 
48
43
  def happens_before_dependencies
@@ -60,12 +55,8 @@ class Promiscuous::Subscriber::Worker::Message
60
55
  dependencies.present?
61
56
  end
62
57
 
63
- def was_during_bootstrap?
64
- !!parsed_payload['was_during_bootstrap']
65
- end
66
-
67
58
  def to_s
68
- "#{app}/#{context} -> #{happens_before_dependencies.join(', ')}"
59
+ "#{app} -> #{write_dependencies.join(', ')}"
69
60
  end
70
61
 
71
62
  def ack
@@ -79,34 +70,15 @@ class Promiscuous::Subscriber::Worker::Message
79
70
  Promiscuous::Config.error_notifier.call(e)
80
71
  end
81
72
 
82
- def postpone
83
- # Only used during bootstrapping
84
- @metadata.postpone
85
- rescue Exception => e
86
- # We don't care if we fail
87
- Promiscuous.warn "[receive] (postpone) Some exception happened, but it's okay: #{e}\n#{e.backtrace.join("\n")}"
88
- Promiscuous::Config.error_notifier.call(e)
89
- end
90
-
91
- def unit_of_work(type, &block)
92
- Promiscuous.context { yield }
93
- ensure
94
- if defined?(ActiveRecord)
95
- ActiveRecord::Base.clear_active_connections!
96
- end
97
- end
98
-
99
73
  def process
100
- unit_of_work(context) do
101
- if Promiscuous::Config.bootstrap
102
- Promiscuous::Subscriber::MessageProcessor::Bootstrap.process(self)
103
- else
104
- Promiscuous::Subscriber::MessageProcessor::Regular.process(self)
105
- end
106
- end
74
+ Promiscuous::Subscriber::UnitOfWork.process(self)
107
75
  rescue Exception => orig_e
108
76
  e = Promiscuous::Error::Subscriber.new(orig_e, :payload => payload)
109
77
  Promiscuous.warn "[receive] #{payload} #{e}\n#{e.backtrace.join("\n")}"
110
78
  Promiscuous::Config.error_notifier.call(e)
79
+ ensure
80
+ if defined?(ActiveRecord)
81
+ ActiveRecord::Base.clear_active_connections!
82
+ end
111
83
  end
112
84
  end
@@ -3,7 +3,7 @@ module Promiscuous::Subscriber::Model::ActiveRecord
3
3
  include Promiscuous::Subscriber::Model::Base
4
4
 
5
5
  included do
6
- if Promiscuous::Config.consistency == :eventual && !self.columns.collect(&:name).include?("_v")
6
+ if !self.columns.collect(&:name).include?("_v")
7
7
  raise <<-help
8
8
  #{self} must include a _v column. Create the following migration:
9
9
  change_table :#{self.table_name} do |t|
@@ -2,10 +2,10 @@ module Promiscuous::Subscriber::Model::Base
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def __promiscuous_eventual_consistency_update(operation)
5
- return true unless Promiscuous::Config.consistency == :eventual
6
- return true unless operation.message.has_dependencies?
5
+ return true unless self.respond_to?(:attributes)
6
+ return true unless operation.version
7
7
 
8
- version = operation.message_processor.instance_dep.version
8
+ version = operation.version
9
9
  generation = operation.message.generation
10
10
  version = (generation << 50) | version
11
11
 
@@ -30,7 +30,7 @@ module Promiscuous::Subscriber::Model::Base
30
30
  value = payload.attributes[attr]
31
31
  update = true
32
32
 
33
- attr_payload = Promiscuous::Subscriber::Operation::Regular.new(value)
33
+ attr_payload = Promiscuous::Subscriber::Operation.new(value)
34
34
  if model = attr_payload.model
35
35
  # Nested subscriber
36
36
  old_value = __send__(attr)
@@ -9,7 +9,7 @@ module Promiscuous::Subscriber::Model::Mongoid
9
9
  end
10
10
 
11
11
  included do
12
- field :_v if Promiscuous::Config.consistency == :eventual
12
+ field :_v
13
13
  end
14
14
 
15
15
  module ClassMethods
@@ -77,7 +77,7 @@ module Promiscuous::Subscriber::Model::Mongoid
77
77
  new_e['existed'] = true
78
78
  old_e.instance_variable_set(:@keep, true)
79
79
 
80
- payload = Promiscuous::Subscriber::Operation::Regular.new(new_e)
80
+ payload = Promiscuous::Subscriber::Operation.new(new_e)
81
81
  old_e.__promiscuous_update(payload, :old_value => old_e)
82
82
  end
83
83
  end
@@ -89,7 +89,7 @@ module Promiscuous::Subscriber::Model::Mongoid
89
89
 
90
90
  # create all the new ones
91
91
  new_embeddeds.reject { |new_e| new_e['existed'] }.each do |new_e|
92
- payload = Promiscuous::Subscriber::Operation::Regular.new(new_e)
92
+ payload = Promiscuous::Subscriber::Operation.new(new_e)
93
93
  new_e_instance = payload.model.__promiscuous_fetch_new(payload.id)
94
94
  new_e_instance.__promiscuous_update(payload)
95
95
  options[:parent].__send__(old_embeddeds.metadata[:name]) << new_e_instance
@@ -1,4 +1,75 @@
1
- module Promiscuous::Subscriber::Operation
2
- extend Promiscuous::Autoload
3
- autoload :Base, :Regular, :Bootstrap
1
+ class Promiscuous::Subscriber::Operation
2
+ attr_accessor :model, :id, :operation, :attributes, :version
3
+ delegate :message, :to => :unit_of_work
4
+
5
+ def initialize(payload)
6
+ if payload.is_a?(Hash)
7
+ self.id = payload['id']
8
+ self.operation = payload['operation'].try(:to_sym)
9
+ self.attributes = payload['attributes']
10
+ self.version = payload['version']
11
+ self.model = self.get_subscribed_model(payload) if payload['types']
12
+ end
13
+ end
14
+
15
+ def get_subscribed_model(payload)
16
+ [message.app, '*'].each do |app|
17
+ app_mapping = Promiscuous::Subscriber::Model.mapping[app] || {}
18
+ payload['types'].to_a.each do |ancestor|
19
+ model = app_mapping[ancestor]
20
+ return model if model
21
+ end
22
+ end
23
+ nil
24
+ end
25
+
26
+ def warn(msg)
27
+ Promiscuous.warn "[receive] #{msg} #{message.payload}"
28
+ end
29
+
30
+ def create(options={})
31
+ model.__promiscuous_fetch_new(id).tap do |instance|
32
+ instance.__promiscuous_update(self)
33
+ instance.save!
34
+ end
35
+ rescue Exception => e
36
+ if model.__promiscuous_duplicate_key_exception?(e)
37
+ options[:on_already_created] ||= proc { warn "ignoring already created record" }
38
+ options[:on_already_created].call
39
+ else
40
+ raise e
41
+ end
42
+ end
43
+
44
+ def update(should_create_on_failure=true)
45
+ model.__promiscuous_fetch_existing(id).tap do |instance|
46
+ if instance.__promiscuous_eventual_consistency_update(self)
47
+ instance.__promiscuous_update(self)
48
+ instance.save!
49
+ end
50
+ end
51
+ rescue model.__promiscuous_missing_record_exception
52
+ warn "upserting"
53
+ create :on_already_created => proc { update(false) if should_create_on_failure }
54
+ end
55
+
56
+ def destroy
57
+ if message.dependencies.present?
58
+ Promiscuous::Subscriber::Worker::EventualDestroyer.postpone_destroy(model, id)
59
+ else
60
+ model.__promiscuous_fetch_existing(id).destroy
61
+ end
62
+ end
63
+
64
+ def execute
65
+ case operation
66
+ when :create then create
67
+ when :update then update
68
+ when :destroy then destroy
69
+ end
70
+ end
71
+
72
+ def unit_of_work
73
+ @unit_of_work ||= Promiscuous::Subscriber::UnitOfWork.current
74
+ end
4
75
  end
@@ -0,0 +1,110 @@
1
+ class Promiscuous::Subscriber::UnitOfWork
2
+ attr_accessor :message
3
+ delegate :dependencies, :to => :message
4
+
5
+ def initialize(message)
6
+ self.message = message
7
+ end
8
+
9
+ def app
10
+ message.parsed_payload['app']
11
+ end
12
+
13
+ def operations
14
+ message.parsed_payload['operations'].
15
+ each_with_index.
16
+ map { |op, i| Promiscuous::Subscriber::Operation.new(op.merge('version' => message.dependencies[i].try(&:version))) }
17
+ end
18
+
19
+ def self.process(*args)
20
+ raise "Same thread is processing a message?" if self.current
21
+
22
+ begin
23
+ self.current = new(*args)
24
+ self.current.process_message
25
+ ensure
26
+ self.current = nil
27
+ end
28
+ end
29
+
30
+ def self.current
31
+ Thread.current[:promiscuous_message_processor]
32
+ end
33
+
34
+ def self.current=(value)
35
+ Thread.current[:promiscuous_message_processor] = value
36
+ end
37
+
38
+ def process_message
39
+ begin
40
+ on_message
41
+ rescue Exception => e
42
+ @fail_count ||= 0; @fail_count += 1
43
+
44
+ if @fail_count <= Promiscuous::Config.max_retries
45
+ Promiscuous.warn("[receive] #{e.message} #{@fail_count.ordinalize} retry: #{@message}")
46
+ sleep @fail_count ** 2
47
+ process_message
48
+ else
49
+ raise e
50
+ end
51
+ end
52
+ end
53
+
54
+ def instance_dep
55
+ @instance_dep ||= dependencies.first
56
+ end
57
+
58
+ def master_node
59
+ @master_node ||= instance_dep.redis_node
60
+ end
61
+
62
+ LOCK_OPTIONS = { :timeout => 1.5.minute, # after 1.5 minute, we give up
63
+ :sleep => 0.1, # polling every 100ms.
64
+ :expire => 1.minute } # after one minute, we are considered dead
65
+
66
+ def with_instance_locked(&block)
67
+ return yield unless message.has_dependencies?
68
+
69
+ lock_options = LOCK_OPTIONS.merge(:node => master_node)
70
+ mutex = Promiscuous::Redis::Mutex.new(instance_dep.key(:sub).to_s, lock_options)
71
+
72
+ unless mutex.lock
73
+ raise Promiscuous::Error::LockUnavailable.new(mutex.key)
74
+ end
75
+
76
+ begin
77
+ yield
78
+ ensure
79
+ unless mutex.unlock
80
+ # TODO Be safe in case we have a duplicate message and lost the lock on it
81
+ raise "The subscriber lost the lock during its operation. It means that someone else\n"+
82
+ "received a duplicate message, and we got screwed.\n"
83
+ end
84
+ end
85
+ end
86
+
87
+ # XXX Used for hooking into e.g. by promiscuous-newrelic
88
+ def execute_operation(operation)
89
+ operation.execute
90
+ end
91
+
92
+ def on_message
93
+ with_instance_locked do
94
+ with_transaction do
95
+ self.operations.each { |op| execute_operation(op) if op.model }
96
+ end
97
+ end
98
+ message.ack
99
+ end
100
+
101
+ private
102
+
103
+ def with_transaction(&block)
104
+ if defined?(ActiveRecord::Base)
105
+ ActiveRecord::Base.transaction { yield }
106
+ else
107
+ yield
108
+ end
109
+ end
110
+ end
@@ -1,20 +1,17 @@
1
1
  class Promiscuous::Subscriber::Worker
2
2
  extend Promiscuous::Autoload
3
- autoload :Message, :Pump, :MessageSynchronizer, :Runner, :Stats, :Recorder,
4
- :EventualDestroyer
3
+ autoload :Pump, :Runner, :Stats, :Recorder, :EventualDestroyer
5
4
 
6
- attr_accessor :message_synchronizer, :pump, :runner, :stats, :eventual_destroyer
5
+ attr_accessor :pump, :runner, :stats, :eventual_destroyer
7
6
 
8
7
  def initialize
9
- @message_synchronizer = MessageSynchronizer.new(self)
10
8
  @pump = Pump.new(self)
11
9
  @runner = Runner.new(self)
12
10
  @stats = Stats.new
13
- @eventual_destroyer = EventualDestroyer.new if Promiscuous::Config.consistency == :eventual
11
+ @eventual_destroyer = EventualDestroyer.new
14
12
  end
15
13
 
16
14
  def start
17
- @message_synchronizer.connect
18
15
  @pump.connect
19
16
  @runner.start
20
17
  @stats.connect
@@ -25,7 +22,6 @@ class Promiscuous::Subscriber::Worker
25
22
  @stats.disconnect
26
23
  @runner.stop
27
24
  @pump.disconnect
28
- @message_synchronizer.disconnect
29
25
  @eventual_destroyer.try(:stop)
30
26
  end
31
27
 
@@ -8,18 +8,14 @@ class Promiscuous::Subscriber::Worker::EventualDestroyer
8
8
  @thread = nil
9
9
  end
10
10
 
11
- def self.destroy_timeout
12
- 1.hour
13
- end
14
-
15
11
  def self.check_every
16
- (10 + rand(10)).minutes
12
+ Promiscuous::Config.destroy_check_interval + rand(Promiscuous::Config.destroy_check_interval)
17
13
  end
18
14
 
19
15
  def main_loop
20
16
  loop do
21
17
  begin
22
- PendingDestroy.next(self.class.destroy_timeout).each(&:perform)
18
+ PendingDestroy.next(Promiscuous::Config.destroy_timeout).each(&:perform)
23
19
  rescue Exception => e
24
20
  Promiscuous.warn "[eventual destroyer] #{e}\n#{e.backtrace.join("\n")}"
25
21
  Promiscuous::Config.error_notifier.call(e)