{"record":{"id":"050aff2a8059cd08","repo":"microsoft/FASTER","slug":"cannot-use-completecheckpointasync-when-using-non-async","errorCode":null,"errorMessage":"Cannot use CompleteCheckpointAsync when using non-async sessions","messagePattern":"Cannot use CompleteCheckpointAsync when using non-async sessions","errorType":"exception","errorClass":"FasterException","httpStatus":null,"severity":"error","filePath":"cs/src/core/Index/FASTER/FASTER.cs","lineNumber":542,"sourceCode":"        /// Asynchronously recover from specific index and log token (blocking operation)\n        /// </summary>\n        /// <param name=\"indexCheckpointToken\"></param>\n        /// <param name=\"hybridLogCheckpointToken\"></param>\n        /// <param name=\"numPagesToPreload\">Number of pages to preload into memory after recovery</param>\n        /// <param name=\"undoNextVersion\">Whether records with versions beyond checkpoint version need to be undone (and invalidated on log)</param>\n        /// <param name=\"cancellationToken\">Cancellation token</param>\n        /// <returns>Version we actually recovered to</returns>\n        public ValueTask<long> RecoverAsync(Guid indexCheckpointToken, Guid hybridLogCheckpointToken, int numPagesToPreload = -1, bool undoNextVersion = true, CancellationToken cancellationToken = default) \n            => InternalRecoverAsync(indexCheckpointToken, hybridLogCheckpointToken, numPagesToPreload, undoNextVersion, -1, cancellationToken);\n\n        /// <summary>\n        /// Wait for ongoing checkpoint to complete\n        /// </summary>\n        /// <returns></returns>\n        public async ValueTask CompleteCheckpointAsync(CancellationToken token = default)\n        {\n            if (epoch.ThisInstanceProtected())\n                throw new FasterException(\"Cannot use CompleteCheckpointAsync when using non-async sessions\");\n\n            token.ThrowIfCancellationRequested();\n\n            while (true)\n            {\n                var systemState = this.systemState;\n                if (systemState.Phase == Phase.REST || systemState.Phase == Phase.PREPARE_GROW ||\n                    systemState.Phase == Phase.IN_PROGRESS_GROW)\n                    return;\n\n                List<ValueTask> valueTasks = new();\n\n                try\n                {\n                    epoch.Resume();\n                    ThreadStateMachineStep<Empty, Empty, Empty, NullFasterSession>(null, NullFasterSession.Instance, valueTasks, token);\n                }\n                catch (Exception)","sourceCodeStart":524,"sourceCodeEnd":560,"githubUrl":"https://github.com/microsoft/FASTER/blob/321d872eabda6a0345c8bd76419f89723ed864ae/cs/src/core/Index/FASTER/FASTER.cs#L524-L560","documentation":"CompleteCheckpointAsync must run inside an async (epoch-unprotected) context. If the calling thread currently holds the epoch protection (ThisInstanceProtected() is true), it means the caller is using a synchronous/non-async session and blocking-epoch semantics, so the async wait would deadlock or violate epoch discipline; the library throws instead.","triggerScenarios":"Calling CompleteCheckpointAsync from code that has entered epoch protection (e.g., inside a session's synchronous callback, insideEpochProtected region, or after manually calling epoch.Resume()/Protect on the same thread), instead of completing the checkpoint on a separate async flow.","commonSituations":"Mixing blocking (synchronous) session APIs like TakeFullCheckpoint followed by CompleteCheckpointAsync on the same protected thread; calling CompleteCheckpointAsync inside IFasterStateMachine callbacks; wrappers that call async APIs while holding epoch protection.","solutions":["Use the async checkpoint APIs end-to-end: TakeFullCheckpointAsync/CompleteCheckpointAsync from a non-epoch-protected context","If using blocking checkpoints, rely on their own completion semantics instead of calling CompleteCheckpointAsync","Move the CompleteCheckpointAsync call out of epoch-protected regions and session callbacks onto an independent async continuation"],"exampleFix":"// before: same thread still epoch-protected\nfasterKV.TakeFullCheckpoint(out token); // blocking, protected\nawait fasterKV.CompleteCheckpointAsync(); // throws\n// after\n(long token, _) = await fasterKV.TakeFullCheckpointAsync(); // async path handles completion","handlingStrategy":"try-catch","validationCode":"// Only call CompleteCheckpointAsync if the current thread is NOT epoch-protected\nif (fasterKV is not null /* and caller is on async session flow, not inside callbacks */)\n{\n    // proceed with async completion only from async session paths\n}","typeGuard":null,"tryCatchPattern":"try { await fasterKV.CompleteCheckpointAsync(); }\ncatch (FasterException ex) when (ex.Message.Contains(\"CompleteCheckpointAsync when using non-async sessions\"))\n{\n    // you are on a blocking/protected path: switch to the async checkpoint API\n}","preventionTips":["Never mix blocking checkpoint calls with CompleteCheckpointAsync on the same thread","Call checkpoint completion only from async session flows, never inside epoch-protected regions or session callbacks","Use TakeFullCheckpointAsync/TakeHybridLogCheckpointAsync, which handle completion internally"],"tags":["async","epoch","checkpoint","api-misuse"],"backgroundTag":"invalid-state-transition","analyzedSha":"321d872eabda6a0345c8bd76419f89723ed864ae","analyzedAt":"2026-09-15T22:18:00.693Z","contentChangedAt":"2026-09-15T22:18:00.693Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}