microsoft/FASTER · error · NotSupportedException
Async operations not supported over protected epoch
Error message
Async operations not supported over protected epoch
What it means
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.
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.
Example fix
// before
using (session.Epoch.Protect())
{
await session.WaitForFlushCompletionAsync(token); // throws
}
// after
session.Epoch.Unprotect();
await session.WaitForFlushCompletionAsync(token);
session.Epoch.Protect(); Defensive patterns
Strategy: validation
Validate before calling
if (session.Epoch.ThisInstanceProtected())
throw new InvalidOperationException("Cannot await async session APIs inside a protected epoch region"); Try / catch
try
{
await session.WaitForFlushCompletionAsync(token);
}
catch (NotSupportedException)
{
// fall back to synchronous CompletePending
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Async operations not supported over protected epoch
- Make sure all async operations issued on this session are…
- Out of order message within session
- Unexpected status of SubscribeKV
- Failed to connect server.
AI-assisted analysis of microsoft/FASTER@321d872eab (2026-09-15).
Data as JSON: /api/errors/7f3b45e5e5b5a2e9.
Report an issue: GitHub.
Appendix: source
Thrown at cs/src/core/Async/AsyncOperationInternal.cs:229
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Status TranslateStatus(OperationStatus internalStatus)
{
if (OperationStatusUtils.TryConvertToCompletedStatusCode(internalStatus, out Status status))
return status;
Debug.Assert(internalStatus == OperationStatus.ALLOCATE_FAILED);
return new(StatusCode.Pending);
}
// This takes flushEvent as a parameter because we can't pass by ref to an async method.
private static async ValueTask<ExceptionDispatchInfo> WaitForFlushCompletionAsync(FasterKV<Key, Value> @this, CompletionEvent flushEvent, CancellationToken token)
{
ExceptionDispatchInfo exceptionDispatchInfo = default;
try
{
token.ThrowIfCancellationRequested();
if (@this.epoch.ThisInstanceProtected())
throw new NotSupportedException("Async operations not supported over protected epoch");
await flushEvent.WaitAsync(token).ConfigureAwait(false);
}
catch (Exception e)
{
exceptionDispatchInfo = ExceptionDispatchInfo.Capture(e);
}
return exceptionDispatchInfo;
}
// This takes flushEvent as a parameter because we can't pass by ref to an async method.
private static async ValueTask<(AsyncIOContext<Key, Value> diskRequest, ExceptionDispatchInfo edi)> WaitForFlushOrIOCompletionAsync<Input, Output, Context>(
FasterKV<Key, Value> @this, FasterExecutionContext<Input, Output, Context> sessionCtx,
CompletionEvent flushEvent, AsyncIOContext<Key, Value> diskRequest, CancellationToken token)
{
ExceptionDispatchInfo exceptionDispatchInfo = default;
try
{View on GitHub (pinned to 321d872eab)