dotnet / dotnet/efcore

SQLite Error 5: 'database is locked'. - Is this Sqlite error code 5 (busy) or 6 (locked)? What causes it?

Open
#33,133 4 comments 0 reactions 0 assignees View on GitHub
area-adonet-sqlite customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

My ASP.NET application using EF and sqlite is occasionally getting this exception in production under load when multiple clients are hitting API endpoints that update the DB: `Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 5: 'database is locked'.` (see below for full stack trace)

The error is not fatal and the application continues to run after it occurs. The DB is not locked or corrupted and continues to work fine after this. If I retry the SaveChanges immediately after, it always succeeds on the next attempt (at least so far). Unfortunately I have not yet managed to reproduce this error in the debugger. It only happens in production under sufficient load.

This error message is confusing because SQLITE result code 5 is SQLITE_BUSY (not locked), while result code 6 is SQLITE_LOCKED. (See: https://www.sqlite.org/rescode.html#busy ). So it would be helpful to know which result code "Error 5 'database is locked'" is actually referring to. If the DB is just temporarily busy, shouldn't the write be queued by EF? And if the write queue is timing out, shouldn't I get a different exception?

The writes to the DB aren't taking a long time, the application response is fast and the error response is immediate when it happens, so I don't think it's a write queue timeout, but I could be wrong. The DB file is not getting accessed by any other processes. So my guess is something else is going on.

I checked that we are following the recommended usage of the DbContext. It is instantiated in Startup via AddDbContext() and injected with DI so every controller, hub, etc. should be getting a scoped instance. There are a few classes where we don't have DI so we get the DbContext with:

```
using var scope = _serviceProvider.CreateScope();
using var dbContext = scope.ServiceProvider.GetRequiredService();
```

This should also be a unique scoped instance and we don't hold on to this dbContext or pass it to another method or thread or anything, it is only used in this local using block and then disposed. Every call to dbContext.SaveChangesAsync() is preceded with an await: `await dbContext.SaveChangesAsync();`

So there should never be multiple threads making overlapping calls on the same dbContext, unless I'm missing something.

I even tried changing all our DI to pass DbContextFactory instead of the DbContext, or changing the DbContext service lifetime from scoped to transient, so every instance of the DbContext should be unique to avoid any issue of multiple threads accessing the same DbContext. Alas, the error still occurs.

I have tried this using EF package versions 7.0.3, 8.0.1 and 8.0.2 with no difference in this regard.

My current workaround is a for loop with a try/catch around the SaveChangesAsync() to retry the SaveChanges in case it fails. So far I have never seen it require more than 1 retry at most. Which again leads me to believe it's not a long write queue timing out, because then I'd expect to see slow response times and multiple retries required.

Can any experts help me understand what specifically causes this error, what I can look for to try to troubleshoot it, or have any other suggestions? Thanks.

### Include stack traces

```
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 5: 'database is locked'.
at Microsoft.Data.Sqlite.SqliteDataRecord.Dispose()
at Microsoft.Data.Sqlite.SqliteDataReader.NextResult()
at System.Data.Common.DbDataReader.NextResultAsync(CancellationToken cancellationToken)
--- End of stack trace from previous location ---
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
```

### Include provider and version information

EF Core version: 8.0.2
Database provider: Microsoft.EntityFrameworkCore.Sqlite 8.0.2
Target framework: .NET 8.0
Operating system: Windows Server 2016

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.