apache/beam · error

prism consistency error: trying to remove a non-existent…

Error message

prism consistency error: trying to remove a non-existent timer for a key: %v,%+v

What it means

This is the second consistency check in timerHandler.removeTimer: the per-user-key timer map exists, but the specific timerKey being removed is absent. Prism's timer state expects every removal to correspond to a previously scheduled timer; a missing entry means firing and clearing bookkeeping got out of sync.

Solutions

  1. Upgrade Beam/prism to the latest version where timer deduplication fixes may apply.
  2. Ensure the pipeline does not clear the same timer twice or set a timer while clearing it in the same bundle.
  3. Log the userKey and timerKey at the failure and file a Beam bug with a reproducing pipeline or TestStream script.
Defensive patterns

Strategy: try-catch

Validate before calling

// Guard removal: only clear a timer if it still exists
if h.TimerExists(userKey, key) {
    h.ClearTimer(userKey, key)
}

Try / catch

func safeRemove(h *timerHandler, userKey string, key timerKey) (recovered any) {
    defer func() { recovered = recover() }()
    h.FireAt(userKey, key)
    return nil
}

Prevention

When it happens

Trigger: FireAt fires a timer whose entry was already deleted (double-fire or clear-then-fire race), or a timer key arrives with a mismatched tag/pane so it no longer matches the stored key.

Common situations: TestStream-driven tests replaying events out of order, pipelines setting and clearing timers concurrently across bundles, or prism version bugs in timer handling.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/02910bf7030b3792. Report an issue: GitHub.

Appendix: source

Thrown at sdks/go/pkg/beam/runners/prism/internal/engine/timers.go:329

// timers returns the timers for the userkey.
func (th *timerHandler) timers(timer element) map[timerKey]fireElement {
	timers, ok := th.nextFiring[string(timer.keyBytes)]
	if !ok {
		timers = th.firingMapPool.Get().(map[timerKey]fireElement)
		th.nextFiring[string(timer.keyBytes)] = timers
	}
	return timers
}

func (th *timerHandler) removeTimer(userKey string, key timerKey) element {
	timers, ok := th.nextFiring[userKey]
	if !ok {
		panic(fmt.Sprintf("prism consistency error: trying to remove a timer for a key without timers: %v,%+v", userKey, key))
	}
	times, ok := timers[key]
	if !ok {
		panic(fmt.Sprintf("prism consistency error: trying to remove a non-existent timer for a key: %v,%+v", userKey, key))
	}
	delete(timers, key)
	if len(timers) == 0 {
		delete(th.nextFiring, userKey)
		th.firingMapPool.Put(timers)
	}
	return times.timer
}

func (th *timerHandler) add(key timerKey, newFire fireElement) {
	byKeys, ok := th.toFire[newFire.firing]
	if !ok {
		byKeys = th.userKeysSetPool.Get().(map[string]set[timerKey])
		th.toFire[newFire.firing] = byKeys
		heap.Push(&th.order, newFire.firing) // We only need to add a firing order when inserting.
	}
	timers, ok := byKeys[string(newFire.timer.keyBytes)]
	if !ok {

View on GitHub (pinned to 12126d8942)