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

  1. Route all write operations (Upsert/RMW/Delete) through session.BasicContext or call them directly on the ClientSession instance instead of on ConsistentReadContext.
  2. Keep separate variables for read and write: use session.ConsistentReadContext for reads and session.BasicContext for writes.
  3. 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

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


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