temporalio/temporal · error

future has already been completed

Error message

future has already been completed

What it means

A Future can only be completed once; Set uses an atomic CAS from pending to setting, and if the future is not pending (already completed or concurrently being completed), it panics. This enforces the single-completion contract that callers of Get/Ready rely on.

Source

Thrown at common/future/future_impl.go:77

	if f.Ready() {
		return f.value, f.err
	}
	var value T
	return value, errorFutureNotReady
}

func (f *FutureImpl[T]) Set(
	value T,
	err error,
) {
	// cannot directly set status to `ready`, to prevent data race in case multiple `Get` occurs
	// instead set status to `setting` to prevent concurrent completion of this future
	if !atomic.CompareAndSwapInt32(
		&f.status,
		pending,
		setting,
	) {
		panic("future has already been completed")
	}

	f.value = value
	f.err = err
	atomic.CompareAndSwapInt32(&f.status, setting, ready)
	close(f.readyCh)
}

// Sets the value of the future, if it has not been set already. Returns true if this call set the value.
func (f *FutureImpl[T]) SetIfNotReady(
	value T,
	err error,
) bool {
	if !atomic.CompareAndSwapInt32(
		&f.status,
		pending,
		setting,
	) {

View on GitHub (pinned to bde624efd1)

Solutions

  1. Track completion with a sync.Once or the future's own state before calling Set
  2. Ensure only one goroutine/path owns completion; others should wait via Get/Ready
  3. If double completion is possible, use a wrapper that swallows the second Set instead of a raw FutureImpl

Example fix

// before
if err != nil {
    f.Set(nil, err)
}
f.Set(result, nil) // panics if err branch ran
// after
if err != nil {
    f.Set(nil, err)
    return
}
f.Set(result, nil)
Defensive patterns

Strategy: try-catch

Try / catch

defer func(){ recover() }(); f.Set(v, err)

Prevention

When it happens

Trigger: Calling Set (or SetError, or any completion method) twice on the same FutureImpl; racing two goroutines that both attempt to resolve the future; a library internally completing a future while user code also completes it.

Common situations: Retry logic that resolves the future on each attempt instead of only the first success; broadcast patterns where multiple watchers write the result; callback invoked twice by an upstream library.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/51f9584e135dd293. Report an issue: GitHub.