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

  1. Remove the epoch protection (unprotect) before awaiting - restructure so Protect/Unprotect tightly scopes only synchronous work.
  2. Use a different session/thread for the async operation instead of awaiting within the protected region.
  3. Use the synchronous non-protected equivalents (e.g. CompletePending) inside protected regions.
  4. 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

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


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)