-
Notifications
You must be signed in to change notification settings - Fork 0
Concurrency
DynamoDbLite needs no application-level lock. SQLite serializes writers for you, and the Microsoft.Data.Sqlite driver makes a blocked writer wait rather than fail. This page covers how that works and how to tune it.
DynamoDbLite opens a fresh SQLite connection per operation. Concurrent calls run on separate connections against the same database — in-memory stores share one database through Cache=Shared (see Getting Started).
Two mechanisms combine to serialize concurrent writers:
-
SQLite's single-writer lock — the mutual exclusion. SQLite allows one writer at a time. A write transaction holds the database write lock (table-level under shared cache), so two writers are never inside a write transaction at the same instant. Microsoft.Data.Sqlite's default transaction is
Serializable, which issuesBEGIN IMMEDIATE— a writer takes the lock when the transaction begins, not at its first write. -
The driver's retry loop — the waiting. SQLite does not queue the loser. When a second writer asks for a held lock, SQLite returns a lock error immediately. Microsoft.Data.Sqlite catches it and retries the statement in a loop until the holder commits and the lock frees, or until
CommandTimeout(default 30 seconds) elapses and it throws.
The result: writers run one at a time, and a contender waits its turn instead of failing — as long as the holder releases within the timeout. Reads do not enter this contest; many connections read at once. The contention that matters is writer against writer.
The lock class — and therefore which timeout governs the wait — depends on the store:
-
In-memory (shared cache). A write conflict raises
SQLITE_LOCKED_SHAREDCACHE. SQLite's busy handler is not invoked for shared-cache locks, sobusy_timeoutdoes nothing. The driver's retry, bounded byCommandTimeout, is the only thing that makes a writer wait. -
File-backed. A write conflict raises
SQLITE_BUSY. SQLite's busy handler is invoked, sobusy_timeoutis the right knob. With WAL enabled, readers also proceed alongside the writer.
- Don't add an application-level lock. SQLite's write lock already gives you mutual exclusion; a second lock on top is redundant.
- Keep write transactions short. The lock is held for the duration of the transaction, and contending writers retry the whole time it is held. Shorter transactions mean less retrying and fewer timeouts.
-
For in-memory, tune the wait with
CommandTimeout, notbusy_timeout. The in-memory wait budget is the driver's retry, governed byCommandTimeout. Set it through the connection string'sDefault Timeoutkeyword — for example,Default Timeout=5for a five-second budget.busy_timeoutis inert for shared-cache in-memory. -
For file-backed stores, use
busy_timeout. There the conflict isSQLITE_BUSY, the busy handler is invoked, andbusy_timeoutis the right knob. Set it withWithPragma. -
Know the failure mode. A writer that cannot get the lock within the timeout throws
SQLITE_LOCKED(in-memory) orSQLITE_BUSY(file) rather than blocking forever. The timeout is your backpressure limit, not a guarantee of eventual success.
In short: SQLite serializes, the driver's bounded retry makes contenders wait instead of fail, you tune the wait per store type — CommandTimeout for memory, busy_timeout for file — and you keep transactions short.
- Storage Architecture — the store implementations and where these locks live.
-
DI and Configuration —
WithPragma,WithConnectionInitializer, and the options surface. -
Transactions —
TransactWriteItemsand transaction semantics.
Repo · NuGet · API Parity
Getting started
Reference
- Table Operations
- Item Operations
- Query and Scan
- Batch Operations
- Transactions
- Secondary Indexes
- TTL
- Tags and Admin
- DI and Configuration
- Concurrency
- Performance
- API Parity
- FAQ
Recipes
- DynamoDBContext for tests
- xUnit per-test isolation
- ASP.NET Core integration test fixture
- Migrating tests off DynamoDB Local
Internals