promiscuous 0.100.5 → 1.0.0.beta1
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 +5 -1
- data/lib/promiscuous/config.rb +6 -5
- data/lib/promiscuous/dsl.rb +0 -4
- data/lib/promiscuous/loader.rb +0 -5
- data/lib/promiscuous/mongoid.rb +15 -5
- data/lib/promiscuous/publisher.rb +1 -1
- data/lib/promiscuous/publisher/model/active_record.rb +6 -1
- data/lib/promiscuous/publisher/model/base.rb +8 -11
- data/lib/promiscuous/publisher/model/mock.rb +2 -2
- data/lib/promiscuous/publisher/model/mongoid.rb +3 -4
- data/lib/promiscuous/publisher/operation/active_record.rb +13 -69
- data/lib/promiscuous/publisher/operation/atomic.rb +15 -158
- data/lib/promiscuous/publisher/operation/base.rb +13 -381
- data/lib/promiscuous/publisher/operation/ephemeral.rb +12 -8
- data/lib/promiscuous/publisher/operation/mongoid.rb +22 -92
- data/lib/promiscuous/publisher/operation/non_persistent.rb +0 -9
- data/lib/promiscuous/publisher/operation/proxy_for_query.rb +8 -6
- data/lib/promiscuous/publisher/operation/transaction.rb +4 -56
- data/lib/promiscuous/publisher/transport.rb +14 -0
- data/lib/promiscuous/publisher/transport/batch.rb +138 -0
- data/lib/promiscuous/publisher/transport/persistence.rb +14 -0
- data/lib/promiscuous/publisher/transport/persistence/active_record.rb +33 -0
- data/lib/promiscuous/publisher/transport/persistence/mongoid.rb +22 -0
- data/lib/promiscuous/publisher/transport/worker.rb +36 -0
- data/lib/promiscuous/publisher/worker.rb +3 -12
- data/lib/promiscuous/redis.rb +5 -0
- data/lib/promiscuous/subscriber/message.rb +1 -29
- data/lib/promiscuous/subscriber/model/base.rb +3 -2
- data/lib/promiscuous/subscriber/model/mongoid.rb +16 -1
- data/lib/promiscuous/subscriber/model/observer.rb +0 -1
- data/lib/promiscuous/subscriber/operation.rb +9 -3
- data/lib/promiscuous/subscriber/unit_of_work.rb +7 -7
- data/lib/promiscuous/subscriber/worker/eventual_destroyer.rb +1 -1
- data/lib/promiscuous/version.rb +1 -1
- metadata +39 -35
- data/lib/promiscuous/dependency.rb +0 -78
- data/lib/promiscuous/error/dependency.rb +0 -116
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9049ba8a421c1866cee586689ed5927acebc1663
|
4
|
+
data.tar.gz: 7dcb69b1cc9e47e3878fd6cd65c55481183069df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0f7e2f082d0684b9c0e8ce7bb6b58a38eb1ec40d2108ab839c295384707e60e704a681ac015702c7ad26ffe6ff40879d38877c86e15512a9b872dc7aae5646e
|
7
|
+
data.tar.gz: a3f0fe71f03c4acca822c6a71164b4b88fd1381b0cd83309ff94a703e6cf60aece9e022e8e6b58083296359a4d81c26ba7da7a5a69eda92ca4fa0fb99330d364
|
data/lib/promiscuous.rb
CHANGED
data/lib/promiscuous/config.rb
CHANGED
@@ -4,8 +4,8 @@ module Promiscuous::Config
|
|
4
4
|
:subscriber_exchanges, :queue_name, :queue_options, :redis_url,
|
5
5
|
:redis_urls, :redis_stats_url, :stats_interval,
|
6
6
|
:socket_timeout, :heartbeat, :hash_size,
|
7
|
-
:prefetch, :recovery_timeout, :logger, :subscriber_threads,
|
8
|
-
:version_field, :error_notifier, :
|
7
|
+
:prefetch, :recovery_timeout, :recovery_interval, :logger, :subscriber_threads,
|
8
|
+
:version_field, :error_notifier, :transport_collection,
|
9
9
|
:on_stats, :max_retries, :generation, :destroy_timeout, :destroy_check_interval
|
10
10
|
|
11
11
|
def self.backend=(value)
|
@@ -52,15 +52,16 @@ module Promiscuous::Config
|
|
52
52
|
self.heartbeat ||= 60
|
53
53
|
self.hash_size ||= 2**20 # one million keys ~ 200Mb.
|
54
54
|
self.prefetch ||= 1000
|
55
|
-
self.recovery_timeout ||= 10
|
55
|
+
self.recovery_timeout ||= 10.seconds
|
56
|
+
self.recovery_interval ||= 5.seconds
|
56
57
|
self.logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDERR).tap { |l| l.level = Logger::WARN }
|
57
58
|
self.subscriber_threads ||= 10
|
58
59
|
self.error_notifier ||= proc {}
|
59
60
|
self.version_field ||= '_v'
|
60
|
-
self.
|
61
|
+
self.transport_collection ||= '_promiscuous'
|
61
62
|
self.on_stats ||= proc { |rate, latency| }
|
62
63
|
self.max_retries ||= defined?(Rails) ? Rails.env.production? ? 10 : 0 : 10
|
63
|
-
self.generation ||=
|
64
|
+
self.generation ||= 0
|
64
65
|
self.destroy_timeout ||= 1.hour
|
65
66
|
self.destroy_check_interval ||= 10.minutes
|
66
67
|
end
|
data/lib/promiscuous/dsl.rb
CHANGED
data/lib/promiscuous/loader.rb
CHANGED
@@ -6,11 +6,6 @@ module Promiscuous::Loader
|
|
6
6
|
file = defined?(Rails) ? Rails.root.join(file_name) : File.join('.', file_name)
|
7
7
|
load file if File.exists?(file)
|
8
8
|
end
|
9
|
-
|
10
|
-
# A one shot recovery on boot
|
11
|
-
if Promiscuous::Config.recovery_on_boot
|
12
|
-
Promiscuous::Publisher::Worker.new.try_recover
|
13
|
-
end
|
14
9
|
end
|
15
10
|
|
16
11
|
def self.cleanup
|
data/lib/promiscuous/mongoid.rb
CHANGED
@@ -1,7 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
if defined? Moped::BSON
|
2
|
+
class Moped::BSON::ObjectId
|
3
|
+
# No {"$oid": "123"}, it's horrible.
|
4
|
+
# TODO Document this shit.
|
5
|
+
def to_json(*args)
|
6
|
+
"\"#{to_s}\""
|
7
|
+
end
|
8
|
+
end
|
9
|
+
else
|
10
|
+
module BSON
|
11
|
+
class ObjectId
|
12
|
+
def as_json(options = {})
|
13
|
+
to_s
|
14
|
+
end
|
15
|
+
end
|
6
16
|
end
|
7
17
|
end
|
@@ -11,7 +11,12 @@ module Promiscuous::Publisher::Model::ActiveRecord
|
|
11
11
|
|
12
12
|
def belongs_to(*args, &block)
|
13
13
|
super.tap do |association|
|
14
|
-
|
14
|
+
fk = if association.is_a?(Hash)
|
15
|
+
association.values.first.foreign_key # ActiveRecord 4x
|
16
|
+
else
|
17
|
+
association.foreign_key # ActiveRecord 3x
|
18
|
+
end
|
19
|
+
publish(fk) if self.in_publish_block?
|
15
20
|
end
|
16
21
|
end
|
17
22
|
end
|
@@ -36,14 +36,17 @@ module Promiscuous::Publisher::Model::Base
|
|
36
36
|
value
|
37
37
|
end
|
38
38
|
|
39
|
-
def get_dependency
|
40
|
-
@collection ||= @instance.class.promiscuous_collection_name
|
41
|
-
Promiscuous::Dependency.new(@collection, :id, id)
|
42
|
-
end
|
43
|
-
|
44
39
|
def id
|
45
40
|
@instance.id
|
46
41
|
end
|
42
|
+
|
43
|
+
def sync(options={}, &block)
|
44
|
+
raise "Model cannot be dirty (have changes) when syncing" if @instance.changed?
|
45
|
+
|
46
|
+
# We can use the ephemeral because both are mongoid and ephemerals are atomic operations.
|
47
|
+
Promiscuous::Publisher::Operation::Ephemeral.new(:instance => @instance, :operation => :update).execute
|
48
|
+
end
|
49
|
+
|
47
50
|
end
|
48
51
|
|
49
52
|
class PromiscuousMethods
|
@@ -55,12 +58,6 @@ module Promiscuous::Publisher::Model::Base
|
|
55
58
|
@promiscuous ||= self.class.const_get(:PromiscuousMethods).new(self)
|
56
59
|
end
|
57
60
|
|
58
|
-
def valid?(*args)
|
59
|
-
# Validations are not dependencies
|
60
|
-
# TODO we should yell if the user is trying to write
|
61
|
-
without_promiscuous { super }
|
62
|
-
end
|
63
|
-
|
64
61
|
module ClassMethods
|
65
62
|
# all methods are virtual
|
66
63
|
|
@@ -28,9 +28,9 @@ module Promiscuous::Publisher::Model::Mock
|
|
28
28
|
op = Promiscuous::Publisher::Operation::Ephemeral.new(:instance => self, :operation => operation)
|
29
29
|
# TODO FIX the mocks to populate app name, also we need to hook before the
|
30
30
|
# json dump.
|
31
|
-
|
31
|
+
batch = op.create_transport_batch([op])
|
32
32
|
|
33
|
-
Promiscuous::Subscriber::Message.new(payload).process
|
33
|
+
Promiscuous::Subscriber::Message.new(batch.payload).process
|
34
34
|
end
|
35
35
|
|
36
36
|
module ClassMethods
|
@@ -15,6 +15,8 @@ module Promiscuous::Publisher::Model::Mongoid
|
|
15
15
|
raise "Please include Promiscuous::Publisher in the root class of #{self}"
|
16
16
|
end
|
17
17
|
|
18
|
+
field Promiscuous::Config.version_field
|
19
|
+
|
18
20
|
Promiscuous::Publisher::Model::Mongoid.collection_mapping[self.collection.name] = self
|
19
21
|
end
|
20
22
|
|
@@ -23,9 +25,7 @@ module Promiscuous::Publisher::Model::Mongoid
|
|
23
25
|
|
24
26
|
def sync(options={}, &block)
|
25
27
|
raise "Use promiscuous.sync on the parent instance" if @instance.embedded?
|
26
|
-
|
27
|
-
# We can use the ephemeral because both are mongoid and ephemerals are atomic operations.
|
28
|
-
Promiscuous::Publisher::Operation::Ephemeral.new(:instance => @instance, :operation => :update).execute
|
28
|
+
super
|
29
29
|
end
|
30
30
|
|
31
31
|
def attribute(attr)
|
@@ -41,7 +41,6 @@ module Promiscuous::Publisher::Model::Mongoid
|
|
41
41
|
end
|
42
42
|
|
43
43
|
module ClassMethods
|
44
|
-
# TODO DRY this up with the publisher side
|
45
44
|
def self.publish_on(method, options={})
|
46
45
|
define_method(method) do |name, *args, &block|
|
47
46
|
super(name, *args, &block)
|
@@ -1,46 +1,4 @@
|
|
1
1
|
class ActiveRecord::Base
|
2
|
-
module PostgresSQL2PCExtensions
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
def prepare_db_transaction
|
6
|
-
execute("PREPARE TRANSACTION '#{quote_string(@current_transaction_id)}'")
|
7
|
-
end
|
8
|
-
|
9
|
-
def commit_prepared_db_transaction(xid)
|
10
|
-
# We might always be racing with another instance, these sort of errors
|
11
|
-
# are spurious.
|
12
|
-
execute("COMMIT PREPARED '#{quote_string(xid)}'")
|
13
|
-
rescue Exception => e
|
14
|
-
raise unless e.message =~ /^PG::UndefinedObject/
|
15
|
-
end
|
16
|
-
|
17
|
-
def rollback_prepared_db_transaction(xid, options={})
|
18
|
-
execute("ROLLBACK PREPARED '#{quote_string(xid)}'")
|
19
|
-
rescue Exception => e
|
20
|
-
raise unless e.message =~ /^PG::UndefinedObject/
|
21
|
-
end
|
22
|
-
|
23
|
-
included do
|
24
|
-
# We want to make sure that we never block the database by having
|
25
|
-
# uncommitted transactions.
|
26
|
-
Promiscuous::Publisher::Operation::Base.register_recovery_mechanism do
|
27
|
-
connection = ActiveRecord::Base.connection
|
28
|
-
db_name = connection.current_database
|
29
|
-
|
30
|
-
# We wait twice the time of expiration, to allow a better recovery scenario.
|
31
|
-
expire_duration = 2 * Promiscuous::Publisher::Operation::Base.lock_options[:expire]
|
32
|
-
|
33
|
-
q = "SELECT gid FROM pg_prepared_xacts " +
|
34
|
-
"WHERE database = '#{db_name}' " +
|
35
|
-
"AND prepared < current_timestamp + #{expire_duration} * interval '1 second'"
|
36
|
-
|
37
|
-
connection.exec_query(q, "Promiscuous Recovery").each do |tx|
|
38
|
-
ActiveRecord::Base::PromiscuousTransaction.recover_transaction(connection, tx['gid'])
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
2
|
class << self
|
45
3
|
alias_method :connection_without_promiscuous, :connection
|
46
4
|
|
@@ -50,10 +8,6 @@ class ActiveRecord::Base
|
|
50
8
|
connection.class.class_eval do
|
51
9
|
attr_accessor :current_transaction_id
|
52
10
|
|
53
|
-
if self.name == "ActiveRecord::ConnectionAdapters::PostgreSQLAdapter"
|
54
|
-
include ActiveRecord::Base::PostgresSQL2PCExtensions
|
55
|
-
end
|
56
|
-
|
57
11
|
def promiscuous_hook; end
|
58
12
|
|
59
13
|
alias_method :begin_db_transaction_without_promiscuous, :begin_db_transaction
|
@@ -165,7 +119,7 @@ class ActiveRecord::Base
|
|
165
119
|
query.non_instrumented { db_operation.call }
|
166
120
|
query.instrumented do
|
167
121
|
db_operation_and_select.tap do
|
168
|
-
transaction_context.add_write_operation(self) if
|
122
|
+
transaction_context.add_write_operation(self) if @instances.present?
|
169
123
|
end
|
170
124
|
end
|
171
125
|
end
|
@@ -178,7 +132,8 @@ class ActiveRecord::Base
|
|
178
132
|
def operation_payloads
|
179
133
|
@instances.map do |instance|
|
180
134
|
instance.promiscuous.payload(:with_attributes => self.operation.in?([:create, :update])).tap do |payload|
|
181
|
-
payload[:operation]
|
135
|
+
payload[:operation] = self.operation
|
136
|
+
payload[:version] = instance.__send__(Promiscuous::Config.version_field)
|
182
137
|
end
|
183
138
|
end
|
184
139
|
end
|
@@ -196,9 +151,13 @@ class ActiveRecord::Base
|
|
196
151
|
|
197
152
|
def db_operation_and_select
|
198
153
|
# XXX This is only supported by Postgres and should be in the postgres driver
|
199
|
-
|
200
|
-
|
201
|
-
|
154
|
+
@connection.transaction do
|
155
|
+
@connection.exec_insert("#{@connection.to_sql(@arel, @binds)} RETURNING *", @operation_name, @binds).tap do |result|
|
156
|
+
@instances = result.map do |row|
|
157
|
+
instance = model.instantiate(row)
|
158
|
+
instance
|
159
|
+
end
|
160
|
+
end
|
202
161
|
end
|
203
162
|
# TODO Use correct primary key
|
204
163
|
@instances.first.id
|
@@ -234,6 +193,8 @@ class ActiveRecord::Base
|
|
234
193
|
|
235
194
|
def db_operation_and_select
|
236
195
|
# TODO this should be in the postgres driver (to also leverage the cache)
|
196
|
+
@arel.ast.values << Arel::Nodes::SqlLiteral.new("\"#{Promiscuous::Config.version_field}\" = COALESCE(\"#{Promiscuous::Config.version_field}\", 0) + 1")
|
197
|
+
|
237
198
|
@connection.exec_query("#{@connection.to_sql(@arel, @binds)} RETURNING *", @operation_name, @binds).tap do |result|
|
238
199
|
@instances = result.map { |row| model.instantiate(row) }
|
239
200
|
end.rows.size
|
@@ -254,7 +215,6 @@ class ActiveRecord::Base
|
|
254
215
|
end
|
255
216
|
|
256
217
|
def db_operation_and_select
|
257
|
-
# TODO We only need the tracked attributes really (most likely, we just need ID)
|
258
218
|
# XXX This is only supported by Postgres.
|
259
219
|
@connection.exec_query("#{@connection.to_sql(@arel, @binds)} RETURNING *", @operation_name, @binds).tap do |result|
|
260
220
|
@instances = result.map { |row| model.instantiate(row) }
|
@@ -272,24 +232,8 @@ class ActiveRecord::Base
|
|
272
232
|
end
|
273
233
|
|
274
234
|
def execute_instrumented(query)
|
275
|
-
query.
|
276
|
-
query.instrumented { @connection.commit_prepared_db_transaction(@transaction_id) }
|
235
|
+
query.instrumented { @connection.commit_db_transaction_without_promiscuous }
|
277
236
|
super
|
278
237
|
end
|
279
|
-
|
280
|
-
def self.recover_transaction(connection, transaction_id)
|
281
|
-
op = new(:connection => connection, :transaction_id => transaction_id)
|
282
|
-
# Getting the lock will trigger the real recovery mechanism
|
283
|
-
if op.acquire_op_lock
|
284
|
-
op.release_op_lock
|
285
|
-
end
|
286
|
-
|
287
|
-
# In the event where the recovery payload wasn't found, we must roll back.
|
288
|
-
# If the operation was recoverable, but couldn't be recovered, an
|
289
|
-
# exception would be thrown, so we won't roll it back by mistake.
|
290
|
-
# If the operation was recovered, the roll back will result in an error,
|
291
|
-
# which is fine.
|
292
|
-
connection.rollback_prepared_db_transaction(transaction_id)
|
293
|
-
end
|
294
238
|
end
|
295
239
|
end
|
@@ -2,176 +2,33 @@ class Promiscuous::Publisher::Operation::Atomic < Promiscuous::Publisher::Operat
|
|
2
2
|
# XXX instance can be a selector representation.
|
3
3
|
attr_accessor :instance
|
4
4
|
|
5
|
-
def
|
6
|
-
|
7
|
-
@instance = options[:instance]
|
8
|
-
end
|
9
|
-
|
10
|
-
def acquire_op_lock
|
11
|
-
unless dependency_for_op_lock
|
12
|
-
return unless reload_instance
|
13
|
-
end
|
14
|
-
|
15
|
-
loop do
|
16
|
-
instance_dep = dependency_for_op_lock
|
17
|
-
|
18
|
-
super
|
19
|
-
|
20
|
-
return if operation == :create
|
21
|
-
|
22
|
-
# We need to make sure that the lock we acquired matches our selector.
|
23
|
-
# There is a bit of room for optimization if we know that we don't have
|
24
|
-
# any tracked attributes on the model and our selector is already an id.
|
25
|
-
return unless reload_instance
|
26
|
-
|
27
|
-
# If reload_instance changed the current instance because the selector,
|
28
|
-
# we need to unlock the old instance, lock this new instance, and
|
29
|
-
# retry.
|
30
|
-
return if instance_dep == dependency_for_op_lock
|
31
|
-
|
32
|
-
# XXX What should we do if we are going in a live lock?
|
33
|
-
# Sleep with some jitter?
|
34
|
-
release_op_lock
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def do_database_query(query)
|
39
|
-
case operation
|
40
|
-
when :create
|
41
|
-
# We don't stash the version in the document as we can't have races
|
42
|
-
# on the same document.
|
43
|
-
when :update
|
44
|
-
increment_version_in_document
|
45
|
-
# We are now in the possession of an instance that matches the original
|
46
|
-
# selector. We need to make sure the db query will operate on it,
|
47
|
-
# instead of the original selector.
|
48
|
-
use_id_selector(:use_atomic_version_selector => true)
|
49
|
-
# We need to use an atomic versioned selector to make sure that
|
50
|
-
# if we lose the lock for a long period of time, we don't mess up
|
51
|
-
# the record. Perhaps the operation has been recovered a while ago.
|
52
|
-
when :destroy
|
53
|
-
use_id_selector
|
54
|
-
end
|
55
|
-
|
56
|
-
# The driver is responsible to set instance to the appropriate value.
|
57
|
-
query.call_and_remember_result(:instrumented)
|
58
|
-
|
59
|
-
if query.failed?
|
60
|
-
# If we get an network failure, we should retry later.
|
61
|
-
return if recoverable_failure?(query.exception)
|
62
|
-
@instance = nil
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def yell_about_missing_instance
|
67
|
-
err = "Cannot find document. Database had a dataloss?. Proceeding anyways. #{@recovery_data}"
|
68
|
-
e = Promiscuous::Error::Recovery.new(err)
|
69
|
-
Promiscuous.warn "[recovery] #{e}"
|
70
|
-
Promiscuous::Config.error_notifier.call(e)
|
5
|
+
def instances
|
6
|
+
[@instance].compact
|
71
7
|
end
|
72
8
|
|
73
9
|
def execute_instrumented(query)
|
74
|
-
if
|
75
|
-
|
76
|
-
# this is a problem, but we need to publish.
|
77
|
-
yell_about_missing_instance if @instance.nil?
|
10
|
+
if operation == :destroy
|
11
|
+
fetch_instance
|
78
12
|
else
|
79
|
-
|
80
|
-
|
81
|
-
if @instance.nil?
|
82
|
-
# The selector missed the instance, bailing out.
|
83
|
-
query.call_and_remember_result(:non_instrumented)
|
84
|
-
return
|
85
|
-
end
|
13
|
+
increment_version_in_document
|
86
14
|
end
|
87
15
|
|
88
|
-
|
89
|
-
|
90
|
-
# database. Once incremented, concurrent queries that are reading our
|
91
|
-
# instance will be serialized after our write, even through it may read our
|
92
|
-
# old instance. This is a race that we tolerate.
|
93
|
-
# XXX We also stash the document for create operations, so the recovery can
|
94
|
-
# redo the create to avoid races when instances are getting partitioned.
|
95
|
-
increment_dependencies
|
96
|
-
|
97
|
-
# From this point, if we die, the one expiring our write locks must finish
|
98
|
-
# the publish, either by sending a dummy, or by sending the real instance.
|
99
|
-
# We could have die before or after the database query.
|
100
|
-
|
101
|
-
# We save the versions in the database, as it is our source of truth.
|
102
|
-
# This allow a reconstruction of redis in the face of failures.
|
103
|
-
# We would also need to send a special message to the subscribers to reset
|
104
|
-
# their read counters to the last write version since we would not be able
|
105
|
-
# to restore the read counters (and we don't want to store them because
|
106
|
-
# this would dramatically augment our footprint on the db).
|
107
|
-
#
|
108
|
-
# If we are doing a destroy operation, and redis dies right after, and
|
109
|
-
# we happen to lost contact with rabbitmq, recovery is going to be complex:
|
110
|
-
# we would need to do a diff from the dummy subscriber to see what
|
111
|
-
# documents are missing on our side to be able to resend the destroy
|
112
|
-
# message.
|
113
|
-
|
114
|
-
do_database_query(query) unless @instance.nil?
|
115
|
-
# We take a timestamp right after the write is performed because latency
|
116
|
-
# measurements are performed on the subscriber.
|
117
|
-
record_timestamp
|
118
|
-
|
119
|
-
# This make sure that if the db operation failed because of a network issue
|
120
|
-
# and we got recovered, we don't send anything as we could send a different
|
121
|
-
# message than the recovery mechanism.
|
122
|
-
ensure_op_still_locked
|
123
|
-
|
124
|
-
generate_payload
|
125
|
-
|
126
|
-
# As soon as we unlock the locks, the rescuer will not be able to assume
|
127
|
-
# that the database instance is still pristine, and so we need to stash the
|
128
|
-
# payload in redis. If redis dies, we don't care because it can be
|
129
|
-
# reconstructed. Subscribers can see "compressed" updates.
|
130
|
-
publish_payload_in_redis
|
131
|
-
|
132
|
-
# TODO Performance: merge these 3 redis operations to speed things up.
|
133
|
-
release_op_lock
|
134
|
-
|
135
|
-
# If we die from this point on, a recovery worker can republish our payload
|
136
|
-
# since we queued it in Redis.
|
137
|
-
|
138
|
-
# We don't care if we lost the lock and got recovered, subscribers are
|
139
|
-
# immune to duplicate messages.
|
140
|
-
publish_payload_in_rabbitmq_async
|
141
|
-
end
|
16
|
+
transport_batch = create_transport_batch([self])
|
17
|
+
transport_batch.prepare
|
142
18
|
|
143
|
-
|
144
|
-
@instance.nil? ? [] : [payload_for(@instance)]
|
145
|
-
end
|
146
|
-
|
147
|
-
def query_dependencies
|
148
|
-
dependencies_for(@instance)
|
149
|
-
end
|
19
|
+
query.call_and_remember_result(:instrumented)
|
150
20
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
21
|
+
unless operation == :destroy
|
22
|
+
# Refresh the operation on the batch to include the updated instance
|
23
|
+
# reflecting the executed operation so that we publish the correct data.
|
24
|
+
transport_batch.clear
|
25
|
+
transport_batch.add query.operation.operation, query.operation.instances
|
26
|
+
end
|
157
27
|
|
158
|
-
|
159
|
-
@instance = fetch_instance
|
28
|
+
transport_batch.publish
|
160
29
|
end
|
161
30
|
|
162
31
|
def increment_version_in_document
|
163
|
-
# Overridden to increment version field in the query
|
164
|
-
end
|
165
|
-
|
166
|
-
def use_id_selector(options={})
|
167
|
-
# Overridden to use the {:id => @instance.id} selector.
|
168
|
-
# if the option use_atomic_version_selector is passed, the driver must add
|
169
|
-
# the version_field selector.
|
170
|
-
end
|
171
|
-
|
172
|
-
def recoverable_failure?(exception)
|
173
|
-
# Overridden to tell if the db exception is spurious, like a network
|
174
|
-
# failure.
|
175
32
|
raise
|
176
33
|
end
|
177
34
|
end
|