microsoft/garnet · error · TsavoriteException
BeginTransactional cannot be called twice (call EndTransacti
Error message
BeginTransactional cannot be called twice (call EndTransactional first)
What it means
CheckIsNotAcquiredTransactional is called by BeginTransactional to prevent nested transactional scopes. If isAcquiredTransactional is already true (a previous BeginTransactional has not been ended), BeginTransactional throws. Tsavorite sessions support one active transaction at a time — you must call EndTransactional before starting a new one.
Source
Thrown at libs/storage/Tsavorite/cs/src/core/ClientSession/ClientSession.cs:86
CheckIsAcquiredTransactional(sessionFunctions);
if (TotalLockCount > 0)
throw new TsavoriteException($"EndTransactional called with locks held: {sharedLockCount} shared locks, {exclusiveLockCount} exclusive locks");
sessionFunctions.Ctx.isAcquiredTransactional = false;
sessionFunctions.Ctx.txnVersion = 0;
}
internal void CheckIsAcquiredTransactional<TSessionFunctions>(TSessionFunctions sessionFunctions)
where TSessionFunctions : ISessionFunctionsWrapper<TInput, TOutput, TContext, TStoreFunctions, TAllocator>
{
if (!sessionFunctions.Ctx.isAcquiredTransactional)
throw new TsavoriteException("Transactional method call when BeginTransactional has not been called");
}
void CheckIsNotAcquiredTransactional<TSessionFunctions>(TSessionFunctions sessionFunctions)
where TSessionFunctions : ISessionFunctionsWrapper<TInput, TOutput, TContext, TStoreFunctions, TAllocator>
{
if (sessionFunctions.Ctx.isAcquiredTransactional)
throw new TsavoriteException("BeginTransactional cannot be called twice (call EndTransactional first)");
}
internal ClientSession(
TsavoriteKV<TStoreFunctions, TAllocator> store,
TsavoriteKV<TStoreFunctions, TAllocator>.TsavoriteExecutionContext<TInput, TOutput, TContext> ctx,
TFunctions functions,
bool enableConsistentRead = false,
ILoggerFactory loggerFactory = null)
{
if (enableConsistentRead)
{
crContext = new(this);
tcrContext = new(this);
bContext = crContext.BasicContext;
uContext = new(this);
lContext = tcrContext.TransactionalContext;
luContext = new(this);
}View on GitHub (pinned to 951b0fc683)
Solutions
- Always pair BeginTransactional with EndTransactional (ideally via try/finally or a using/dispose wrapper).
- Before calling BeginTransactional, ensure no prior transaction is active — track the state in your own code.
- Recover from a leaked transaction by calling EndTransactional (after releasing locks) before starting a new one.
Example fix
// before
session.BeginTransactional();
/* ... exception skips EndTransactional ... */
session.BeginTransactional(); // throws
// after
session.BeginTransactional();
try { /* ... work ... */ }
finally { session.EndTransactional(); }
session.BeginTransactional(); // now safe Defensive patterns
Strategy: validation
Validate before calling
// Track transaction state; before BeginTransactional:
if (isInTransaction) throw new InvalidOperationException("EndTransactional must be called before starting a new transaction."); Prevention
- Wrap transactions in try/finally to guarantee EndTransactional runs.
- Maintain a boolean flag for active transaction state and check it before BeginTransactional.
- Never nest transactional scopes on the same session.
When it happens
Trigger: Calling session.BeginTransactional() twice in a row without an intervening EndTransactional.
Common situations: Forgetting EndTransactional from a previous transaction; exception in a transaction body that skips EndTransactional, then re-entering; nested method calls that both start transactions.
Related errors
- Transactional method call when BeginTransactional has not be
- EndTransactional called with locks held: {sharedLockCount} s
- Consistent read context does not allow reads from address!
- TsavoriteLogAllocator does not support VerifyRecordFromDiskC
- TsavoriteLogAllocator Scan methods should not be used
AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13).
Data as JSON: /api/errors/bc0713bfc1e11b43.
Report an issue: GitHub.