microsoft/garnet · error · TsavoriteException

Transactional consistent read context does not Refresh!

Error message

Transactional consistent read context does not Refresh!

What it means

Refresh (line 393) throws on TransactionalConsistentReadContext with 'does not Refresh!'. Refresh re-resolves the session's cached page pointers against the live log (epoch-protected); a consistent read context deliberately holds a stable snapshot, so forcing a refresh would break read consistency and is rejected. Use the write-capable contexts for Refresh.

Source

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

        /// <inheritdoc/>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Status Delete(TKey key, ref DeleteOptions deleteOptions, TContext userContext = default)
            => throw new TsavoriteException("Transactional consistent read context does not allow writes!");

        /// <inheritdoc/>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void ResetModified(TKey key)
            => throw new TsavoriteException("Transactional consistent read context does not reset ResetModified!");

        /// <inheritdoc/>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal bool IsModified(TKey key)
            => throw new TsavoriteException("Transactional consistent read context does not allow IsModified!");

        /// <inheritdoc/>
        public void Refresh()
            => throw new TsavoriteException("Transactional consistent read context does not Refresh!");

        #endregion ITsavoriteContext
    }
}

View on GitHub (pinned to 951b0fc683)

Solutions

  1. Call Refresh on a write-capable context: TransactionalUnsafeContext.Refresh (TransactionalUnsafeContext.cs:507), or BasicContext/UnsafeContext.
  2. For a transactional consistent read, end and re-begin the transaction to get a fresh snapshot instead of calling Refresh.
  3. Avoid holding a consistent read context across pointer-invalidating events.

Example fix

// before
var tcr = session.TransactionalConsistentReadContext;
tcr.Refresh(); // throws

// after — refresh via a write-capable context, or restart the read snapshot
tcr.EndTransaction();
tcr.BeginTransaction(); // fresh consistent snapshot

// or, on the unsafe transactional context:
// var luc = session.TransactionalUnsafeContext;
// luc.BeginUnsafe();
// try { luc.Refresh(); } finally { luc.EndUnsafe(); }
Defensive patterns

Strategy: validation

Validate before calling

// Do not Refresh on the read-only snapshot; refresh via a write-capable context, or restart the snapshot.
var w = session.TransactionalUnsafeContext;
w.BeginUnsafe();
try { w.Refresh(); }
finally { w.EndUnsafe(); }
// Or for a fresh consistent read: tcr.EndTransaction(); tcr.BeginTransaction();

Type guard

static bool IsReadOnlyContext<TCtx>() => typeof(TCtx).Name.Contains("ConsistentReadContext");
// Assert before any Refresh() call.

Prevention

When it happens

Trigger: Calling tcrContext.Refresh() on the transactional consistent read context, e.g. to refresh record pointers after long-running pending I/O while still inside the consistent read scope.

Common situations: A long-lived consistent-read session tries to call Refresh (copied from a BasicContext/UnsafeContext pattern) to refresh in-memory pointers; the read-only context forbids it to preserve snapshot consistency.

Related errors


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