{"record":{"id":"f00fa83729ae881c","repo":"microsoft/garnet","slug":"make-sure-all-async-operations-issued-on-this-sess","errorCode":null,"errorMessage":"Make sure all async operations issued on this session are awaited and completed first","messagePattern":"Make sure all async operations issued on this session are awaited and completed first","errorType":"exception","errorClass":"TsavoriteException","httpStatus":null,"severity":"error","filePath":"libs/storage/Tsavorite/cs/src/core/ClientSession/ClientSession.cs","lineNumber":408,"sourceCode":"            OperationStatus status;\n            do\n                status = store.InternalModifiedBitOperation(key, out modifiedInfo, false);\n            while (store.HandleImmediateNonPendingRetryStatus<TInput, TOutput, TContext, TSessionFunctionsWrapper>(status, sessionFunctions));\n            return modifiedInfo.Modified;\n        }\n\n        /// <summary>\n        /// Wait for commit of all operations completed until the current point in session.\n        /// Does not itself issue checkpoint/commits.\n        /// </summary>\n        /// <returns></returns>\n        private async ValueTask WaitForCommitAsync<TSessionFunctionsWrapper>(TSessionFunctionsWrapper sessionFunctions, CancellationToken token = default)\n            where TSessionFunctionsWrapper : ISessionFunctionsWrapper<TInput, TOutput, TContext, TStoreFunctions, TAllocator>\n        {\n            token.ThrowIfCancellationRequested();\n\n            if (!ctx.pendingReads.IsEmpty)\n                throw new TsavoriteException(\"Make sure all async operations issued on this session are awaited and completed first\");\n\n            // Complete all pending sync operations on session\n            await CompletePendingAsync(sessionFunctions, token: token).ConfigureAwait(false);\n\n            var task = store.CheckpointTask;\n\n            while (true)\n            {\n                _ = await task.WithCancellationAsync(token).ConfigureAwait(false);\n                Refresh(sessionFunctions);\n                task = store.CheckpointTask;\n            }\n        }\n\n        /// <summary>\n        /// Compact the log until specified address, moving active records to the tail of the log. BeginAddress is shifted, but the physical log\n        /// is not deleted from disk. Caller is responsible for truncating the physical log on disk by taking a checkpoint or calling Log.Truncate\n        /// </summary>","sourceCodeStart":390,"sourceCodeEnd":426,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/ClientSession/ClientSession.cs#L390-L426","documentation":"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.","triggerScenarios":"Calling WaitForCommitAsync (directly or via CompletePendingAsync with waitForCommit=true) while the session has outstanding async reads that have not been awaited.","commonSituations":"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.","solutions":["Await and complete all pending async reads (via CompletePendingAsync) before calling WaitForCommitAsync.","Track all issued async read ValueTasks and await them in a Task.WhenAll before waiting for commit.","Use try/finally to ensure reads are drained even if an exception occurs."],"exampleFix":"// before\nvar readTask = session.ReadAsync(key, ref input, token);\nsession.WaitForCommitAsync(funcs, token); // throws\n\n// after\nvar readTask = session.ReadAsync(key, ref input, token);\nawait readTask.ConfigureAwait(false);\nawait session.CompletePendingAsync(funcs, token).ConfigureAwait(false);\nawait session.WaitForCommitAsync(funcs, token).ConfigureAwait(false);","handlingStrategy":"validation","validationCode":"if (!ctx.pendingReads.IsEmpty) throw new InvalidOperationException(\"Complete all pending async reads before WaitForCommitAsync.\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Await every ReadAsync ValueTask before waiting for commit.","Call CompletePendingAsync to drain outstanding reads before WaitForCommitAsync.","Track issued read tasks and await them via Task.WhenAll."],"tags":["tsavorite","session","async","commit","pending-reads","csharp"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}