{"record":{"id":"7f3b45e5e5b5a2e9","repo":"microsoft/FASTER","slug":"async-operations-not-supported-over-protected-epoch","errorCode":null,"errorMessage":"Async operations not supported over protected epoch","messagePattern":"Async operations not supported over protected epoch","errorType":"exception","errorClass":"NotSupportedException","httpStatus":null,"severity":"error","filePath":"cs/src/core/Async/AsyncOperationInternal.cs","lineNumber":229,"sourceCode":"        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private static Status TranslateStatus(OperationStatus internalStatus)\n        {\n            if (OperationStatusUtils.TryConvertToCompletedStatusCode(internalStatus, out Status status))\n                return status;\n            Debug.Assert(internalStatus == OperationStatus.ALLOCATE_FAILED);\n            return new(StatusCode.Pending);\n        }\n\n        // This takes flushEvent as a parameter because we can't pass by ref to an async method.\n        private static async ValueTask<ExceptionDispatchInfo> WaitForFlushCompletionAsync(FasterKV<Key, Value> @this, CompletionEvent flushEvent, CancellationToken token)\n        {\n            ExceptionDispatchInfo exceptionDispatchInfo = default;\n            try\n            {\n                token.ThrowIfCancellationRequested();\n\n                if (@this.epoch.ThisInstanceProtected())\n                    throw new NotSupportedException(\"Async operations not supported over protected epoch\");\n\n                await flushEvent.WaitAsync(token).ConfigureAwait(false);\n            }\n            catch (Exception e)\n            {\n                exceptionDispatchInfo = ExceptionDispatchInfo.Capture(e);\n            }\n            return exceptionDispatchInfo;\n        }\n\n        // This takes flushEvent as a parameter because we can't pass by ref to an async method.\n        private static async ValueTask<(AsyncIOContext<Key, Value> diskRequest, ExceptionDispatchInfo edi)> WaitForFlushOrIOCompletionAsync<Input, Output, Context>(\n                        FasterKV<Key, Value> @this, FasterExecutionContext<Input, Output, Context> sessionCtx,\n                        CompletionEvent flushEvent, AsyncIOContext<Key, Value> diskRequest, CancellationToken token)\n        {\n            ExceptionDispatchInfo exceptionDispatchInfo = default;\n            try\n            {","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/microsoft/FASTER/blob/321d872eabda6a0345c8bd76419f89723ed864ae/cs/src/core/Async/AsyncOperationInternal.cs#L211-L247","documentation":"This NotSupportedException is thrown by WaitForFlushCompletionAsync when the caller's epoch is currently protected by the same thread. FASTER/Tsavorite's async flush wait suspends the thread, which would leave a manually protected epoch held across an await, corrupting epoch-based memory protection and potentially causing deadlocks or unprotected access.","triggerScenarios":"Calling an async API such as WaitForFlushCompletionAsync (flush/RefreshAndWait) from inside a region where the thread has called Protect/ProtectAndDrain (ThisInstanceProtected() returns true) - e.g. inside an IFunctions callback or a manual epoch-protected section.","commonSituations":"Developers using ManualEpochScopes or performing epoch-protected work in a session, then awaiting an async operation inside the protected region; porting synchronous protected code paths to async/await; calling blocking-refresh-style async helpers inside IFunctions callbacks invoked under epoch protection.","solutions":["Remove the epoch protection (unprotect) before awaiting - restructure so Protect/Unprotect tightly scopes only synchronous work.","Use a different session/thread for the async operation instead of awaiting within the protected region.","Use the synchronous non-protected equivalents (e.g. CompletePending) inside protected regions.","If a throw is expected-but-tolerable, catch NotSupportedException and fall back to a synchronous path."],"exampleFix":"// before\nusing (session.Epoch.Protect())\n{\n    await session.WaitForFlushCompletionAsync(token); // throws\n}\n// after\nsession.Epoch.Unprotect();\nawait session.WaitForFlushCompletionAsync(token);\nsession.Epoch.Protect();","handlingStrategy":"validation","validationCode":"if (session.Epoch.ThisInstanceProtected())\n    throw new InvalidOperationException(\"Cannot await async session APIs inside a protected epoch region\");","typeGuard":null,"tryCatchPattern":"try\n{\n    await session.WaitForFlushCompletionAsync(token);\n}\ncatch (NotSupportedException)\n{\n    // fall back to synchronous CompletePending\n}","preventionTips":["Never await inside an IFunctions callback or a Protect/Unprotect scope","Keep epoch-protected regions strictly synchronous and short-lived","Use a separate session for async operations on protected threads"],"tags":["async","epoch-protection","threading","csharp"],"backgroundTag":"unsupported-operation","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"}