// Exec executes fn in the context of a distributed transaction. // Execution is controlled by opt (see comments in TxnExecOptions). // // opt is passed to fn, and it's valid for fn to modify opt as it sees // fit during each execution attempt. // // It's valid for txn to be nil (meaning the txn has already aborted) if fn // can handle that. This is useful for continuing transactions that have been // aborted because of an error in a previous batch of statements in the hope // that a ROLLBACK will reset the state. Neither opt.AutoRetry not opt.AutoCommit // can be set in this case. // // If an error is returned, the txn has been aborted. func (txn *Txn) Exec( opt TxnExecOptions, fn func(txn *Txn, opt *TxnExecOptions) *roachpb.Error) *roachpb.Error { // Run fn in a retry loop until we encounter a success or // error condition this loop isn't capable of handling. var pErr *roachpb.Error var retryOptions retry.Options if txn == nil && (opt.AutoRetry || opt.AutoCommit) { panic("asked to retry or commit a txn that is already aborted") } if opt.AutoRetry { retryOptions = txn.db.txnRetryOptions } RetryLoop: for r := retry.Start(retryOptions); r.Next(); { pErr = fn(txn, &opt) if (pErr == nil) && opt.AutoCommit && (txn.Proto.Status == roachpb.PENDING) { // fn succeeded, but didn't commit. pErr = txn.commit(nil) } if pErr == nil { break } // Make sure the txn record that pErr carries is for this txn. // We check only when txn.Proto.ID has been initialized after an initial successful send. if pErr.GetTxn() != nil && txn.Proto.ID != nil { if errTxn := pErr.GetTxn(); !errTxn.Equal(&txn.Proto) { return roachpb.NewErrorf("mismatching transaction record in the error:\n%s\nv.s.\n%s", errTxn, txn.Proto) } } if !opt.AutoRetry { break RetryLoop } switch pErr.TransactionRestart { case roachpb.TransactionRestart_IMMEDIATE: r.Reset() case roachpb.TransactionRestart_BACKOFF: default: break RetryLoop } if log.V(2) { log.Infof("automatically retrying transaction: %s because of error: %s", txn.DebugName(), pErr) } } if txn != nil { // TODO(andrei): don't do Cleanup() on retriable errors here. // Let the sql executor do it. txn.Cleanup(pErr) } if pErr != nil { pErr.StripErrorTransaction() } return pErr }
// Exec executes fn in the context of a distributed transaction. // Execution is controlled by opt (see comments in TxnExecOptions). // // opt is passed to fn, and it's valid for fn to modify opt as it sees // fit during each execution attempt. // // It's valid for txn to be nil (meaning the txn has already aborted) if fn // can handle that. This is useful for continuing transactions that have been // aborted because of an error in a previous batch of statements in the hope // that a ROLLBACK will reset the state. Neither opt.AutoRetry not opt.AutoCommit // can be set in this case. // // When this method returns, txn might be in any state; Exec does not attempt // to clean up the transaction before returning an error. In case of // TransactionAbortedError, txn is reset to a fresh transaction, ready to be // used. // // TODO(andrei): Make Exec() return error; make fn return an error + a retriable // bit. There's no reason to propagate roachpb.Error (protos) above this point. func (txn *Txn) Exec( opt TxnExecOptions, fn func(txn *Txn, opt *TxnExecOptions) *roachpb.Error) *roachpb.Error { // Run fn in a retry loop until we encounter a success or // error condition this loop isn't capable of handling. var pErr *roachpb.Error var retryOptions retry.Options if txn == nil && (opt.AutoRetry || opt.AutoCommit) { panic("asked to retry or commit a txn that is already aborted") } if opt.AutoRetry { retryOptions = txn.db.txnRetryOptions } RetryLoop: for r := retry.Start(retryOptions); r.Next(); { if txn != nil { // If we're looking at a brand new transaction, then communicate // what should be used as initial timestamp for the KV txn created // by TxnCoordSender. if txn.Proto.OrigTimestamp == roachpb.ZeroTimestamp { txn.Proto.OrigTimestamp = opt.MinInitialTimestamp } } pErr = fn(txn, &opt) if txn != nil { txn.retrying = true defer func() { txn.retrying = false }() } if (pErr == nil) && opt.AutoCommit && (txn.Proto.Status == roachpb.PENDING) { // fn succeeded, but didn't commit. pErr = txn.Commit() } if pErr == nil { break } // Make sure the txn record that pErr carries is for this txn. // We check only when txn.Proto.ID has been initialized after an initial successful send. if pErr.GetTxn() != nil && txn.Proto.ID != nil { if errTxn := pErr.GetTxn(); !errTxn.Equal(&txn.Proto) { return roachpb.NewErrorf("mismatching transaction record in the error:\n%s\nv.s.\n%s", errTxn, txn.Proto) } } if !opt.AutoRetry { break RetryLoop } switch pErr.TransactionRestart { case roachpb.TransactionRestart_IMMEDIATE: r.Reset() case roachpb.TransactionRestart_BACKOFF: default: break RetryLoop } if log.V(2) { log.Infof("automatically retrying transaction: %s because of error: %s", txn.DebugName(), pErr) } } if pErr != nil { pErr.StripErrorTransaction() } return pErr }