dgraph-io/badger · error
Unclosed iterator at time of Txn.Discard.
Error message
Unclosed iterator at time of Txn.Discard.
What it means
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.
Source
Thrown at txn.go:513
// the same time. The reads slice is not currently thread-safe and
// needs to be locked whenever we mark a key as read.
txn.readsLock.Lock()
txn.reads = append(txn.reads, fp)
txn.readsLock.Unlock()
}
}
// Discard discards a created transaction. This method is very important and must be called. Commit
// method calls this internally, however, calling this multiple times doesn't cause any issues. So,
// this can safely be called via a defer right when transaction is created.
//
// NOTE: If any operations are run on a discarded transaction, ErrDiscardedTxn is returned.
func (txn *Txn) Discard() {
if txn.discarded { // Avoid a re-run.
return
}
if txn.numIterators.Load() > 0 {
panic("Unclosed iterator at time of Txn.Discard.")
}
txn.discarded = true
if !txn.db.orc.isManaged {
txn.db.orc.doneRead(txn)
}
}
func (txn *Txn) commitAndSend() (func() error, error) {
orc := txn.db.orc
// Ensure that the order in which we get the commit timestamp is the same as
// the order in which we push these updates to the write channel. So, we
// acquire a writeChLock before getting a commit timestamp, and only release
// it after pushing the entries to it.
orc.writeChLock.Lock()
defer orc.writeChLock.Unlock()
commitTs, conflict := orc.newCommitTs(txn)
if conflict {View on GitHub (pinned to 2a001d466f)
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
Example fix
// before
it := txn.NewIterator(opts)
for it.Rewind(); it.Valid(); it.Next() {
if err := process(it.Item()); err != nil {
return err // iterator never closed -> Discard panics
}
}
// after
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
if err := process(it.Item()); err != nil {
return err // deferred Close runs before deferred Discard
}
} Defensive patterns
Strategy: try-catch
Try / catch
// ensure cleanup order via nested defers:
txn := db.NewTransaction(true)
defer txn.Discard()
it := txn.NewIterator(opts)
defer it.Close() // LIFO: Close runs before Discard
for it.Rewind(); it.Valid(); it.Next() { ... } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- BlockCacheSize should be set since compression/encryption ar
- Cannot use GetSequence with managedDB=true.
- Cannot use GetSequence in read-only mode.
- Cannot flatten in read-only mode.
- Attempting to drop data in read-only mode.
AI-assisted analysis of dgraph-io/badger@2a001d466f (2026-09-05).
Data as JSON: /api/errors/b41882eacb380ff4.
Report an issue: GitHub.