{"record":{"id":"51f9584e135dd293","repo":"temporalio/temporal","slug":"future-has-already-been-completed","errorCode":null,"errorMessage":"future has already been completed","messagePattern":"future has already been completed","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/future/future_impl.go","lineNumber":77,"sourceCode":"\tif f.Ready() {\n\t\treturn f.value, f.err\n\t}\n\tvar value T\n\treturn value, errorFutureNotReady\n}\n\nfunc (f *FutureImpl[T]) Set(\n\tvalue T,\n\terr error,\n) {\n\t// cannot directly set status to `ready`, to prevent data race in case multiple `Get` occurs\n\t// instead set status to `setting` to prevent concurrent completion of this future\n\tif !atomic.CompareAndSwapInt32(\n\t\t&f.status,\n\t\tpending,\n\t\tsetting,\n\t) {\n\t\tpanic(\"future has already been completed\")\n\t}\n\n\tf.value = value\n\tf.err = err\n\tatomic.CompareAndSwapInt32(&f.status, setting, ready)\n\tclose(f.readyCh)\n}\n\n// Sets the value of the future, if it has not been set already. Returns true if this call set the value.\nfunc (f *FutureImpl[T]) SetIfNotReady(\n\tvalue T,\n\terr error,\n) bool {\n\tif !atomic.CompareAndSwapInt32(\n\t\t&f.status,\n\t\tpending,\n\t\tsetting,\n\t) {","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/common/future/future_impl.go#L59-L95","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Track completion with a sync.Once or the future's own state before calling Set","Ensure only one goroutine/path owns completion; others should wait via Get/Ready","If double completion is possible, use a wrapper that swallows the second Set instead of a raw FutureImpl"],"exampleFix":"// before\nif err != nil {\n    f.Set(nil, err)\n}\nf.Set(result, nil) // panics if err branch ran\n// after\nif err != nil {\n    f.Set(nil, err)\n    return\n}\nf.Set(result, nil)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"defer func(){ recover() }(); f.Set(v, err)","preventionTips":["sync.Once around completion","Single completion owner"],"tags":["go","panic","concurrency","future","double-completion"],"backgroundTag":"future-already-completed","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}