promiscuous 0.92.0 → 0.100.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/promiscuous.rb +2 -5
- data/lib/promiscuous/amqp.rb +1 -2
- data/lib/promiscuous/cli.rb +3 -43
- data/lib/promiscuous/config.rb +5 -7
- data/lib/promiscuous/error/dependency.rb +1 -3
- data/lib/promiscuous/publisher/context.rb +1 -1
- data/lib/promiscuous/publisher/context/base.rb +3 -34
- data/lib/promiscuous/publisher/model/base.rb +5 -25
- data/lib/promiscuous/publisher/model/mock.rb +5 -7
- data/lib/promiscuous/publisher/operation/active_record.rb +4 -69
- data/lib/promiscuous/publisher/operation/atomic.rb +1 -3
- data/lib/promiscuous/publisher/operation/base.rb +33 -123
- data/lib/promiscuous/publisher/operation/mongoid.rb +0 -67
- data/lib/promiscuous/publisher/operation/non_persistent.rb +0 -1
- data/lib/promiscuous/publisher/operation/transaction.rb +1 -3
- data/lib/promiscuous/railtie.rb +0 -31
- data/lib/promiscuous/subscriber.rb +1 -1
- data/lib/promiscuous/subscriber/{worker/message.rb → message.rb} +12 -40
- data/lib/promiscuous/subscriber/model/active_record.rb +1 -1
- data/lib/promiscuous/subscriber/model/base.rb +4 -4
- data/lib/promiscuous/subscriber/model/mongoid.rb +3 -3
- data/lib/promiscuous/subscriber/operation.rb +74 -3
- data/lib/promiscuous/subscriber/unit_of_work.rb +110 -0
- data/lib/promiscuous/subscriber/worker.rb +3 -7
- data/lib/promiscuous/subscriber/worker/eventual_destroyer.rb +2 -6
- data/lib/promiscuous/subscriber/worker/pump.rb +2 -11
- data/lib/promiscuous/version.rb +1 -1
- metadata +18 -36
- data/lib/promiscuous/error/missing_context.rb +0 -29
- data/lib/promiscuous/publisher/bootstrap.rb +0 -27
- data/lib/promiscuous/publisher/bootstrap/connection.rb +0 -25
- data/lib/promiscuous/publisher/bootstrap/data.rb +0 -127
- data/lib/promiscuous/publisher/bootstrap/mode.rb +0 -19
- data/lib/promiscuous/publisher/bootstrap/status.rb +0 -40
- data/lib/promiscuous/publisher/bootstrap/version.rb +0 -46
- data/lib/promiscuous/publisher/context/middleware.rb +0 -94
- data/lib/promiscuous/resque.rb +0 -12
- data/lib/promiscuous/sidekiq.rb +0 -15
- data/lib/promiscuous/subscriber/message_processor.rb +0 -4
- data/lib/promiscuous/subscriber/message_processor/base.rb +0 -54
- data/lib/promiscuous/subscriber/message_processor/bootstrap.rb +0 -17
- data/lib/promiscuous/subscriber/message_processor/regular.rb +0 -238
- data/lib/promiscuous/subscriber/operation/base.rb +0 -66
- data/lib/promiscuous/subscriber/operation/bootstrap.rb +0 -60
- data/lib/promiscuous/subscriber/operation/regular.rb +0 -19
- data/lib/promiscuous/subscriber/worker/message_synchronizer.rb +0 -333
data/lib/promiscuous/railtie.rb
CHANGED
@@ -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,4 +1,4 @@
|
|
1
|
-
class Promiscuous::Subscriber::
|
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['
|
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
|
41
|
-
@
|
35
|
+
def types
|
36
|
+
@parsed_payload['types']
|
42
37
|
end
|
43
38
|
|
44
|
-
def
|
45
|
-
@
|
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}
|
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
|
-
|
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
|
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
|
6
|
-
return true unless operation.
|
5
|
+
return true unless self.respond_to?(:attributes)
|
6
|
+
return true unless operation.version
|
7
7
|
|
8
|
-
version = operation.
|
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
|
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
|
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
|
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
|
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
|
-
|
2
|
-
|
3
|
-
|
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 :
|
4
|
-
:EventualDestroyer
|
3
|
+
autoload :Pump, :Runner, :Stats, :Recorder, :EventualDestroyer
|
5
4
|
|
6
|
-
attr_accessor :
|
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
|
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
|
-
|
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(
|
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)
|