{"record":{"id":"f1f1cc2bd985f698","repo":"ory/hydra","slug":"database-error-on-committing-or-rolling-back-trans","errorCode":null,"errorMessage":"database error on committing or rolling back transaction: %w","messagePattern":"database error on committing or rolling back transaction: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/popx/transaction.go","lineNumber":118,"sourceCode":"\t\t\t\t\terr = callback(WithTransaction(ctx, cn), cn)\n\t\t\t\t\tvar dberr error\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tdberr = cn.TX.Rollback()\n\t\t\t\t\t\tif errors.Is(dberr, sql.ErrTxDone) {\n\t\t\t\t\t\t\t// Already rolled back by the database (e.g. context cancelled).\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif dberr != nil && dberr.Error() == \"conn closed\" {\n\t\t\t\t\t\t\t// pgx closes the connection on context cancellation before\n\t\t\t\t\t\t\t// database/sql gets a chance to roll back.\n\t\t\t\t\t\t\t// See https://github.com/jackc/pgx/issues/2551\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdberr = cn.TX.Commit()\n\t\t\t\t\t}\n\t\t\t\t\tif dberr != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"database error on committing or rolling back transaction: %w\", dberr)\n\t\t\t\t\t}\n\t\t\t\t\treturn err\n\t\t\t\t}()\n\t\t\t\tif err == nil || !errors.Is(sqlcon.HandleError(err), sqlcon.ErrConcurrentUpdate()) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn err\n\t\t})\n\t}\n\n\t// SQLite and unknown dialects: opts are ignored; use pop's default\n\t// transaction path with concurrent-update retry handling.\n\tvar err error\n\tfor attempt := range MaxTransactionRetries {\n\t\terr = conn.Transaction(func(tx *pop.Connection) error {\n\t\t\treturn callback(WithTransaction(ctx, tx), tx)\n\t\t})","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/popx/transaction.go#L100-L136","documentation":"In popx's transaction wrapper, if Commit (success path) or Rollback (error path) itself fails with a database error other than the tolerated sql.ErrTxDone / 'conn closed' cases, the wrapper wraps that dberr in this message and returns it, obscuring but preserving the commit/rollback failure.","triggerScenarios":"Calling conn.WithTransaction-style wrapped transactions where the connection drops between the callback and Commit/Rollback; database restart or network cut mid-transaction; context cancellation racing with Commit; pgx connection closed unexpectedly.","commonSituations":"Long-running transactions hitting idle timeouts; Kubernetes killing connections during deploys; context deadlines expiring exactly at commit time; transient network blips with Postgres-compatible stores (CockroachDB, YugabyteDB).","solutions":["Check the wrapped %w cause for the underlying driver error (context canceled, connection reset, etc.)","Retry the whole transaction operation — the wrapper already retries concurrent-update (40001) failures, but commit-time connection loss needs an application-level retry","Reduce transaction duration and set appropriate statement/idle timeouts so the DB does not kill the connection mid-commit","Ensure callers pass a context that stays alive until the transaction completes","Inspect DB/proxy logs (PgBouncer, LB idle timeouts) for connection teardown at commit time"],"exampleFix":"// before\nerr := trx(ctx, conn, func(ctx context.Context, tx *pop.Connection) error { ... }) // one-shot, fails on commit drop\n// after\nfor i := 0; i < 3; i++ {\n    if err := trx(ctx, conn, callback); err == nil || !isConnectionError(err) {\n        return err\n    }\n    time.Sleep(backoff(i))\n}","handlingStrategy":"retry","validationCode":"func withinDeadline(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc, error) {\n    if ctx.Err() != nil {\n        return nil, nil, fmt.Errorf(\"context already cancelled before transaction: %w\", ctx.Err())\n    }\n    return context.WithTimeout(ctx, d), nil, nil\n}","typeGuard":null,"tryCatchPattern":"var err error\nfor attempt := 0; attempt < 3; attempt++ {\n    err = withTransaction(ctx, conn, callback)\n    if err == nil {\n        break\n    }\n    var dbErr interface{ Unwrap() error }\n    if !errors.As(err, &dbErr) || !isTransientConnError(errors.Unwrap(err)) {\n        break // permanent error, do not retry\n    }\n    time.Sleep(time.Duration(1<<attempt) * 100 * time.Millisecond)\n}","preventionTips":["Keep transactions short so idle/statement timeouts cannot kill the connection before Commit","Pass a live context — cancelled contexts race with Commit and produce conn-closed failures","Check proxy/LB (PgBouncer, cloud LB) idle timeouts against your longest transactions","Retry at the application level for transient connection errors; only 40001 is retried internally","Monitor the wrapped cause (%w) in logs to distinguish commit vs rollback failures"],"tags":["database","transaction","commit","rollback","go"],"backgroundTag":"transaction-commit-failed","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}