File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -475,6 +475,21 @@ func (t *Transaction) Commit() (c *Commit, err error) {
475475// Returns last attempt rollback error if rollback fails even after retries
476476func (t * Transaction ) rollbackWithRetry () error {
477477 var rollbackErr error
478+ // Use a detached context for rollback so that if the parent context is cancelled/timed out,
479+ // the rollback RPCs can still be sent to the server to release locks.
480+ // We set a timeout of 5 seconds on the rollback context.
481+ rollbackCtx , cancel := context .WithTimeout (context .WithoutCancel (t .ctx ), 5 * time .Second )
482+ defer cancel ()
483+
484+ // Temporarily override the transaction context for the rollback operation.
485+ // Since Rollback and rollbackWithRetry are only called once at the end of a transaction,
486+ // this is clean and safe.
487+ origCtx := t .ctx
488+ t .ctx = rollbackCtx
489+ defer func () {
490+ t .ctx = origCtx
491+ }()
492+
478493 retryer := gax .OnCodes (rollbackRetryCodes , txnBackoff )
479494 for rollbackAttempt := 0 ; rollbackAttempt < maxIndividualReqTxnRetry ; rollbackAttempt ++ {
480495 rollbackErr = t .Rollback ()
Original file line number Diff line number Diff line change @@ -881,3 +881,30 @@ func TestRunInTransaction(t *testing.T) {
881881 })
882882 }
883883}
884+
885+ func TestRunInTransaction_ContextCanceled (t * testing.T ) {
886+ ctx , cancel := context .WithCancel (context .Background ())
887+ client , srv , cleanup := newMock (t )
888+ defer cleanup ()
889+
890+ srv .addRPC (& pb.BeginTransactionRequest {ProjectId : mockProjectID }, & pb.BeginTransactionResponse {Transaction : []byte ("tid" )})
891+ // We expect Rollback to be called even though context is canceled.
892+ srv .addRPC (& pb.RollbackRequest {ProjectId : mockProjectID , Transaction : []byte ("tid" )}, & pb.RollbackResponse {})
893+
894+ f := func (tx * Transaction ) error {
895+ cancel () // Cancel the context inside the transaction function
896+ return nil
897+ }
898+
899+ _ , err := client .RunInTransaction (ctx , f )
900+ if err == nil {
901+ t .Fatal ("expected error, got nil" )
902+ }
903+ if status .Code (err ) != codes .Canceled && err != context .Canceled {
904+ t .Errorf ("expected canceled error, got %v" , err )
905+ }
906+
907+ if len (srv .reqItems ) != 0 {
908+ t .Errorf ("mock server has %d remaining expected RPCs (Rollback likely not called)" , len (srv .reqItems ))
909+ }
910+ }
You can’t perform that action at this time.
0 commit comments