Skip to content

transaction canceled context may result in ErrInvalidConn instead of context.Canceled error #1690

Closed
@brad-defined

Description

@brad-defined

When executing a context-aware operation during a transaction, different error types are returned depending on when the context is canceled.

In the following example:

               tx, err := db.Begin()
               if err != nil {
                      fmt.Printf("Failed to obtain db connection: %v", err)
                      return
               }
               returnedErrors := make(chan error, 1)
               go func() {
                       _, err := tx.ExecContext(ctx, `INSERT INTO foo VALUES (3, "three")`)
                       returnedErrors <- err
               }()
               cancel()

               err = <-returnedErrors
               switch {
               case errors.Is(err, ErrInvalidConn):
                       fmt.Printf("The context was canceled, but ExecContext got error %v", err)
               case errors.Is(err, context.Canceled):
                       // cool
               case err != nil:
                       fmt.Printf("Got unexpected error %v", err)
               }

               err = tx.Rollback()
               switch {
               case errors.Is(err, ErrInvalidConn):
                      fmt.Printf("The context was canceled, but ROLLBACK got error %v", err)
               case errors.Is(err, context.Canceled):
                       // cool
               case err != nil:
                       fmt.Printf("Got unexpected error %v", err)
               }

The context may be canceled (1) before or during the ExecContext operation, (2) after the ExecContext operation returns, or (3) after the ExecContext operation succeeds but before the context is removed from the watcher.

(1) If the context is canceled before or during ExecContext, ExecContext returns the error as cached by the context watcher, returning context.Canceled.
(2) If the context is canceled after the ExecContext, no errors are returned, everything succeeds.
(3) !bug! If the context is canceled after the ExecContext operation succeeds but before the context is removed from the watcher, then tx.Rollback() or tx.Commit() return ErrInvalidConn.

I argue that the behavior in (3) should be consistent with the behavior in (1) - the error returned by tx.Rollback() or tx.Commit() should be context.Canceled. The connection being closed is a side effect of the root cause that the context passed to ExecContext has been canceled. (This is also a simpler fix than attempting to remove the race of a context being canceled after ExecContext's operation is successfully executed but before the context is removed from the watcher.)

In our application, we trigger alerts on unexpected errors like ErrInvalidConn, but log and do not alert on expected errors like context.Canceled. The Commit/Rollback behavior ends up causing alerts in our application.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions