promiscuous 0.92.0 → 0.100.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.
- 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)
|