promiscuous 0.100.5 → 1.0.0.beta1
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 +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
|