{"record":{"id":"b41882eacb380ff4","repo":"dgraph-io/badger","slug":"unclosed-iterator-at-time-of-txn-discard","errorCode":null,"errorMessage":"Unclosed iterator at time of Txn.Discard.","messagePattern":"Unclosed iterator at time of Txn\\.Discard\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"txn.go","lineNumber":513,"sourceCode":"\t\t// the same time. The reads slice is not currently thread-safe and\n\t\t// needs to be locked whenever we mark a key as read.\n\t\ttxn.readsLock.Lock()\n\t\ttxn.reads = append(txn.reads, fp)\n\t\ttxn.readsLock.Unlock()\n\t}\n}\n\n// Discard discards a created transaction. This method is very important and must be called. Commit\n// method calls this internally, however, calling this multiple times doesn't cause any issues. So,\n// this can safely be called via a defer right when transaction is created.\n//\n// NOTE: If any operations are run on a discarded transaction, ErrDiscardedTxn is returned.\nfunc (txn *Txn) Discard() {\n\tif txn.discarded { // Avoid a re-run.\n\t\treturn\n\t}\n\tif txn.numIterators.Load() > 0 {\n\t\tpanic(\"Unclosed iterator at time of Txn.Discard.\")\n\t}\n\ttxn.discarded = true\n\tif !txn.db.orc.isManaged {\n\t\ttxn.db.orc.doneRead(txn)\n\t}\n}\n\nfunc (txn *Txn) commitAndSend() (func() error, error) {\n\torc := txn.db.orc\n\t// Ensure that the order in which we get the commit timestamp is the same as\n\t// the order in which we push these updates to the write channel. So, we\n\t// acquire a writeChLock before getting a commit timestamp, and only release\n\t// it after pushing the entries to it.\n\torc.writeChLock.Lock()\n\tdefer orc.writeChLock.Unlock()\n\n\tcommitTs, conflict := orc.newCommitTs(txn)\n\tif conflict {","sourceCodeStart":495,"sourceCodeEnd":531,"githubUrl":"https://github.com/dgraph-io/badger/blob/2a001d466f6b71a917319a1db41f99860e16e269/txn.go#L495-L531","documentation":"Txn.Discard() panics if the transaction still has open iterators (txn.numIterators > 0) at discard time. Iterators hold read state against the transaction; discarding without closing them first would leak oracle read-mark resources. The panic enforces the invariant that every it.NewIterator must be matched by it.Close() before Discard.","triggerScenarios":"Returning from a function where a defer txn.Discard() runs but an Iterator created from that txn was never Closed; panics in the middle of an iteration skipping the it.Close() defer; committing/discarding a txn while an iterator created with the PrefillValues/streaming options (yieldItemValue/produceKVs paths) is still open.","commonSituations":"Early return inside it.Seek/iter loops without closing the iterator; a panic/error path that unwinds past the iterator close; holding an iterator across a callback that commits the txn; stream.Send paths in Stream framework where iterators are managed internally and user code discards the txn early.","solutions":["Always defer it.Close() immediately after txn.NewIterator(opts), before any early returns","Ensure iterator lifetime is strictly nested inside the transaction's lifetime (close iterator before Commit/Discard)","On error paths that abandon iteration, close the iterator explicitly before returning","If using Stream/WriteBatch internals, do not manually Discard transactions that the stream framework owns"],"exampleFix":"// before\nit := txn.NewIterator(opts)\nfor it.Rewind(); it.Valid(); it.Next() {\n    if err := process(it.Item()); err != nil {\n        return err // iterator never closed -> Discard panics\n    }\n}\n// after\nit := txn.NewIterator(opts)\ndefer it.Close()\nfor it.Rewind(); it.Valid(); it.Next() {\n    if err := process(it.Item()); err != nil {\n        return err // deferred Close runs before deferred Discard\n    }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// ensure cleanup order via nested defers:\ntxn := db.NewTransaction(true)\ndefer txn.Discard()\nit := txn.NewIterator(opts)\ndefer it.Close() // LIFO: Close runs before Discard\nfor it.Rewind(); it.Valid(); it.Next() { ... }","preventionTips":["defer it.Close() immediately after NewIterator","Never let an iterator outlive its transaction","Avoid discarding/committing txns inside iteration callbacks","Recover from this panic by closing the iterator and creating a fresh transaction"],"tags":["badger","iterator","transaction-lifecycle","panic"],"backgroundTag":"unclosed-iterator-at-discard","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"}