microsoft/garnet · error · NotSupportedException

Async operations not supported over protected epoch

Error message

Async operations not supported over protected epoch

What it means

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.

Source

Thrown at libs/storage/Tsavorite/cs/src/core/ClientSession/ClientSession.cs:332

            => CompletePendingAsync(sessionFunctions, getOutputs: false, waitForCommit, token);

        /// <inheritdoc/>
        internal async ValueTask<CompletedOutputIterator<TInput, TOutput, TContext>> CompletePendingWithOutputsAsync<TSessionFunctionsWrapper>(TSessionFunctionsWrapper sessionFunctions,
                bool waitForCommit = false, CancellationToken token = default)
            where TSessionFunctionsWrapper : ISessionFunctionsWrapper<TInput, TOutput, TContext, TStoreFunctions, TAllocator>
        {
            InitializeCompletedOutputs();
            await CompletePendingAsync(sessionFunctions, getOutputs: true, waitForCommit, token).ConfigureAwait(false);
            return completedOutputs;
        }

        private async ValueTask CompletePendingAsync<TSessionFunctionsWrapper>(TSessionFunctionsWrapper sessionFunctions, bool getOutputs, bool waitForCommit = false, CancellationToken token = default)
            where TSessionFunctionsWrapper : ISessionFunctionsWrapper<TInput, TOutput, TContext, TStoreFunctions, TAllocator>
        {
            token.ThrowIfCancellationRequested();

            if (store.epoch.ThisInstanceProtected())
                throw new NotSupportedException("Async operations not supported over protected epoch");

            // Complete all pending operations on session
            await store.CompletePendingAsync(sessionFunctions, token, getOutputs ? completedOutputs : null).ConfigureAwait(false);

            // Wait for commit if necessary
            if (waitForCommit)
                await WaitForCommitAsync(sessionFunctions, token).ConfigureAwait(false);
        }

        /// <summary>
        /// Check if at least one synchronous request is ready for CompletePending to be called on
        /// Returns completed immediately if there are no outstanding synchronous requests
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public async ValueTask ReadyToCompletePendingAsync(CancellationToken token = default)
        {
            token.ThrowIfCancellationRequested();

View on GitHub (pinned to 951b0fc683)

Solutions

  1. Ensure the epoch is not protected on the calling thread before awaiting async session operations.
  2. Use the synchronous CompletePending (with wait=true) inside epoch-protected scopes instead of the async variant.
  3. Refactor to release epoch protection before the first await and re-protect only when needed afterward.

Example fix

// before
store.epoch.Suspend(); // or some scope that left epoch protected
await session.CompletePendingAsync(funcs, token);

// after
// Ensure epoch is not protected before async call:
if (store.epoch.ThisInstanceProtected()) store.epoch.Resume();
await session.CompletePendingAsync(funcs, token);
Defensive patterns

Strategy: validation

Validate before calling

if (store.epoch.ThisInstanceProtected()) throw new InvalidOperationException("Release epoch protection before calling async session methods.");

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13). Data as JSON: /api/errors/26d52a37713b182f. Report an issue: GitHub.