Skip to content

Commit 23b8282

Browse files
authored
chore: allow changing retry mode for inactive tx (#472)
Allow enabling/disabling automatic retries of transactions after a transaction has been started, but before any statements have been executed. This allows an application to do the following: ``` tx, _ := db.BeginTx(ctx, nil) _, _ = tx.ExecContext(ctx, "set retry_aborts_internally=true") _l _ = tx.ExecContext(ctx, "insert into my_table (id, values) values (1, 'one')") ```
1 parent 12ead7c commit 23b8282

File tree

4 files changed

+74
-3
lines changed

4 files changed

+74
-3
lines changed

conn.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ func (c *conn) SetRetryAbortsInternally(retry bool) error {
290290

291291
func (c *conn) setRetryAbortsInternally(retry bool) (driver.Result, error) {
292292
if c.inTransaction() {
293-
return nil, spanner.ToSpannerError(status.Error(codes.FailedPrecondition, "cannot change retry mode while a transaction is active"))
293+
return c.tx.setRetryAbortsInternally(retry)
294294
}
295295
c.retryAborts = retry
296296
return driver.ResultNoRows, nil

conn_with_mockserver_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,3 +410,41 @@ func TestRunDmlBatch(t *testing.T) {
410410
t.Fatalf("affected mismatch\n Got: %v\nWant: %v", g, w)
411411
}
412412
}
413+
414+
func TestSetRetryAbortsInternallyInInactiveTransaction(t *testing.T) {
415+
t.Parallel()
416+
417+
db, _, teardown := setupTestDBConnection(t)
418+
defer teardown()
419+
ctx := context.Background()
420+
421+
tx, err := db.BeginTx(ctx, &sql.TxOptions{})
422+
if err != nil {
423+
t.Fatal(err)
424+
}
425+
if _, err := tx.ExecContext(ctx, "set retry_aborts_internally = false"); err != nil {
426+
t.Fatal(err)
427+
}
428+
_ = tx.Rollback()
429+
}
430+
431+
func TestSetRetryAbortsInternallyInActiveTransaction(t *testing.T) {
432+
t.Parallel()
433+
434+
db, _, teardown := setupTestDBConnection(t)
435+
defer teardown()
436+
ctx := context.Background()
437+
438+
tx, err := db.BeginTx(ctx, &sql.TxOptions{})
439+
if err != nil {
440+
t.Fatal(err)
441+
}
442+
if _, err := tx.ExecContext(ctx, testutil.UpdateBarSetFoo); err != nil {
443+
t.Fatal(err)
444+
}
445+
_, err = tx.ExecContext(ctx, "set retry_aborts_internally = false")
446+
if g, w := err.Error(), "spanner: code = \"FailedPrecondition\", desc = \"cannot change retry mode while a transaction is active\""; g != w {
447+
t.Fatalf("error mismatch\n Got: %v\nWant: %v", g, w)
448+
}
449+
_ = tx.Rollback()
450+
}

driver_with_mockserver_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2673,13 +2673,25 @@ func TestShowAndSetVariableRetryAbortsInternally(t *testing.T) {
26732673
}
26742674
}
26752675

2676-
// Verify that the value cannot be set during a transaction.
2676+
// Verify that the value cannot be set during an active transaction.
26772677
tx, _ := c.BeginTx(ctx, nil)
2678-
defer func() { _ = tx.Rollback() }()
2678+
// Execute a statement to activate the transaction.
2679+
if _, err := c.ExecContext(ctx, testutil.UpdateBarSetFoo); err != nil {
2680+
t.Fatal(err)
2681+
}
26792682
_, err = c.ExecContext(ctx, "SET RETRY_ABORTS_INTERNALLY = TRUE")
26802683
if g, w := spanner.ErrCode(err), codes.FailedPrecondition; g != w {
26812684
t.Fatalf("error code mismatch for setting retry_aborts_internally during a transaction\nGot: %v\nWant: %v", g, w)
26822685
}
2686+
_ = tx.Rollback()
2687+
2688+
// Verify that the value can be set at the start of a transaction
2689+
// before any statements have been executed.
2690+
tx, _ = c.BeginTx(ctx, nil)
2691+
if _, err = c.ExecContext(ctx, "SET RETRY_ABORTS_INTERNALLY = TRUE"); err != nil {
2692+
t.Fatal(err)
2693+
}
2694+
_ = tx.Rollback()
26832695
}
26842696

26852697
func TestPartitionedDml(t *testing.T) {

transaction.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type contextTransaction interface {
5353
AbortBatch() (driver.Result, error)
5454

5555
BufferWrite(ms []*spanner.Mutation) error
56+
57+
setRetryAbortsInternally(retry bool) (driver.Result, error)
5658
}
5759

5860
type rowIterator interface {
@@ -195,6 +197,11 @@ func (tx *readOnlyTransaction) BufferWrite([]*spanner.Mutation) error {
195197
return spanner.ToSpannerError(status.Errorf(codes.FailedPrecondition, "read-only transactions cannot write"))
196198
}
197199

200+
func (tx *readOnlyTransaction) setRetryAbortsInternally(_ bool) (driver.Result, error) {
201+
// no-op, ignore
202+
return driver.ResultNoRows, nil
203+
}
204+
198205
// ErrAbortedDueToConcurrentModification is returned by a read/write transaction
199206
// that was aborted by Cloud Spanner, and where the internal retry attempt
200207
// failed because it detected that the results during the retry were different
@@ -222,6 +229,8 @@ type readWriteTransaction struct {
222229
// rwTx is the underlying Spanner read/write transaction. This transaction
223230
// will be replaced with a new one if the initial transaction is aborted.
224231
rwTx *spanner.ReadWriteStmtBasedTransaction
232+
// active indicates whether at least one statement has been executed on this transaction.
233+
active bool
225234
// batch is any DML batch that is active for this transaction.
226235
batch *batch
227236
close func(commitTs *time.Time, commitErr error)
@@ -391,6 +400,7 @@ func (tx *readWriteTransaction) retry(ctx context.Context) (err error) {
391400
// unless internal retries have been disabled.
392401
func (tx *readWriteTransaction) Commit() (err error) {
393402
tx.logger.Debug("committing transaction")
403+
tx.active = true
394404
if err := tx.maybeRunAutoDmlBatch(tx.ctx); err != nil {
395405
_ = tx.rollback(tx.ctx)
396406
return err
@@ -447,6 +457,7 @@ func (tx *readWriteTransaction) resetForRetry(ctx context.Context) error {
447457
// transaction is aborted during the query or while iterating the returned rows.
448458
func (tx *readWriteTransaction) Query(ctx context.Context, stmt spanner.Statement, execOptions ExecOptions) (rowIterator, error) {
449459
tx.logger.Debug("Query", "stmt", stmt.SQL)
460+
tx.active = true
450461
if err := tx.maybeRunAutoDmlBatch(ctx); err != nil {
451462
return nil, err
452463
}
@@ -478,6 +489,7 @@ func (tx *readWriteTransaction) partitionQuery(ctx context.Context, stmt spanner
478489

479490
func (tx *readWriteTransaction) ExecContext(ctx context.Context, stmt spanner.Statement, statementInfo *statementInfo, options spanner.QueryOptions) (res *result, err error) {
480491
tx.logger.Debug("ExecContext", "stmt", stmt.SQL)
492+
tx.active = true
481493
if tx.batch != nil {
482494
tx.logger.Debug("adding statement to batch")
483495
tx.batch.statements = append(tx.batch.statements, stmt)
@@ -515,6 +527,7 @@ func (tx *readWriteTransaction) StartBatchDML(options spanner.QueryOptions, auto
515527
return nil, spanner.ToSpannerError(status.Errorf(codes.FailedPrecondition, "This transaction already has an active batch."))
516528
}
517529
tx.logger.Debug("starting dml batch in transaction", "automatic", automatic)
530+
tx.active = true
518531
tx.batch = &batch{tp: dml, options: ExecOptions{QueryOptions: options}, automatic: automatic}
519532
return driver.ResultNoRows, nil
520533
}
@@ -629,3 +642,11 @@ func errorsEqualForRetry(err1, err2 error) bool {
629642
}
630643
return false
631644
}
645+
646+
func (tx *readWriteTransaction) setRetryAbortsInternally(retry bool) (driver.Result, error) {
647+
if tx.active {
648+
return nil, spanner.ToSpannerError(status.Error(codes.FailedPrecondition, "cannot change retry mode while a transaction is active"))
649+
}
650+
tx.retryAborts = retry
651+
return driver.ResultNoRows, nil
652+
}

0 commit comments

Comments
 (0)