microsoft/garnet · error · TsavoriteException
Consistent read context does not allow writes!
Error message
Consistent read context does not allow writes!
What it means
ConsistentReadContext is a read-only snapshot view of the Tsavorite store, obtained from session.ConsistentReadContext when the session was created with enableConsistentRead: true. It implements the ITsavoriteContext interface but every mutation method (Upsert, RMW, Delete) is an expression-bodied member that throws TsavoriteException unconditionally. This is by design: the consistent-read protocol wraps each Read in PreSingleKeyConsistentRead/PostSingleKeyConsistentReadCallback calls to guarantee snapshot isolation, and allowing writes would break that guarantee.
Source
Thrown at libs/storage/Tsavorite/cs/src/core/ClientSession/ConsistentReadContext.cs:172
/// <inheritdoc/>
public async ValueTask CompletePendingAsync(bool waitForCommit = false, CancellationToken token = default)
{
await BasicContext.CompletePendingAsync(waitForCommit, token).ConfigureAwait(false);
Session.functions.PostSingleKeyConsistentReadCallback();
}
/// <inheritdoc/>
public async ValueTask<CompletedOutputIterator<TInput, TOutput, TContext>> CompletePendingWithOutputsAsync(bool waitForCommit = false, CancellationToken token = default)
{
var status = await BasicContext.CompletePendingWithOutputsAsync(waitForCommit, token).ConfigureAwait(false);
Session.functions.PostSingleKeyConsistentReadCallback();
return status;
}
/// <inheritdoc/>
public Status Upsert(TKey key, ReadOnlySpan<byte> desiredValue, TContext userContext = default)
=> throw new TsavoriteException("Consistent read context does not allow writes!");
/// <inheritdoc/>
public Status Upsert(TKey key, ReadOnlySpan<byte> desiredValue, ref UpsertOptions upsertOptions, TContext userContext = default)
=> throw new TsavoriteException("Consistent read context does not allow writes!");
/// <inheritdoc/>
public Status Upsert(TKey key, ref TInput input, ReadOnlySpan<byte> desiredValue, ref TOutput output, TContext userContext = default)
=> throw new TsavoriteException("Consistent read context does not allow writes!");
/// <inheritdoc/>
public Status Upsert(TKey key, ref TInput input, ReadOnlySpan<byte> desiredValue, ref TOutput output, ref UpsertOptions upsertOptions, TContext userContext = default)
=> throw new TsavoriteException("Consistent read context does not allow writes!");
/// <inheritdoc/>
public Status Upsert(TKey key, ref TInput input, ReadOnlySpan<byte> desiredValue, ref TOutput output, ref UpsertOptions upsertOptions, out RecordMetadata recordMetadata, TContext userContext = default)
=> throw new TsavoriteException("Consistent read context does not allow writes!");
/// <inheritdoc/>View on GitHub (pinned to 951b0fc683)
Solutions
- Route all write operations (Upsert/RMW/Delete) through session.BasicContext or call them directly on the ClientSession instance instead of on ConsistentReadContext.
- Keep separate variables for read and write: use session.ConsistentReadContext for reads and session.BasicContext for writes.
- If you need both consistent reads and writes in the same code block, call the write on the session object or BasicContext and the read on ConsistentReadContext.
Example fix
// before (throws) var ctx = session.ConsistentReadContext; ctx.Upsert(key, valueBytes); // after session.BasicContext.Upsert(key, valueBytes);
Defensive patterns
Strategy: validation
Validate before calling
// Before calling Upsert, ensure you are using a writable context
// ConsistentReadContext only supports reads; use BasicContext for writes
if (session.ConsistentReadContext.IsNull)
throw new InvalidOperationException("Consistent read context not available");
// Use BasicContext for the write instead:
session.BasicContext.Upsert(key, valueBytes); Type guard
// Type guard: distinguish read-only context from writable context at compile time // ConsistentReadContext and BasicContext are different struct types. // Store them in separate typed variables to prevent accidental cross-use: readonly ConsistentReadContext<...> readCtx = session.ConsistentReadContext; readonly BasicContext<...> writeCtx = session.BasicContext; // readCtx.Upsert(...) // compile-time OK but throws at runtime // writeCtx.Upsert(...) // correct // Convention: only call Read* on readCtx, only call Upsert/RMW/Delete on writeCtx
Prevention
- Never store ConsistentReadContext in a variable typed as ITsavoriteContext if that variable will also be used for writes — the interface allows the call but the struct throws.
- Establish a naming convention: prefix read-context variables with 'read' (e.g. readCtx) and write-context variables with 'write' (e.g. writeCtx).
- Write unit tests that exercise both read and write paths to catch context-misuse early.
When it happens
Trigger: Calling Upsert(TKey key, ReadOnlySpan<byte> desiredValue, TContext userContext) on the ConsistentReadContext struct. This happens when a developer stores session.ConsistentReadContext in a variable typed as ITsavoriteContext (or as ConsistentReadContext) and then calls .Upsert() on it instead of on session.BasicContext or the session directly.
Common situations: Migrating code from BasicContext to ConsistentReadContext for read consistency and forgetting to split read and write paths; storing the context in a shared variable or field and calling the same method for both reads and writes; refactoring that changes which context a variable points to.
Related errors
- Consistent read context does not allow writes!
- Transactional consistent read context does not allow writes!
- Consistent read context does not reset ResetModified!
- Transactional consistent read context does not allow reads f
- Consistent read context does not reset Refresh!
AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13).
Data as JSON: /api/errors/11d38f1aabc0a485.
Report an issue: GitHub.