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.
@@ -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").callback do
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 { |ex| rollback(ex) }.tap { |df| @dg.add(df) }
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
- self.fail(ex)
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
@@ -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
- expect_query(q, args, exec_time, :fail)
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
- ex = expect(mock_conn).
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
@@ -2,7 +2,7 @@ require 'spork'
2
2
 
3
3
  Spork.prefork do
4
4
  require 'bundler'
5
- Bundler.setup(:default, :test)
5
+ Bundler.setup(:default, :development, :test)
6
6
  require 'rspec/core'
7
7
  require 'rspec/mocks'
8
8
 
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.5.1
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: -4325502789198036899
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: -4325502789198036899
273
+ hash: -3912850109456819706
274
274
  requirements: []
275
275
  rubyforge_project:
276
276
  rubygems_version: 1.8.23