{"record":{"id":"bc0713bfc1e11b43","repo":"microsoft/garnet","slug":"begintransactional-cannot-be-called-twice-call-en","errorCode":null,"errorMessage":"BeginTransactional cannot be called twice (call EndTransactional first)","messagePattern":"BeginTransactional cannot be called twice \\(call EndTransactional first\\)","errorType":"exception","errorClass":"TsavoriteException","httpStatus":null,"severity":"error","filePath":"libs/storage/Tsavorite/cs/src/core/ClientSession/ClientSession.cs","lineNumber":86,"sourceCode":"            CheckIsAcquiredTransactional(sessionFunctions);\n            if (TotalLockCount > 0)\n                throw new TsavoriteException($\"EndTransactional called with locks held: {sharedLockCount} shared locks, {exclusiveLockCount} exclusive locks\");\n            sessionFunctions.Ctx.isAcquiredTransactional = false;\n            sessionFunctions.Ctx.txnVersion = 0;\n        }\n\n        internal void CheckIsAcquiredTransactional<TSessionFunctions>(TSessionFunctions sessionFunctions)\n            where TSessionFunctions : ISessionFunctionsWrapper<TInput, TOutput, TContext, TStoreFunctions, TAllocator>\n        {\n            if (!sessionFunctions.Ctx.isAcquiredTransactional)\n                throw new TsavoriteException(\"Transactional method call when BeginTransactional has not been called\");\n        }\n\n        void CheckIsNotAcquiredTransactional<TSessionFunctions>(TSessionFunctions sessionFunctions)\n            where TSessionFunctions : ISessionFunctionsWrapper<TInput, TOutput, TContext, TStoreFunctions, TAllocator>\n        {\n            if (sessionFunctions.Ctx.isAcquiredTransactional)\n                throw new TsavoriteException(\"BeginTransactional cannot be called twice (call EndTransactional first)\");\n        }\n\n        internal ClientSession(\n            TsavoriteKV<TStoreFunctions, TAllocator> store,\n            TsavoriteKV<TStoreFunctions, TAllocator>.TsavoriteExecutionContext<TInput, TOutput, TContext> ctx,\n            TFunctions functions,\n            bool enableConsistentRead = false,\n            ILoggerFactory loggerFactory = null)\n        {\n            if (enableConsistentRead)\n            {\n                crContext = new(this);\n                tcrContext = new(this);\n                bContext = crContext.BasicContext;\n                uContext = new(this);\n                lContext = tcrContext.TransactionalContext;\n                luContext = new(this);\n            }","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/ClientSession/ClientSession.cs#L68-L104","documentation":"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.","triggerScenarios":"Calling session.BeginTransactional() twice in a row without an intervening EndTransactional.","commonSituations":"Forgetting EndTransactional from a previous transaction; exception in a transaction body that skips EndTransactional, then re-entering; nested method calls that both start transactions.","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."],"exampleFix":"// before\nsession.BeginTransactional();\n/* ... exception skips EndTransactional ... */\nsession.BeginTransactional(); // throws\n\n// after\nsession.BeginTransactional();\ntry { /* ... work ... */ }\nfinally { session.EndTransactional(); }\nsession.BeginTransactional(); // now safe","handlingStrategy":"validation","validationCode":"// Track transaction state; before BeginTransactional:\nif (isInTransaction) throw new InvalidOperationException(\"EndTransactional must be called before starting a new transaction.\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["tsavorite","transaction","session","api-misuse","csharp"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}