em-pg-client-helper 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/em-pg-client-helper/transaction.rb +28 -3
- data/spec/db_transaction_spec.rb +53 -9
- data/spec/spec_helper.rb +1 -1
- metadata +3 -3
@@ -10,26 +10,51 @@ class PG::EM::Client::Helper::Transaction
|
|
10
10
|
# This can be `nil` if the txn is in progress, or it will be
|
11
11
|
# true or false to indicate success/failure of the txn
|
12
12
|
@committed = nil
|
13
|
+
@retryable = false
|
13
14
|
|
14
15
|
DeferrableGroup.new do |dg|
|
15
16
|
@dg = dg
|
16
17
|
|
17
18
|
trace_query("BEGIN")
|
18
|
-
@conn.exec_defer("BEGIN").
|
19
|
+
@conn.exec_defer("BEGIN").tap do |df|
|
20
|
+
@dg.add(df)
|
21
|
+
end.callback do
|
19
22
|
begin
|
20
23
|
blk.call(self)
|
21
24
|
rescue StandardError => ex
|
22
25
|
rollback(ex)
|
23
26
|
end
|
24
|
-
end.errback
|
27
|
+
end.errback do |ex|
|
28
|
+
rollback(ex)
|
29
|
+
end
|
25
30
|
end.callback do
|
26
31
|
rollback(RuntimeError.new("txn.commit was not called")) unless @committed
|
27
32
|
self.succeed
|
28
33
|
end.errback do |ex|
|
29
|
-
|
34
|
+
if @retryable and [PG::TRSerializationFailure].include?(ex.class)
|
35
|
+
self.class.new(conn, opts, &blk).callback do
|
36
|
+
self.succeed
|
37
|
+
end.errback do |ex|
|
38
|
+
self.fail(ex)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
self.fail(ex)
|
42
|
+
end
|
30
43
|
end
|
31
44
|
end
|
32
45
|
|
46
|
+
# Mark the transaction as requiring the serializable isolation level.
|
47
|
+
#
|
48
|
+
# @param retryable [TrueClass, FalseClass] Whether or not the transaction
|
49
|
+
# should be retried if some sort of transaction-level failure occurs.
|
50
|
+
# Be careful enabling this, as the entire block will be re-run, including
|
51
|
+
# any code that creates side-effects elsewhere.
|
52
|
+
#
|
53
|
+
def serializable(retryable = false, &blk)
|
54
|
+
@retryable = retryable
|
55
|
+
exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE", &blk)
|
56
|
+
end
|
57
|
+
|
33
58
|
# Signal the database to commit this transaction. You must do this
|
34
59
|
# once you've completed your queries, it won't be called automatically
|
35
60
|
# for you. Once you've committed the transaction, you cannot use it
|
data/spec/db_transaction_spec.rb
CHANGED
@@ -3,21 +3,22 @@ require_relative './spec_helper'
|
|
3
3
|
describe "PG::EM::Client::Helper#db_transaction" do
|
4
4
|
let(:mock_conn) { double(PG::EM::Client) }
|
5
5
|
|
6
|
-
def expect_query_failure(q, args=nil, exec_time = 0.001)
|
7
|
-
|
6
|
+
def expect_query_failure(q, args=nil, err=nil, exec_time = 0.001)
|
7
|
+
err ||= RuntimeError.new("Dummy failure")
|
8
|
+
expect_query(q, args, exec_time, :fail, err)
|
8
9
|
end
|
9
10
|
|
10
|
-
def expect_query(q, args=nil, exec_time = 0.001, disposition = :succeed)
|
11
|
+
def expect_query(q, args=nil, exec_time = 0.001, disposition = :succeed, *disp_opts)
|
11
12
|
df = EM::DefaultDeferrable.new
|
12
13
|
|
13
|
-
|
14
|
-
to receive(:exec_defer)
|
15
|
-
with(*[q, args].compact)
|
16
|
-
and_return(df)
|
17
|
-
ordered
|
14
|
+
expect(mock_conn)
|
15
|
+
.to receive(:exec_defer)
|
16
|
+
.with(*[q, args].compact)
|
17
|
+
.and_return(df)
|
18
|
+
.ordered
|
18
19
|
|
19
20
|
EM.add_timer(exec_time) do
|
20
|
-
df.__send__(disposition)
|
21
|
+
df.__send__(disposition, *disp_opts)
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
@@ -158,4 +159,47 @@ describe "PG::EM::Client::Helper#db_transaction" do
|
|
158
159
|
end
|
159
160
|
end
|
160
161
|
end
|
162
|
+
|
163
|
+
it "retries if it gets an error during the transaction" do
|
164
|
+
in_em do
|
165
|
+
expect_query("BEGIN")
|
166
|
+
expect_query("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE", [])
|
167
|
+
expect_query_failure('INSERT INTO "foo" ("bar") VALUES ($1)', ["baz"], PG::TRSerializationFailure.new("OMFG!"))
|
168
|
+
expect_query("ROLLBACK")
|
169
|
+
expect_query("BEGIN")
|
170
|
+
expect_query("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE", [])
|
171
|
+
expect_query('INSERT INTO "foo" ("bar") VALUES ($1)', ["baz"])
|
172
|
+
expect_query("COMMIT")
|
173
|
+
|
174
|
+
in_transaction do |txn|
|
175
|
+
txn.serializable(true) do
|
176
|
+
txn.insert("foo", :bar => 'baz') do
|
177
|
+
txn.commit
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
it "retries if it gets an error on commit" do
|
185
|
+
in_em do
|
186
|
+
expect_query("BEGIN")
|
187
|
+
expect_query("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE", [])
|
188
|
+
expect_query('INSERT INTO "foo" ("bar") VALUES ($1)', ["baz"])
|
189
|
+
expect_query_failure("COMMIT", nil, PG::TRSerializationFailure.new("OMFG!"))
|
190
|
+
expect_query("ROLLBACK")
|
191
|
+
expect_query("BEGIN")
|
192
|
+
expect_query("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE", [])
|
193
|
+
expect_query('INSERT INTO "foo" ("bar") VALUES ($1)', ["baz"])
|
194
|
+
expect_query("COMMIT")
|
195
|
+
|
196
|
+
in_transaction do |txn|
|
197
|
+
txn.serializable(true) do
|
198
|
+
txn.insert("foo", :bar => 'baz') do
|
199
|
+
txn.commit
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
161
205
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: em-pg-client-helper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -261,7 +261,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
261
261
|
version: '0'
|
262
262
|
segments:
|
263
263
|
- 0
|
264
|
-
hash: -
|
264
|
+
hash: -3912850109456819706
|
265
265
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
266
266
|
none: false
|
267
267
|
requirements:
|
@@ -270,7 +270,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
270
270
|
version: '0'
|
271
271
|
segments:
|
272
272
|
- 0
|
273
|
-
hash: -
|
273
|
+
hash: -3912850109456819706
|
274
274
|
requirements: []
|
275
275
|
rubyforge_project:
|
276
276
|
rubygems_version: 1.8.23
|