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
- 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.
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
- 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.
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
- Make sure all async operations issued on this session are aw
- EndTransactional called with locks held: {sharedLockCount} s
- Transactional method call when BeginTransactional has not be
- BeginTransactional cannot be called twice (call EndTransacti
- Can spin-wait for commit (checkpoint completion) only if wai
AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13).
Data as JSON: /api/errors/26d52a37713b182f.
Report an issue: GitHub.