microsoft/garnet · error · TsavoriteException

Make sure all async operations issued on this session are aw

Error message

Make sure all async operations issued on this session are awaited and completed first

What it means

WaitForCommitAsync waits for the store's checkpoint (commit) to cover all operations completed up to the current session point. It requires that all pending async reads are drained first — if ctx.pendingReads is non-empty, there are outstanding async read operations that have not been awaited/completed, which would mean the commit point is ambiguous. The throw tells the developer to complete those reads first.

Source

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

            OperationStatus status;
            do
                status = store.InternalModifiedBitOperation(key, out modifiedInfo, false);
            while (store.HandleImmediateNonPendingRetryStatus<TInput, TOutput, TContext, TSessionFunctionsWrapper>(status, sessionFunctions));
            return modifiedInfo.Modified;
        }

        /// <summary>
        /// Wait for commit of all operations completed until the current point in session.
        /// Does not itself issue checkpoint/commits.
        /// </summary>
        /// <returns></returns>
        private async ValueTask WaitForCommitAsync<TSessionFunctionsWrapper>(TSessionFunctionsWrapper sessionFunctions, CancellationToken token = default)
            where TSessionFunctionsWrapper : ISessionFunctionsWrapper<TInput, TOutput, TContext, TStoreFunctions, TAllocator>
        {
            token.ThrowIfCancellationRequested();

            if (!ctx.pendingReads.IsEmpty)
                throw new TsavoriteException("Make sure all async operations issued on this session are awaited and completed first");

            // Complete all pending sync operations on session
            await CompletePendingAsync(sessionFunctions, token: token).ConfigureAwait(false);

            var task = store.CheckpointTask;

            while (true)
            {
                _ = await task.WithCancellationAsync(token).ConfigureAwait(false);
                Refresh(sessionFunctions);
                task = store.CheckpointTask;
            }
        }

        /// <summary>
        /// Compact the log until specified address, moving active records to the tail of the log. BeginAddress is shifted, but the physical log
        /// is not deleted from disk. Caller is responsible for truncating the physical log on disk by taking a checkpoint or calling Log.Truncate
        /// </summary>

View on GitHub (pinned to 951b0fc683)

Solutions

  1. Await and complete all pending async reads (via CompletePendingAsync) before calling WaitForCommitAsync.
  2. Track all issued async read ValueTasks and await them in a Task.WhenAll before waiting for commit.
  3. Use try/finally to ensure reads are drained even if an exception occurs.

Example fix

// before
var readTask = session.ReadAsync(key, ref input, token);
session.WaitForCommitAsync(funcs, token); // throws

// after
var readTask = session.ReadAsync(key, ref input, token);
await readTask.ConfigureAwait(false);
await session.CompletePendingAsync(funcs, token).ConfigureAwait(false);
await session.WaitForCommitAsync(funcs, token).ConfigureAwait(false);
Defensive patterns

Strategy: validation

Validate before calling

if (!ctx.pendingReads.IsEmpty) throw new InvalidOperationException("Complete all pending async reads before WaitForCommitAsync.");

Prevention

When it happens

Trigger: Calling WaitForCommitAsync (directly or via CompletePendingAsync with waitForCommit=true) while the session has outstanding async reads that have not been awaited.

Common situations: Issuing ReadAsync calls and immediately calling WaitForCommitAsync without awaiting all of them; fire-and-forget async reads followed by a commit wait; an exception that skips awaiting some reads.

Related errors


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