EDIT: (Resolved by number 6)
What causes a System.AccessViolationException
or System.ExecutionEngineException
crash in SQLitePCLRaw.provider.e_sqlite3.dll when multiple threads access my FooDbContext
simultaneously?
I have a Xamarin Forms app (3.5.0.169047) supporting UWP, Android, and iOS, using NETstandard 2.0.3, Microsoft.EntityFrameworkCore 2.2.4, Microsoft.EntityFrameworkCore.Sqlite 2.2.4, and I have a reproducible crash situation (it happens for sure on UWP) that I'm have trouble resolving that arises during simultaneous access to a sqlite database on device from two different threads at once. I have a sync process (pushes local data to an API and pulls online data from an API) that can take up to a minute or so that I need to execute in a separate thread to keep the UI responsive during its operation. I also need to allow querying of local data during sync to allow navigation during sync or other read-only data operations within the app during sync. The long-running sync works fine if I don't do any data access operation during the sync, but crashes right after the completion of any interrupting shorter data access operation.
The two crash exceptions that I've seen occur (possibly timing related for which causes the crash on identical reproductions) are the following, as seen from the Debug output from Visual Studio 2017 (v15.9.5):
An unhandled exception of type 'System.AccessViolationException' occurred in SQLitePCLRaw.provider.e_sqlite3.dll Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
An unhandled exception of type 'System.ExecutionEngineException' occurred in SQLitePCLRaw.provider.e_sqlite3.dll
While debugging, there is no additional detail about the exception; it happens during different long-running lines of the sync code, depending on the timing of doing the interrupting action; and the long-running code where the debugger shows the exception occurring is all contained within a
Try { ... } Catch(Exception) { ... }
try block in my code.
What might be causing this and how can I resolve it?
I've gone through all of the following:
Is the SQLite usage compatible with multiple threads?
- Yes; it is in "Serialized" mode by default according to https://www.sqlite.org/threadsafe.html and that supports multi-thread usage without restriction. The underlying version of SQLite being used is 3.26.0, which I determined while investigating my
Microsoft.Data.Sqlite.SQLiteConnection
information while debugging.
- Yes; it is in "Serialized" mode by default according to https://www.sqlite.org/threadsafe.html and that supports multi-thread usage without restriction. The underlying version of SQLite being used is 3.26.0, which I determined while investigating my
Is it because I can't have more than one connection open with write capabilities simultaneously?
- No; I even modified my connection strings using
Microsoft.Data.Sqlite.SqliteConnectionStringBuilder
to have the appropriateMode
(SqliteOpenMode.ReadOnly
orSqliteOpenMode.ReadWriteCreate
) depending on the needs of each data access.
- No; I even modified my connection strings using
Is it because the interrupting thread's
FooDbContext
'sDispose()
gets rid of resources that the long-running thread'sFooDbContext
relied upon?- No; I investigated this a lot since that was the last breakpoint I could hit prior to the crash. Even when I overrode the
Dispose
method inFooDbContext
to do nothing, not even call the base class'sDispose
(not recommended, but I tried it temporarily), the crash still occurred.
- No; I investigated this a lot since that was the last breakpoint I could hit prior to the crash. Even when I overrode the
Is there a setting I can set using
Microsoft.Data.Sqlite.SqliteConnectionStringBuilder
orMicrosoft.Data.Sqlite.SQLiteConnection
orMicrosoft.EntityFrameworkCore.DbContextOptionsBuilder
'sUseSqlite
function to ensure that Serialized mode is used (I wasn't 100% sure it was at this point)?- No; I looked extensively, and that option must be hidden away in the internals of the SQLite library chosen.
I did a fair amount of reading and research, which still gave me little else I could try.
- https://www.sqlite.org/sharedcache.html
- https://www.sqlite.org/lockingv3.html
- https://www.sqlite.org/atomiccommit.html#sect_9_0
- https://www.sqlite.org/uri.html
- https://docs.microsoft.com/en-us/ef/core/get-started/uwp/getting-started
- https://docs.microsoft.com/en-us/ef/core/get-started/netcore/new-db-sqlite
- https://github.com/aspnet/EntityFrameworkCore/issues/5466
- https://docs.microsoft.com/en-us/windows/uwp/data-access/sqlite-databases
- https://www.connectionstrings.com/sqlite/
- https://csharp.hotexamples.com/examples/-/DbContextOptionsBuilder/UseSqlite/php-dbcontextoptionsbuilder-usesqlite-method-examples.html
- https://system.data.sqlite.org/index.html/info/dd30ecb89d423c4c
- https://www.sqlite.org/src/info/e8d439c77685eca6
- https://forums.asp.net/t/2143077.aspx?EntityFramework+Core+Do+we+have+to+explicitly+dispose+the+DBContext
- https://github.com/aspnet/EntityFrameworkCore/issues/9901
- https://xamarinhelp.com/entity-framework-core-xamarin-forms/
EDIT: Answer that solved this problem for me:
- I noticed that the dll the exception came from was SQLitePCLRaw.provider.e_sqlite3.dll. That led me to looking at what actually sets up the low-level SQLite library, and it is ultimately the call to
SQLitePCL.Batteries_V2.Init();
that selects the platform-specific low-level SQLite provider to be used. Could that be wrong?- Yes; it turns out that after reading through the wiki info at https://github.com/ericsink/SQLitePCL.raw/wiki/SQLitePCL.Batteries.Init#what-does-batteries_v2init-do that the call to
SQLitePCL.Batteries_V2.Init
was intended to be done only once per platform (either in platform specific code, or in the shared code as long asMicrosoft.EntityFrameworkCore.Sqlite
is installed in the shared project as well as each platform specific project). MySQLitePCL.Batteries.Init
usage was incorrectly insideOnConfiguring
inFooDbContext
, which made it called once per configuring of aFooDbContext
instead of only being done once per app startup. Moving theSQLitePCL.Batteries_V2.Init();
line out ofOnConfiguring
, and into theApp.xaml.cs
constructor in my shared project fixed it! The crashes no longer occurred after the interrupting thread's data access. I really hope this saves someone the huge hassle it saved me trying to get to the bottom of this.
- Yes; it turns out that after reading through the wiki info at https://github.com/ericsink/SQLitePCL.raw/wiki/SQLitePCL.Batteries.Init#what-does-batteries_v2init-do that the call to
Answer that solved this problem for me:
SQLitePCL.Batteries_V2.Init();
that selects the platform-specific low-level SQLite provider to be used. Could that be wrong?SQLitePCL.Batteries_V2.Init
was intended to be done only once per platform (either in platform specific code, or in the shared code as long asMicrosoft.EntityFrameworkCore.Sqlite
is installed in the shared project as well as each platform specific project). MySQLitePCL.Batteries.Init
usage was incorrectly insideOnConfiguring
inFooDbContext
, which made it called once per configuring of aFooDbContext
instead of only being done once per app startup. Moving theSQLitePCL.Batteries_V2.Init();
line out ofOnConfiguring
, and into theApp.xaml.cs
constructor in my shared project fixed it! The crashes no longer occurred after the interrupting thread's data access. I really hope this saves someone the huge hassle it saved me trying to get to the bottom of this.