{"record":{"id":"26d52a37713b182f","repo":"microsoft/garnet","slug":"async-operations-not-supported-over-protected-epoc","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":"libs/storage/Tsavorite/cs/src/core/ClientSession/ClientSession.cs","lineNumber":332,"sourceCode":"            => CompletePendingAsync(sessionFunctions, getOutputs: false, waitForCommit, token);\n\n        /// <inheritdoc/>\n        internal async ValueTask<CompletedOutputIterator<TInput, TOutput, TContext>> CompletePendingWithOutputsAsync<TSessionFunctionsWrapper>(TSessionFunctionsWrapper sessionFunctions,\n                bool waitForCommit = false, CancellationToken token = default)\n            where TSessionFunctionsWrapper : ISessionFunctionsWrapper<TInput, TOutput, TContext, TStoreFunctions, TAllocator>\n        {\n            InitializeCompletedOutputs();\n            await CompletePendingAsync(sessionFunctions, getOutputs: true, waitForCommit, token).ConfigureAwait(false);\n            return completedOutputs;\n        }\n\n        private async ValueTask CompletePendingAsync<TSessionFunctionsWrapper>(TSessionFunctionsWrapper sessionFunctions, bool getOutputs, bool waitForCommit = false, CancellationToken token = default)\n            where TSessionFunctionsWrapper : ISessionFunctionsWrapper<TInput, TOutput, TContext, TStoreFunctions, TAllocator>\n        {\n            token.ThrowIfCancellationRequested();\n\n            if (store.epoch.ThisInstanceProtected())\n                throw new NotSupportedException(\"Async operations not supported over protected epoch\");\n\n            // Complete all pending operations on session\n            await store.CompletePendingAsync(sessionFunctions, token, getOutputs ? completedOutputs : null).ConfigureAwait(false);\n\n            // Wait for commit if necessary\n            if (waitForCommit)\n                await WaitForCommitAsync(sessionFunctions, token).ConfigureAwait(false);\n        }\n\n        /// <summary>\n        /// Check if at least one synchronous request is ready for CompletePending to be called on\n        /// Returns completed immediately if there are no outstanding synchronous requests\n        /// </summary>\n        /// <param name=\"token\"></param>\n        /// <returns></returns>\n        public async ValueTask ReadyToCompletePendingAsync(CancellationToken token = default)\n        {\n            token.ThrowIfCancellationRequested();","sourceCodeStart":314,"sourceCodeEnd":350,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/ClientSession/ClientSession.cs#L314-L350","documentation":"CompletePendingAsync is the async path for completing outstanding operations on a session. Tsavorite uses an epoch-based memory-reclamation model where threads protect the epoch before touching shared in-memory structures. The async completion path must release the epoch (leave unprotected) so continuations can run on any thread; calling it while ThisInstanceProtected() is true would be unsafe, so it throws NotSupportedException.","triggerScenarios":"Calling CompletePendingAsync (or ReadAsync/WriteAsync that internally completes pending) while the calling thread has the epoch protected, e.g. inside a synchronized callback or within an epoch-protected scope.","commonSituations":"Mixing sync and async session APIs; calling async methods from within epoch-protected callbacks (e.g. compaction, scan callbacks); older code that protected the epoch before awaiting.","solutions":["Ensure the epoch is not protected on the calling thread before awaiting async session operations.","Use the synchronous CompletePending (with wait=true) inside epoch-protected scopes instead of the async variant.","Refactor to release epoch protection before the first await and re-protect only when needed afterward."],"exampleFix":"// before\nstore.epoch.Suspend(); // or some scope that left epoch protected\nawait session.CompletePendingAsync(funcs, token);\n\n// after\n// Ensure epoch is not protected before async call:\nif (store.epoch.ThisInstanceProtected()) store.epoch.Resume();\nawait session.CompletePendingAsync(funcs, token);","handlingStrategy":"validation","validationCode":"if (store.epoch.ThisInstanceProtected()) throw new InvalidOperationException(\"Release epoch protection before calling async session methods.\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never call async session methods from within an epoch-protected scope.","Release epoch protection before the first await.","Use synchronous CompletePending inside epoch-protected callbacks."],"tags":["tsavorite","session","async","epoch","memory-safety","csharp"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}