{"record":{"id":"be1c641b72198a39","repo":"litedb-org/LiteDB","slug":"0-be1c64","errorCode":"0","errorMessage":"Maximum number of transactions reached","messagePattern":"Maximum number of transactions reached","errorType":"exception","errorClass":"LiteException","httpStatus":null,"severity":"error","filePath":"LiteDB/Engine/Services/TransactionMonitor.cs","lineNumber":58,"sourceCode":"\n            // initial size \n            _initialSize = MAX_TRANSACTION_SIZE / MAX_OPEN_TRANSACTIONS;\n        }\n\n        public TransactionService GetTransaction(bool create, bool queryOnly, out bool isNew)\n        {\n            var transaction = _slot.Value;\n\n            if (create && transaction == null)\n            {\n                isNew = true;\n\n                bool alreadyLock;\n\n                // must lock _transaction before work with _transactions (GetInitialSize use _transactions)\n                lock (_transactions)\n                {\n                    if (_transactions.Count >= MAX_OPEN_TRANSACTIONS) throw new LiteException(0, \"Maximum number of transactions reached\");\n\n                    var initialSize = this.GetInitialSize();\n\n                    // check if current thread contains any transaction\n                    alreadyLock = _transactions.Values.Any(x => x.ThreadID == Environment.CurrentManagedThreadId);\n\n                    transaction = new TransactionService(_header, _locker, _disk, _walIndex, initialSize, this, queryOnly);\n\n                    // add transaction to execution transaction dict\n                    _transactions[transaction.TransactionID] = transaction;\n                }\n\n                // enter in lock transaction after release _transaction lock\n                if (alreadyLock == false)\n                {\n                    try\n                    {\n                        _locker.EnterTransaction();","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/litedb-org/LiteDB/blob/f906a5f850678719e39a39a006cb66dcae563cfa/LiteDB/Engine/Services/TransactionMonitor.cs#L40-L76","documentation":"Thrown by TransactionMonitor.GetTransaction when the count of concurrently open transactions reaches MAX_OPEN_TRANSACTIONS (100). LiteDB caps concurrent transactions to bound memory (each gets MAX_TRANSACTION_SIZE/MAX_OPEN_TRANSACTIONS pages). The check runs under a lock on the _transactions dictionary before a new TransactionService is created.","triggerScenarios":"Opening more than 100 simultaneous transactions: heavy multi-threaded workloads, parallel async writes without sharing a transaction, leaked transactions never committed/aborted (each holds a slot until Dispose), or using a new ILiteDatabase connection per thread in a thread-pool storm.","commonSituations":"Async/await code paths that BeginTransaction but never Commit/Dispose on an exception branch; fan-out parallel tasks each doing its own insert; web request handlers that open transactions without a finally-block disposal.","solutions":["Ensure every transaction is wrapped in a using/finally so it is committed or disposed even on exceptions (leaked transactions are the most common cause).","Batch writes into a single shared transaction instead of one transaction per item.","Reduce concurrency or serialize write workers to stay under 100 in-flight transactions.","Use the implicit transaction API (collection.Insert) which auto-commits, avoiding manual transaction lifecycle bugs."],"exampleFix":"// before - leaked on exception\nvar tx = db.BeginTransaction();\ndoWork();          // throws here\ntx.Commit();       // never reached, slot leaks\n\n// after\nusing (var tx = db.BeginTransaction()) {\n    doWork();\n    tx.Commit();\n}","handlingStrategy":"retry","validationCode":"// bound concurrency below the 100-transaction cap\nusing var sem = new SemaphoreSlim(80);\nawait Task.WhenAll(jobs.Select(async j => {\n    await sem.WaitAsync();\n    try { using var tx = db.BeginTransaction(); /*...*/ tx.Commit(); }\n    finally { sem.Release(); }\n}));","typeGuard":null,"tryCatchPattern":"try { using var tx = db.BeginTransaction(); /* work */ tx.Commit(); }\ncatch (LiteException ex) when (ex.Message.Contains(\"Maximum number of transactions\")) {\n    await Task.Delay(TimeSpan.FromMilliseconds(50));\n    // retry with backoff\n}","preventionTips":["Always wrap transactions in using/finally to prevent slot leaks.","Prefer batched single-transaction writes over per-item transactions.","Throttle parallelism with a SemaphoreSlim well under 100."],"tags":["transactions","concurrency","resource-leak"],"backgroundTag":null,"analyzedSha":"f906a5f850678719e39a39a006cb66dcae563cfa","analyzedAt":"2026-08-13T21:56:30.148Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}