em-pg-client-helper 0.5.1 → 0.6.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.
@@ -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