Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] "ENSURE ... Clear() cache" error and DB corruption when modifying collection during its enumeration #2480

Closed
oleksii-datsiuk opened this issue May 15, 2024 · 0 comments
Labels

Comments

@oleksii-datsiuk
Copy link
Contributor

oleksii-datsiuk commented May 15, 2024

LiteDB 5.0.17, Windows 11, .NET6

When trying to update collection while enumerating over it in the same transaction, I get following error:

LiteDB ENSURE: must have no pages in use when call Clear() cache

This error happens on checkpoint execution (for example during disposing database). But problem itself starts at the moment when I try to update collection during enumeration. And the real pain is that if application continues working after that, it often leads to the database corruption.

In our company we already catched this pattern, that first we get "ENSURE ... Clear() cache" error, and afterwards different other errors are coming, and finally DB is corrupted.


After debugging LiteDB, we have found following:

In LiteDB each transaction has multiple snapshots (separate snapshot is created for each collection (table) when it is accessed.

Each snapshot has multiple "pages" that contain data. These pages are then written to the database file.

Snapshots could have either read or write mode. Transaction also could have either read or write mode.

When we need to read from the collection, transaction creates read-only snapshot and transaction itself remains in read mode.

However, if later we need to write to the same collection, transaction disposes read-only snapshot, creates instance of writable snapshot and keeps reference to the new snapshot, while old one is removed from the internal snapshot list of the transaction.

This could cause a problem which I described above. Let's imagine, we start iterating over Test collection. But inside the loop we start updating documents (records) in this collection. So, when we start to iterate over collection, transaction is read-only and it creates read-only snapshot for "Pending" collection. But inside this loop, in the same transaction we update documents in this collection. This actually causes disposing and removal of the existing read-only snapshot and replacing it with writable one. However, enumerator which goes over Test collection, still keeps reference on old snapshot. And when we need to fetch next record during next iteration, it loads new data page and adds it to this orphan snapshot. All such pages are never released, because transaction no longer has reference on this snapshot and will never call its Dispose().

As a result, we get unreleased DB pages after all transactions are finished. And this is actually the condition to display "ENSURE ... Clear() cache" error during checkpoint.

Here I described how "ENSURE ... Clear() cache" error happens. I didn't find exact mechnism how DB becomes corrupted. But I think that such orphan snapshots that independently read data from DB while there is another writable instance of the same snapshot could cause all kinds of different unexpected scenarios in LiteDB code.


From all described above, I would propose to implement at least minimum fix that will prevent using orphan snapshots. Add _disposed flag to SnapShot class, and add following statement to each public member of this class (except ToString() and Dispose()):

ENSURE(!_disposed, "the snapshot is disposed");

Dispose should be like this:

public void Dispose()
{
    if (_disposed)
        return;

    this.Clear();

    _disposed = true;

But ideally would be to have a comprehensive fix, which will allow to modify collection during enumeration.


I have attached sample program which reproduces "ENSURE ... Clear() cache" error.

Program2.zip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants