{"record":{"id":"b0444ee9ab3e70bc","repo":"microsoft/garnet","slug":"transactional-method-call-when-begintransactional","errorCode":null,"errorMessage":"Transactional method call when BeginTransactional has not been called","messagePattern":"Transactional method call when BeginTransactional has not been called","errorType":"exception","errorClass":"TsavoriteException","httpStatus":null,"severity":"error","filePath":"libs/storage/Tsavorite/cs/src/core/ClientSession/ClientSession.cs","lineNumber":79,"sourceCode":"            CheckIsAcquiredTransactional(sessionFunctions);\n            sessionFunctions.Ctx.txnVersion = txnVersion;\n        }\n\n        internal void ReleaseTransactional<TSessionFunctions>(TSessionFunctions sessionFunctions)\n            where TSessionFunctions : ISessionFunctionsWrapper<TInput, TOutput, TContext, TStoreFunctions, TAllocator>\n        {\n            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            {","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cs/src/core/ClientSession/ClientSession.cs#L61-L97","documentation":"CheckIsAcquiredTransactional verifies that the session has entered a transactional scope (via BeginTransactional) before any transactional operation is allowed. If isAcquiredTransactional is false and a transactional method (LocksAcquired, ReleaseTransactional, etc.) is called, it throws. This catches transaction API misuse early.","triggerScenarios":"Calling a transactional operation (e.g. EndTransactional, or a lock operation that checks transactional state) without first calling BeginTransactional on the session.","commonSituations":"Calling EndTransactional without a matching BeginTransactional; lock operations invoked outside a transactional scope; session reused across transactions with missing BeginTransactional calls.","solutions":["Always call session.BeginTransactional() before issuing transactional lock/read/write operations.","Wrap transaction usage in try/finally with EndTransactional in finally to ensure balanced pairing.","Add a debug assertion or guard that checks the transactional state before calling transactional methods."],"exampleFix":"// before\nsession.EndTransactional(); // throws — never began\n\n// after\nusing (session.BeginTransactional())\n{\n    /* ... transactional work ... */\n} // disposes/ends the transaction","handlingStrategy":"validation","validationCode":"// Before calling EndTransactional or transactional ops:\nif (!isInTransaction) throw new InvalidOperationException(\"BeginTransactional must be called first.\");","typeGuard":null,"tryCatchPattern":"try { session.BeginTransactional(); /* ... */ }\ncatch (TsavoriteException ex) when (ex.Message.Contains(\"BeginTransactional has not been called\"))\n{ /* log and ensure BeginTransactional is called first */ }","preventionTips":["Always open a transactional scope with BeginTransactional before transactional operations.","Track the transactional state in your own code and assert it before each transactional call."],"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"}