Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: Bug Report
description: Something isn't working — SQLITE_BUSY still occurring, incorrect behavior, crash
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to file a bug report. The more detail you provide, the faster we can help.

- type: input
id: package_version
attributes:
label: Package Version
description: Which version of EntityFrameworkCore.Sqlite.Concurrency?
placeholder: e.g. 10.1.0
validations:
required: true

- type: input
id: dotnet_version
attributes:
label: .NET Version
description: Output of `dotnet --version`
placeholder: e.g. 10.0.100
validations:
required: true

- type: input
id: os
attributes:
label: Operating System
placeholder: e.g. Windows 11, Ubuntu 24.04, macOS 15
validations:
required: true

- type: textarea
id: error_message
attributes:
label: Error Message / Stack Trace
description: The full exception message and stack trace. Remove any sensitive connection string details.
render: text
validations:
required: true

- type: textarea
id: connection_string
attributes:
label: Connection String (redacted)
description: Your connection string with any sensitive values replaced with `***`
placeholder: "Data Source=app.db"
validations:
required: true

- type: textarea
id: registration
attributes:
label: Registration Code
description: How you registered the context (AddConcurrentSqliteDbContext, AddConcurrentSqliteDbContextFactory, UseSqliteWithConcurrency, etc.)
render: csharp
validations:
required: true

- type: textarea
id: reproduction
attributes:
label: Reproduction Steps
description: Minimal steps to reproduce. A failing test or small console app is ideal.
validations:
required: true

- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What should have happened?
validations:
required: true

- type: textarea
id: actual
attributes:
label: Actual Behavior
description: What actually happened?
validations:
required: true

- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: I am not using `Cache=Shared` in my connection string
required: false
- label: I am using `IDbContextFactory<T>` for concurrent workloads (not a shared `DbContext`)
required: false
- label: The database is on local disk (not NFS/SMB/network path)
required: false
41 changes: 41 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Feature Request
description: Suggest a new feature or improvement
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Thanks for suggesting an improvement. Please describe the problem you're trying to solve — not just the solution.

- type: textarea
id: problem
attributes:
label: Problem Description
description: What problem are you hitting? What can't you do today?
placeholder: "I'm trying to... but currently..."
validations:
required: true

- type: textarea
id: solution
attributes:
label: Proposed Solution
description: What would you like to see added or changed?
validations:
required: true

- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Any workarounds or alternative approaches you've tried?
validations:
required: false

- type: textarea
id: context
attributes:
label: Additional Context
description: Any other context, links, or examples that would help.
validations:
required: false
34 changes: 34 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Summary

<!-- What does this PR do? One paragraph. -->

## Type of Change

- [ ] Bug fix (non-breaking — fixes incorrect behavior)
- [ ] New feature (non-breaking — adds functionality)
- [ ] Breaking change (changes existing public API or behavior)
- [ ] Documentation update
- [ ] CI / build change
- [ ] Performance improvement

## Testing

- [ ] New tests added covering the change
- [ ] All existing tests pass (`dotnet test EFCore.Sqlite.Concurrency.Test/`)
- [ ] If performance-sensitive: benchmarks run and results included below

## Checklist

- [ ] `CHANGELOG.md` updated under `[Unreleased]`
- [ ] XML doc comments added/updated for all public API changes
- [ ] No `Cache=Shared` introduced in any connection string
- [ ] `packages.lock.json` updated if dependencies changed (`dotnet restore`)
- [ ] Breaking changes documented (or none — confirm below)

## Breaking Changes

<!-- List any breaking changes, or write "None." -->

## Benchmark Results (if applicable)

<!-- Paste BenchmarkDotNet output here if this is a performance change -->
14 changes: 11 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,18 @@ jobs:
cache-dependency-path: EntityFrameworkCore.Sqlite.Concurrency/packages.lock.json

- name: Restore dependencies
run: dotnet restore EntityFrameworkCore.Sqlite.Concurrency/EFCore.Sqlite.Concurrency.csproj
run: |
dotnet restore EntityFrameworkCore.Sqlite.Concurrency/EFCore.Sqlite.Concurrency.csproj
dotnet restore EFCore.Sqlite.Concurrency.Test/EFCore.Sqlite.Concurrency.Test.csproj

- name: Build
- name: Build library
run: dotnet build EntityFrameworkCore.Sqlite.Concurrency/EFCore.Sqlite.Concurrency.csproj -c Release --no-restore

- name: Build test project
run: dotnet build EFCore.Sqlite.Concurrency.Test/EFCore.Sqlite.Concurrency.Test.csproj -c Release --no-restore

- name: Test
run: dotnet test EFCore.Sqlite.Concurrency.Test/EFCore.Sqlite.Concurrency.Test.csproj --framework net10.0 -c Release --no-build

- name: Pack
run: dotnet pack EntityFrameworkCore.Sqlite.Concurrency/EFCore.Sqlite.Concurrency.csproj -c Release --no-build -o out
run: dotnet pack EntityFrameworkCore.Sqlite.Concurrency/EFCore.Sqlite.Concurrency.csproj -c Release --no-build -o out
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,13 @@ EntityFrameworkCore.Sqlite.Concurrency/bin/
Tests/bin/

Tests/obj/

EFCore.Sqlite.Concurrency.Test/bin/

EFCore.Sqlite.Concurrency.Test/obj/

EFCore.Sqlite.Concurrency.Benchmarks/bin/

EFCore.Sqlite.Concurrency.Benchmarks/obj/

BenchmarkDotNet.Artifacts/
100 changes: 100 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [Unreleased]

---

## [10.1.0] - 2026-07-05

### Added
- **Channel-based write queue** — replaces `SemaphoreSlim` with `Channel<IWriteRequest>` + single
background writer. Eliminates thundering-herd wakeup storms under high concurrent write load.
All callers park in FIFO order; zero API change required.
- **`WriteQueueCapacity` option** — new `int?` on `SqliteConcurrencyOptions` (default `null` = unbounded).
When set, creates a `BoundedChannel` with `BoundedChannelFullMode.Wait` for back-pressure control.
- **netstandard 2.0 TFM** — `SqliteConnectionEnhancer`, `SqliteWriteQueue`, and `SqliteConcurrencyOptions`
now available on any runtime supporting netstandard 2.0. EF Core APIs remain net10.0-only.
- **BenchmarkDotNet project** — `EFCore.Sqlite.Concurrency.Benchmarks/` with reproducible benchmarks
for bulk insert, concurrent writes, and parallel reads.
- **Stress tests** — `EFCore.Sqlite.Concurrency.Test/` with four correctness-under-load xUnit tests
covering all write paths at 50 concurrent writers.

### Fixed
- `ThreadSafeSqliteContext.ExecuteWriteAsync` always used default `SqliteConcurrencyOptions` instead
of reading the registered interceptor's configured options. Fixed via `TryGetInterceptor` lookup.
- `BulkInsertSafeAsync` used `Skip(n).Take(1000)` — O(n²) in LINQ-to-objects for large lists.
Replaced with `Chunk(1000)` for O(n) enumeration.
- `BulkInsertSafeAsync` did not call `ChangeTracker.Clear()` between batches, causing unbounded
memory growth during large imports. Fixed.

---

## [10.0.3] - 2026-01-15

### Added
- `AddConcurrentSqliteDbContextFactory<T>` — registers `IDbContextFactory<T>` with all concurrency
settings. Recommended pattern for background services, `Task.WhenAll`, and `Channel<T>` consumers.
- Structured logging for `SQLITE_BUSY*` events via `ILoggerFactory` (resolved from DI automatically).
- `GetWalCheckpointStatusAsync` — runs `PRAGMA wal_checkpoint(PASSIVE)` and returns a typed
`WalCheckpointStatus` (IsBusy, TotalWalFrames, CheckpointedFrames, CheckpointProgress).
- `TryReleaseMigrationLockAsync` — detects and optionally clears a stale `__EFMigrationsLock` row
left by a crashed migration process.
- `SynchronousMode` option — configures `PRAGMA synchronous` (Off / Normal / Full / Extra).
- `UpgradeTransactionsToImmediate` option — opt out of the `BEGIN → BEGIN IMMEDIATE` rewrite.

### Fixed
- `SQLITE_BUSY_SNAPSHOT` (extended code 517) now correctly restarts the full operation lambda
instead of retrying the same statement — the only correct fix for a stale WAL read snapshot.
- Exponential backoff now uses full jitter (`[baseDelay, 2×baseDelay]`) to prevent thundering herd.

---

## [10.0.2] - 2025-12-01

### Added
- Startup validation for `SqliteConcurrencyOptions` — invalid values (`MaxRetryAttempts ≤ 0`,
negative `BusyTimeout`) now throw `ArgumentOutOfRangeException` at startup.

### Fixed
- `Cache=Shared` in connection strings now throws `ArgumentException` at startup (was silently
breaking WAL mode semantics in prior versions).

---

## [10.0.1] - 2025-11-01

### Fixed
- Corrected minor bug causing READ-ONLY lock file error on startup (`PrepareForConnectionOpen`
now strips `FileAttributes.ReadOnly` and deletes stale `.db-shm` files).

---

## [10.0.0] - 2025-10-01

### Added
- Initial release.
- `UseSqliteWithConcurrency` — drop-in replacement for `UseSqlite` with WAL mode, write
serialization, `BEGIN IMMEDIATE` transaction upgrade, and exponential-backoff retry.
- `AddConcurrentSqliteDbContext<T>` — DI registration for request-scoped workloads.
- `BulkInsertOptimizedAsync` — batched bulk insert with WAL-optimized transactions.
- `SaveChangesSerializedAsync` — explicit serialized save with retry.
- `ExecuteWithRetryAsync` — generic operation retry wrapper.
- `ThreadSafeSqliteContext<T>` — optional base `DbContext` with write serialization built in.
- `ThreadSafeFactory` — DI-free factory for non-DI environments.
- Per-database `SemaphoreSlim` write lock via `SqliteConnectionEnhancer`.
- `SqliteDiagnostics` — opt-in Spectre.Console diagnostics UI (`-p:IncludeSpectre=true`).
- `MemoryPackExtensions` — opt-in MemoryPack serialization (`-p:IncludeMemoryPack=true`).

[Unreleased]: https://github.com/CornerstoneCode/EntityFrameworkCore.Sqlite.Concurrency/compare/v10.1.0...HEAD
[10.1.0]: https://github.com/CornerstoneCode/EntityFrameworkCore.Sqlite.Concurrency/compare/v10.0.3...v10.1.0
[10.0.3]: https://github.com/CornerstoneCode/EntityFrameworkCore.Sqlite.Concurrency/compare/v10.0.2...v10.0.3
[10.0.2]: https://github.com/CornerstoneCode/EntityFrameworkCore.Sqlite.Concurrency/compare/v10.0.1...v10.0.2
[10.0.1]: https://github.com/CornerstoneCode/EntityFrameworkCore.Sqlite.Concurrency/compare/v10.0.0...v10.0.1
[10.0.0]: https://github.com/CornerstoneCode/EntityFrameworkCore.Sqlite.Concurrency/releases/tag/v10.0.0
Loading
Loading