litedb-org/LiteDB · error · LiteException

0

0

Error message

Maximum number of transactions reached

What it means

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.

Source

Thrown at LiteDB/Engine/Services/TransactionMonitor.cs:58

            // initial size 
            _initialSize = MAX_TRANSACTION_SIZE / MAX_OPEN_TRANSACTIONS;
        }

        public TransactionService GetTransaction(bool create, bool queryOnly, out bool isNew)
        {
            var transaction = _slot.Value;

            if (create && transaction == null)
            {
                isNew = true;

                bool alreadyLock;

                // must lock _transaction before work with _transactions (GetInitialSize use _transactions)
                lock (_transactions)
                {
                    if (_transactions.Count >= MAX_OPEN_TRANSACTIONS) throw new LiteException(0, "Maximum number of transactions reached");

                    var initialSize = this.GetInitialSize();

                    // check if current thread contains any transaction
                    alreadyLock = _transactions.Values.Any(x => x.ThreadID == Environment.CurrentManagedThreadId);

                    transaction = new TransactionService(_header, _locker, _disk, _walIndex, initialSize, this, queryOnly);

                    // add transaction to execution transaction dict
                    _transactions[transaction.TransactionID] = transaction;
                }

                // enter in lock transaction after release _transaction lock
                if (alreadyLock == false)
                {
                    try
                    {
                        _locker.EnterTransaction();

View on GitHub (pinned to f906a5f850)

Solutions

  1. 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).
  2. Batch writes into a single shared transaction instead of one transaction per item.
  3. Reduce concurrency or serialize write workers to stay under 100 in-flight transactions.
  4. Use the implicit transaction API (collection.Insert) which auto-commits, avoiding manual transaction lifecycle bugs.

Example fix

// before - leaked on exception
var tx = db.BeginTransaction();
doWork();          // throws here
tx.Commit();       // never reached, slot leaks

// after
using (var tx = db.BeginTransaction()) {
    doWork();
    tx.Commit();
}
Defensive patterns

Strategy: retry

Validate before calling

// bound concurrency below the 100-transaction cap
using var sem = new SemaphoreSlim(80);
await Task.WhenAll(jobs.Select(async j => {
    await sem.WaitAsync();
    try { using var tx = db.BeginTransaction(); /*...*/ tx.Commit(); }
    finally { sem.Release(); }
}));

Try / catch

try { using var tx = db.BeginTransaction(); /* work */ tx.Commit(); }
catch (LiteException ex) when (ex.Message.Contains("Maximum number of transactions")) {
    await Task.Delay(TimeSpan.FromMilliseconds(50));
    // retry with backoff
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of litedb-org/LiteDB@f906a5f850 (2026-08-13). Data as JSON: /api/errors/be1c641b72198a39. Report an issue: GitHub.