dgraph-io/badger · critical

Must have caught a nil callback for txn.CommitWith

Error message

Must have caught a nil callback for txn.CommitWith

What it means

This panic guards runTxnCallback against a txnCb whose user callback field is nil. CommitWith is supposed to reject nil callbacks up front, so reaching this panic means the nil check in CommitWith was bypassed or the txnCb was constructed without a user function. It indicates an internal invariant violation, not normal user-facing error handling.

Source

Thrown at txn.go:685

	// If batchSet failed, LSM would not have been updated. So, no need to rollback anything.

	// TODO: What if some of the txns successfully make it to value log, but others fail.
	// Nothing gets updated to LSM, until a restart happens.
	return txnCb()
}

type txnCb struct {
	commit func() error
	user   func(error)
	err    error
}

func runTxnCallback(cb *txnCb) {
	switch {
	case cb == nil:
		panic("txn callback is nil")
	case cb.user == nil:
		panic("Must have caught a nil callback for txn.CommitWith")
	case cb.err != nil:
		cb.user(cb.err)
	case cb.commit != nil:
		err := cb.commit()
		cb.user(err)
	default:
		cb.user(nil)
	}
}

// CommitWith acts like Commit, but takes a callback, which gets run via a
// goroutine to avoid blocking this function. The callback is guaranteed to run,
// so it is safe to increment sync.WaitGroup before calling CommitWith, and
// decrementing it in the callback; to block until all callbacks are run.
func (txn *Txn) CommitWith(cb func(error)) {
	if cb == nil {
		panic("Nil callback provided to CommitWith")
	}

View on GitHub (pinned to 2a001d466f)

Solutions

  1. Always pass a concrete non-nil func(error) to CommitWith — e.g. cb := func(error){}; txn.CommitWith(cb)
  2. Upgrade Badger: current versions make CommitWith's nil check unconditionally hit first
  3. If wrapping callbacks in interfaces, check reflect.ValueOf(cb).IsNil() to catch typed-nil funcs

Example fix

// before
var cb func(error)
txn.CommitWith(cb) // nil, panics
// after
txn.CommitWith(func(err error) { if err != nil { log.Error(err) } })
Defensive patterns

Strategy: validation

Validate before calling

if cb == nil { return errors.New("CommitWith requires a non-nil callback") }
txn.CommitWith(cb)

Type guard

func isNilFunc(cb func(error)) bool { return cb == nil || reflect.ValueOf(cb).IsNil() }

Try / catch

defer func(){ if r := recover(); r != nil && strings.Contains(fmt.Sprint(r), "nil callback") { /* restore and re-create txn */ } }()

Prevention

When it happens

Trigger: Calling CommitWith with a nil func(error) on a code path where the CommitWith nil-check is skipped (patched/forked code), or a txnCb built with cb.user == nil passed to runTxnCallback.

Common situations: Custom forks of Badger; testing harnesses that call runTxnCallback directly; subtle bugs where a nil-typed func value is wrapped (typed nil interface) and slips past an `if cb == nil` check in CommitWith.

Related errors


AI-assisted analysis of dgraph-io/badger@2a001d466f (2026-09-05). Data as JSON: /api/errors/12db56d6578d9db3. Report an issue: GitHub.