{"record":{"id":"88acee9f47596fa5","repo":"dgraph-io/badger","slug":"trying-to-commit-a-discarded-txn","errorCode":null,"errorMessage":"Trying to commit a discarded txn","messagePattern":"Trying to commit a discarded txn","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"txn.go","lineNumber":611,"sourceCode":"\treq, err := txn.db.sendToWriteCh(entries)\n\tif err != nil {\n\t\torc.doneCommit(commitTs)\n\t\treturn nil, err\n\t}\n\tret := func() error {\n\t\terr := req.Wait()\n\t\t// Wait before marking commitTs as done.\n\t\t// We can't defer doneCommit above, because it is being called from a\n\t\t// callback here.\n\t\torc.doneCommit(commitTs)\n\t\treturn err\n\t}\n\treturn ret, nil\n}\n\nfunc (txn *Txn) commitPrecheck() error {\n\tif txn.discarded {\n\t\treturn errors.New(\"Trying to commit a discarded txn\")\n\t}\n\tkeepTogether := true\n\tfor _, e := range txn.pendingWrites {\n\t\tif e.version != 0 {\n\t\t\tkeepTogether = false\n\t\t}\n\t}\n\n\t// If keepTogether is True, it implies transaction markers will be added.\n\t// In that case, commitTs should not be never be zero. This might happen if\n\t// someone uses txn.Commit instead of txn.CommitAt in managed mode.  This\n\t// should happen only in managed mode. In normal mode, keepTogether will\n\t// always be true.\n\tif keepTogether && txn.db.opt.managedTxns && txn.commitTs == 0 {\n\t\treturn errors.New(\"CommitTs cannot be zero. Please use commitAt instead\")\n\t}\n\treturn nil\n}","sourceCodeStart":593,"sourceCodeEnd":629,"githubUrl":"https://github.com/dgraph-io/badger/blob/2a001d466f6b71a917319a1db41f99860e16e269/txn.go#L593-L629","documentation":"commitPrecheck panics with this error when txn.Commit() (or CommitWith) is called on a transaction that has already been discarded via txn.Discard(). Badger transactions are single-use; once discarded they can no longer be committed. The panic happens before any commit work begins.","triggerScenarios":"Calling Discard() (explicitly or via defer) and later calling Commit() on the same Txn; calling Cancel() then Commit(); reusing a Txn after a prior Commit, since Commit also calls Discard internally.","commonSituations":"Double-defer patterns where one defer discards and later code commits; storing a Txn in a struct and committing after a request-scoped discard; committing after txn.Commit already ran (commit implies discard); error paths that Cancel then retry Commit on the same txn.","solutions":["Ensure Commit is called exactly once per Txn, before any Discard/Cancel","Remove the early Discard/defer Discard that runs before Commit, or reorder so Discard happens only on the error path","Do not retry Commit on a discarded Txn — create a new transaction with db.NewTransactionAt/db.NewTransaction instead","Track commit state (e.g. a bool or sync.Once) if the Txn is shared across goroutines"],"exampleFix":"// before\ntxn := db.NewTransaction(true)\ndefer txn.Discard()\n...\ntxn.Commit()\n// after\ntxn := db.NewTransaction(true)\nerr := txn.Commit() // Commit marks txn discarded internally; no separate Discard needed\nif err != nil {\n    txn.Discard()\n    return err\n}","handlingStrategy":"validation","validationCode":"func canCommit(txn *badger.Txn) bool { return txn != nil } // track discarded via your own wrapper, since Txn.discarded is unexported\n// Wrap the txn:\ntype TxnOnce struct { t *badger.Txn; done atomic.Bool }\nfunc (w *TxnOnce) Commit() error {\n    if w.done.Swap(true) { return errors.New(\"txn already committed/discarded\") }\n    return w.t.Commit()\n}","typeGuard":"func isUsable(w *TxnOnce) bool { return !w.done.Load() }","tryCatchPattern":null,"preventionTips":["Use defer txn.Discard() only on paths that do not Commit","Never call Commit twice on one Txn; create a new txn for retries","Wrap txns in a once-style helper if ownership is shared"],"tags":["badger","transaction","double-commit","lifecycle"],"backgroundTag":"transaction-already-discarded","analyzedSha":"2a001d466f6b71a917319a1db41f99860e16e269","analyzedAt":"2026-09-05T13:00:02.264Z","contentChangedAt":"2026-09-05T13:00:02.264Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}