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] Fail: invalid page position when INSERT/DELETE #1762

Open
gaspapp opened this issue Jul 24, 2020 · 12 comments
Open

[BUG] Fail: invalid page position when INSERT/DELETE #1762

gaspapp opened this issue Jul 24, 2020 · 12 comments
Labels

Comments

@gaspapp
Copy link

gaspapp commented Jul 24, 2020

LiteDB version: 5.0.7
OS: Windows 10 / Android 9 / iOS 13.5.1
.NET: Core 3.1 on windows

Describe the bug
When performing a Delete or an insert in a collection, the operation fails with an "Fail: invalid page position"
It's non systematically but it seems to happen sometimes when the application is closed and restarted like if the database file has been corrupted during the app closure.
The same operations work fine with a new database file.

Stacktrace
Insert operation

Fail: invalid page position
  at System.Diagnostics.DefaultTraceListener.Fail (System.String message, System.String detailMessage) [0x00008] in <40f246d3e6214581a540496b6a7456a6>:0 
  at System.Diagnostics.TraceListener.Fail (System.String message) [0x00000] in <40f246d3e6214581a540496b6a7456a6>:0 
  at System.Diagnostics.DefaultTraceListener.Fail (System.String message) [0x00000] in <40f246d3e6214581a540496b6a7456a6>:0 
  at System.Diagnostics.TraceInternal.Fail (System.String message) [0x000a1] in <40f246d3e6214581a540496b6a7456a6>:0 
  at System.Diagnostics.Debug.Fail (System.String message) [0x00000] in <40f246d3e6214581a540496b6a7456a6>:0 
  at LiteDB.Constants.ENSURE (System.Boolean conditional, System.String message) [0x00014] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.DiskReader.ReadPage (System.Int64 position, System.Boolean writable, LiteDB.Engine.FileOrigin origin) [0x0000e] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.Snapshot.ReadPage[T] (System.UInt32 pageID, LiteDB.Engine.FileOrigin& origin, System.Int64& position, System.Int32& walVersion) [0x000a4] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.Snapshot.GetPage[T] (System.UInt32 pageID, LiteDB.Engine.FileOrigin& origin, System.Int64& position, System.Int32& walVersion) [0x00066] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.Snapshot.GetPage[T] (System.UInt32 pageID) [0x00001] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.IndexService.GetNode (LiteDB.Engine.PageAddress address) [0x00012] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.IndexService.AddNode (LiteDB.Engine.CollectionIndex index, LiteDB.BsonValue key, LiteDB.Engine.PageAddress dataBlock, System.Byte level, LiteDB.Engine.IndexNode last) [0x0007a] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.IndexService.AddNode (LiteDB.Engine.CollectionIndex index, LiteDB.BsonValue key, LiteDB.Engine.PageAddress dataBlock, LiteDB.Engine.IndexNode last) [0x00055] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.LiteEngine.InsertDocument (LiteDB.Engine.Snapshot snapshot, LiteDB.BsonDocument doc, LiteDB.BsonAutoId autoId, LiteDB.Engine.IndexService indexer, LiteDB.Engine.DataService data) [0x000f3] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.LiteEngine+<>c__DisplayClass7_0.<Insert>b__0 (LiteDB.Engine.TransactionService transaction) [0x00076] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.LiteEngine.AutoTransaction[T] (System.Func`2[T,TResult] fn) [0x00012] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.LiteEngine.Insert (System.String collection, System.Collections.Generic.IEnumerable`1[T] docs, LiteDB.BsonAutoId autoId) [0x00055] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.LiteCollection`1[T].Insert (T entity) [0x0002e] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 

Delete operation

Fail: invalid page position
  at System.Diagnostics.DefaultTraceListener.Fail (System.String message, System.String detailMessage) [0x00008] in <40f246d3e6214581a540496b6a7456a6>:0 
  at System.Diagnostics.TraceListener.Fail (System.String message) [0x00000] in <40f246d3e6214581a540496b6a7456a6>:0 
  at System.Diagnostics.DefaultTraceListener.Fail (System.String message) [0x00000] in <40f246d3e6214581a540496b6a7456a6>:0 
  at System.Diagnostics.TraceInternal.Fail (System.String message) [0x000a1] in <40f246d3e6214581a540496b6a7456a6>:0 
  at System.Diagnostics.Debug.Fail (System.String message) [0x00000] in <40f246d3e6214581a540496b6a7456a6>:0 
  at LiteDB.Constants.ENSURE (System.Boolean conditional, System.String message) [0x00014] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.DiskReader.ReadPage (System.Int64 position, System.Boolean writable, LiteDB.Engine.FileOrigin origin) [0x0000e] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.Snapshot.ReadPage[T] (System.UInt32 pageID, LiteDB.Engine.FileOrigin& origin, System.Int64& position, System.Int32& walVersion) [0x000a4] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.Snapshot.GetPage[T] (System.UInt32 pageID, LiteDB.Engine.FileOrigin& origin, System.Int64& position, System.Int32& walVersion) [0x00066] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.Snapshot.GetPage[T] (System.UInt32 pageID) [0x00001] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.CollectionService.Get (System.String name, System.Boolean addIfNotExists, LiteDB.Engine.CollectionPage& collectionPage) [0x0001a] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.Snapshot..ctor (LiteDB.Engine.LockMode mode, System.String collectionName, LiteDB.Engine.HeaderPage header, System.UInt32 transactionID, LiteDB.Engine.TransactionPages transPages, LiteDB.Engine.LockService locker, LiteDB.Engine.WalIndexService walIndex, LiteDB.Engine.DiskReader reader, System.Boolean addIfNotExists) [0x00090] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.TransactionService.<CreateSnapshot>g__create|42_0 (LiteDB.Engine.TransactionService+<>c__DisplayClass42_0& ) [0x00000] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.TransactionService.CreateSnapshot (LiteDB.Engine.LockMode mode, System.String collection, System.Boolean addIfNotExists) [0x000b5] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.LiteEngine+<>c__DisplayClass3_0.<Delete>b__0 (LiteDB.Engine.TransactionService transaction) [0x00001] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.LiteEngine.AutoTransaction[T] (System.Func`2[T,TResult] fn) [0x00012] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.Engine.LiteEngine.Delete (System.String collection, System.Collections.Generic.IEnumerable`1[T] ids) [0x0004e] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
  at LiteDB.LiteCollection`1[T].Delete (LiteDB.BsonValue id) [0x00022] in <33e3d1eaf0f44474a0cc39720c90fd6d>:0 
@gaspapp gaspapp added the bug label Jul 24, 2020
@lbnascimento
Copy link
Contributor

@gasblack Could you send me your datafile?

@gaspapp
Copy link
Author

gaspapp commented Jul 24, 2020

@lbnascimento unfortunately I deleted the database when I tried to replicate the issue
I'm still trying to replicate it. Can the datafile be corrupted after a process exit without disposing the database?

@gaspapp
Copy link
Author

gaspapp commented Jul 30, 2020

@gasblack Could you send me your datafile?

I have a corrupted database available, where can I send it to you in private?

In addition, it seems that it's not the datafile to be corrupted but the log file, in fact, if I delete only the log file all seems to work correctly.
But in this manner I lose data right?

@lbnascimento
Copy link
Contributor

@lbnascimento You could send me both files at lbnascimento@inf.ufrgs.br

@lbnascimento
Copy link
Contributor

@gasblack I have identified the issue with the log file that you sent me by email. In this specific case, all the 15 pages in the log file were from uncommited transactions, so you can safely discard the log file and use the data file as if nothing happended.

LiteDB data files are divided in 8192-byte chunks called "pages". For an unencrypted data file, page 0 (the header page) starts at byte 0, but encrypted data files have a "hidden page" before the header contaning the salt used in the encryption and some other stuff. Only the first 64 bytes of this hidden page are used, the rest being filled with zeroes. For whatever reason, this page was written incorrectly in your log file, only the 64 bytes were written, without the padding zeroes.

I manually added the padding zeroes to the page, which fixed the log file, but all of its pages were from uncommited transactions anyway, so that's why it's safe to discard the log file.

Are there any steps to reproduce this issue consistently, or is it intermittent? What OS and .NET environment were you using when the log file was corrupted?

I will work on a fix.

@nicolo-coolshop
Copy link

Hi,
It seems that the problem occurs when the app crashes for some reason and DB is not disposed properly.

It is not possible to simply delete the log file, because we can't know beforehand if it contains any data that has not yet stored.

@lbnascimento
Copy link
Contributor

@nicolo-coolshop You are correct in saying that it is not always correct to discard the log file. What I meant is that in his specific situation, the log file (which was corrupted and which I fixed) happened to only have data from uncommited transactions, so the correct thing to do was to discard it.

If you send me your data and log files, I could check (and potentially fix) if it's the same type of problem.

@nicolo-coolshop
Copy link

I work with @gasblack, so it's the same problem.

How can we prevent that from happening? How can we "detect" and "restore" programatically when it happens?

Are there any steps to reproduce this issue consistently, or is it intermittent? What OS and .NET environment were you using when the log file was corrupted?

We are using WPF and Xamarin IOS/Android platform happens on all platforms.

@lbnascimento
Copy link
Contributor

@nicolo-coolshop

How can we prevent that from happening?

I'm not sure yet, I don't know why this is happening. If you have a consistent series of steps to make this kind of corruption happen, please let me know.

How can we "detect" and "restore" programatically when it happens?

The correct structure for a LiteDB encrypted file (both data or log) is as follows:

  • Byte 0: 1 (indicating that the file is indeed encrypted)
  • Bytes 1-16: the salt used in the encryption
  • Bytes 17-31: 0
  • Bytes 32-63: encrypted 1's (used for password checking)
  • Bytes 64-8191: 0
  • Actual encrypted data starts at byte 8192

What happened in your corrupted log file was that the "Bytes 64-8191" section was ommited, so the data incorrectly started at byte 64. In order to detect and fix such files, you could do something like this:

using (var fs = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite))
{
    var bytes = new byte[128];
    fs.Read(bytes, 0, 128);

    if (bytes[0] == 1 && bytes.Skip(17).Take(15).All(x => x == 0)) //making sure that the file is encrypted
    {
        if (bytes[63] != 0 && bytes[64] != 0) //checking if padding bytes weren't added
        {
            var fixedBytes = new byte[fs.Length + 8192 - 64];

            fs.Position = 0;

            fs.Read(fixedBytes, 0, 64);
            fs.Read(fixedBytes, 8192, (int)fs.Length - 64);

            fs.Position = 0;
            fs.Write(fixedBytes, 0, fixedBytes.Length);
        }
    }
}

I tested this solution with your corrupted log file and it fixed the problem. Of course, you're encouraged to do your own testing. Also, there are probably more efficient ways to achieve the same effect, but I believe the general idea is clear.

Given that I don't know the root cause of this issue, I don't know if it can also affect data files, but the solution that I proposed should work with data files too.

@lbnascimento
Copy link
Contributor

@gasblack @nicolo-coolshop Could you test with the latest master? I believe it is fixed.

@rajapc
Copy link

rajapc commented Oct 13, 2021

Hi, thank you for the great database. But I need some help. I am also getting the same exception. But not sure why. I tried to open the same db using LiteDB Studio which works fine without any issue. But the application throws exception. I copied the same db to different machine and debugged, but no issue there. Why is it happening on a specific machine. Once the issue come in a machine, until I delete that db, I cannot use it anymore. Is the fix or any option to find the root cause? I see this issue frequently. Please help.

I am using the litedb version 5.0.10 from Nuget

2021-10-13 11:09:36.715 +05:30 [ERR] Failed
System.AggregateException: One or more errors occurred. (LiteDB ENSURE: position must be in PAGE_SIZE module)
---> System.Exception: LiteDB ENSURE: position must be in PAGE_SIZE module
at LiteDB.Constants.ENSURE(Boolean conditional, String message)
at LiteDB.Engine.AesStream.Write(Byte[] array, Int32 offset, Int32 count)
at LiteDB.Engine.DiskWriterQueue.ExecuteQueue()
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at LiteDB.Engine.DiskWriterQueue.Wait()
at LiteDB.Engine.WalIndexService.CheckpointInternal()
at LiteDB.Engine.WalIndexService.TryCheckpoint()
at LiteDB.Engine.LiteEngine.Dispose(Boolean disposing)
at LiteDB.SharedEngine.Dispose(Boolean disposing)
at LiteDB.SharedEngine.Dispose()
at .Dispose()
at .()

@Jyoti1682
Copy link

Jyoti1682 commented Dec 21, 2022

@rajapc Are you using LITEDB with single process or multiple process are accessing the DB. We are also facing similar issue with LiteDB

@mbdavid Can you please help us with this exception. We are observing this very frequently

System.Exception: LiteDB ENSURE: position must be in PAGE_SIZE module\r\n
at LiteDB.Constants.ENSURE(Boolean conditional, String message)\r\n
at LiteDB.Engine.AesStream.Write(Byte[] array, Int32 offset, Int32 count)\r\n
at LiteDB.Engine.DiskWriterQueue.ExecuteQueue()\r\n at System.Threading.Tasks.Task.InnerInvoke()\r\n
at System.Threading.Tasks.Task.Execute()\r\n
--- End of inner exception stack trace ---\r\n
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)\r\n
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)\r\n
at LiteDB.Engine.DiskWriterQueue.Wait()\r\n at LiteDB.Engine.WalIndexService.CheckpointInternal()\r\n
at LiteDB.Engine.WalIndexService.TryCheckpoint()\r\n
at LiteDB.Engine.LiteEngine.Dispose(Boolean disposing)\r\n
at LiteDB.SharedEngine.Dispose(Boolean disposing)\r\n
at LiteDB.SharedEngine.Dispose()\r\n
at LiteDB.LiteDatabase.Dispose(Boolean disposing)\r\n
at Provider.LiteDB.LiteDbContext.Dispose(Boolean disposing)\r\n
at Provider.LiteDB.LiteDbContext.Dispose()\r\n

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

5 participants