fix(WebAPI): Apply SQLite WAL and busy_timeout PRAGMAs to Quartz ADO.…#620
Conversation
…NET connections
Quartz's AdoJobStore opens raw ADO.NET SqliteConnection objects that
bypass the EF Core SqliteConnectionEnhancer interceptor pipeline. Without
busy_timeout, concurrent writes fail instantly with SQLITE_BUSY
('database is locked') during MisfireHandler.Manage().
Add QuartzSqliteConnectionProvider — a custom IDbProvider that hooks
StateChange to call SqliteConnectionEnhancer.ApplyRuntimePragmas() on
every connection Open(), applying the same PRAGMAs (WAL, busy_timeout,
synchronous, cache_size, etc.) as EF Core connections.
Set BindByName=true, ParameterNamePrefix="@", and UseParameterNamePrefixInParameterCollection=true so that AdoUtil.AddCommandParameter does not substitute SQL parameter references with empty strings. Without this, DbMetadata defaults (BindByName=false, ParameterNamePrefix=null) caused the !BindByName branch to replace every @paramName reference with null (empty string), corrupting the SQL text. On fresh databases, this produced: UPDATE QRTZ_TRIGGERS SET TRIGGER_STATE = WHERE ... which SQLite rejects with "near WHERE: syntax error". Also simplifies the StateChange PRAGMA to a synchronous inline busy_timeout=30000 command, replacing the earlier async ApplyRuntimePragmas call to avoid fire-and-forget issues.
… telling the user their credentials are invalid
…owing the validity
…a Plex server finished when connectable or all fail
|
@coderabbitai full review |
✅ Action performedFull review finished. |
This comment was marked as resolved.
This comment was marked as resolved.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/Application/_Shared/Config/Quartz/QuartzSqliteConnectionProvider.cs (1)
79-82: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick winApply all intended SQLite PRAGMAs.
The PR summary states that this provider configures
WAL,busy_timeout,synchronous, andcache_size. However, the code currently only setsbusy_timeout = 30000;. Omitting the other PRAGMAs, especiallysynchronous = NORMAL, causes SQLite to fall back to its default behavior, which can result in excessive disk I/O and degrade concurrent performance.⚡ Proposed fix
- using var pragmaCmd = conn.CreateCommand(); - pragmaCmd.CommandText = "PRAGMA busy_timeout = 30000;"; - pragmaCmd.ExecuteNonQuery(); + using var pragmaCmd = conn.CreateCommand(); + pragmaCmd.CommandText = @" + PRAGMA busy_timeout = 30000; + PRAGMA journal_mode = WAL; + PRAGMA synchronous = NORMAL; + PRAGMA cache_size = -20000;"; + pragmaCmd.ExecuteNonQuery();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Application/_Shared/Config/Quartz/QuartzSqliteConnectionProvider.cs` around lines 79 - 82, Update the SQLite initialization in the connection provider to apply all intended PRAGMAs: enable WAL, retain the 30-second busy timeout, set synchronous mode to NORMAL, and configure the intended cache_size. Ensure each setting is executed on the created connection alongside the existing pragma command.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@tests/UnitTests/Application.UnitTests/PlexServerConnection/CheckAllConnectionsByServer/CheckAllConnectionsStatusByPlexServerCommand.UnitTests.cs`:
- Around line 140-144: Update the test around
CheckAllConnectionsStatusByPlexServerCommand to construct the request with a
non-default timeout such as 30, then change the ICommandExecutor verification
predicate to require CheckConnectionStatusByIdCommand.Timeout == 30 while
preserving the existing invocation count and cancellation-token checks.
---
Duplicate comments:
In `@src/Application/_Shared/Config/Quartz/QuartzSqliteConnectionProvider.cs`:
- Around line 79-82: Update the SQLite initialization in the connection provider
to apply all intended PRAGMAs: enable WAL, retain the 30-second busy timeout,
set synchronous mode to NORMAL, and configure the intended cache_size. Ensure
each setting is executed on the created connection alongside the existing pragma
command.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7e5d28a0-5c4f-4b6c-83a9-b1284a0f8175
⛔ Files ignored due to path filters (51)
src/AppHost/AppHost.csprojis excluded by!**/*.csprojand included by nonesrc/AppHost/ClientApp/cypress/e2e/pages/dialogs/check-server-connections-dialog.cy.tsis excluded by none and included by nonesrc/AppHost/ClientApp/cypress/e2e/pages/settings/accounts/account-create.cy.tsis excluded by none and included by nonesrc/AppHost/ClientApp/src/components/Dialogs/AccountDialog/AccountTokenValidateDialog.vueis excluded by none and included by nonesrc/AppHost/ClientApp/src/components/Dialogs/AccountDialog/AccountVerificationCodeDialog.vueis excluded by none and included by nonesrc/AppHost/ClientApp/src/components/Dialogs/CheckServerConnectionsDialog.vueis excluded by none and included by nonesrc/AppHost/ClientApp/src/lang/de-DE.jsonis excluded by none and included by nonesrc/AppHost/ClientApp/src/lang/en-US.jsonis excluded by none and included by nonesrc/AppHost/ClientApp/src/lang/fr-FR.jsonis excluded by none and included by nonesrc/AppHost/ClientApp/src/lang/pl-PL.jsonis excluded by none and included by nonesrc/AppHost/ClientApp/src/store/accountDialogStore.tsis excluded by none and included by nonesrc/AppHost/ClientApp/tests/nuxt/stores/account-dialog-store/validate-plex-account.test.tsis excluded by none and included by nonesrc/AppHost/ClientApp/tests/nuxt/stores/account-dialog-store/validate-plex-token.test.tsis excluded by none and included by nonesrc/AppHost/ClientApp/tests/nuxt/stores/account-dialog-store/validate-verification-code.test.tsis excluded by none and included by nonesrc/AppHost/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/Application.Contracts/Application.Contracts.csprojis excluded by!**/*.csprojand included by nonesrc/Application.Contracts/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/Application/Application.csprojis excluded by!**/*.csprojand included by nonesrc/Application/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/BackgroundJobs.Contracts/BackgroundJobs.Contracts.csprojis excluded by!**/*.csprojand included by nonesrc/BackgroundJobs.Contracts/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/BackgroundJobs/BackgroundJobs.csprojis excluded by!**/*.csprojand included by nonesrc/BackgroundJobs/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/Data.Contracts/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/Data/Data.csprojis excluded by!**/*.csprojand included by nonesrc/Data/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/External/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/FileSystem/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/Identity.Contracts/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/Identity/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/PlexApi.Contracts/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/PlexApi/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/PublicAPI/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonesrc/SignalR/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/BaseTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/IntegrationTests/IntegrationTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/AppHost.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/Application.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/BackgroundJobs.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/BaseTests.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/Build.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/Data.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/Domain.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/Environment.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/External.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/FileSystem.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/FluentResultExtension.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/Logging.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/PlexApi.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/PublicApi.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by nonetests/UnitTests/Settings.UnitTests/packages.lock.jsonis excluded by!**/packages.lock.jsonand included by none
📒 Files selected for processing (14)
src/Application/PlexServerConnection/CheckAllConnectionsByServer/CheckAllConnectionsStatusByPlexServerCommand.cssrc/Application/PlexServerConnection/CheckConnectionStatus/CheckConnectionStatusByIdCommand.cssrc/Application/PlexServers/InspectPlexServer/InspectPlexServerJob.cssrc/Application/_Shared/Config/Autofac/QuartzModule.cssrc/Application/_Shared/Config/Quartz/QuartzSqliteConnectionProvider.cssrc/BackgroundJobs/LibrarySync/Commands/RefreshLibraryMedia/CRUD/InsertMediaMetaDataCommand.cssrc/Data.Contracts/Extensions/DbContext/DbContextExtensions.PlexMetaData.cssrc/PlexApi.Contracts/PlexServer/GetServerStatus/GetServerStatusCommand.cssrc/PlexApi/PlexAPI/PlexAPIClient/PlexApiClient.cssrc/PlexApi/PlexServer/GetServerStatus/GetServerStatusCommandHandler.cstests/UnitTests/Application.UnitTests/BackgroundServices/Listeners/DownloadJobListener.UnitTests.cstests/UnitTests/Application.UnitTests/PlexServerConnection/CheckAllConnectionsByServer/CheckAllConnectionsStatusByPlexServerCommand.UnitTests.cstests/UnitTests/Application.UnitTests/PlexServers/InspectPlexServer/InspectPlexServerJob.UnitTests.cstests/UnitTests/PlexApi.UnitTests/PlexApiClient/PlexApiClient.UnitTests.cs
| Mock.Mock<ICommandExecutor>() | ||
| .Verify( | ||
| x => x.Send(It.Is<CheckConnectionStatusByIdCommand>(command => command.Timeout == 10), It.IsAny<CancellationToken>()), | ||
| Times.AtLeastOnce() | ||
| ); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Test a non-default timeout to cover propagation.
Because the request uses the default value, this assertion would still pass if the handler hardcoded 10. Construct the request with a distinct timeout, such as 30, and assert that CheckConnectionStatusByIdCommand.Timeout == 30.
Proposed test adjustment
-var request = new CheckAllConnectionsStatusByPlexServerCommand(1);
+var request = new CheckAllConnectionsStatusByPlexServerCommand(1, 30);
- command => command.Timeout == 10
+ command => command.Timeout == 30🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@tests/UnitTests/Application.UnitTests/PlexServerConnection/CheckAllConnectionsByServer/CheckAllConnectionsStatusByPlexServerCommand.UnitTests.cs`
around lines 140 - 144, Update the test around
CheckAllConnectionsStatusByPlexServerCommand to construct the request with a
non-default timeout such as 30, then change the ICommandExecutor verification
predicate to require CheckConnectionStatusByIdCommand.Timeout == 30 while
preserving the existing invocation count and cancellation-token checks.
## [0.38.3-dev.1](v0.38.2...v0.38.3-dev.1) (2026-07-20) ### Bug Fixes * **WebAPI:** Add correct DbMetadata to QuartzSqliteConnectionProvider ([b553ed9](b553ed9)) * **WebAPI:** Apply SQLite WAL and busy_timeout PRAGMAs to Quartz ADO.… ([#620](#620)) ([fcebb9a](fcebb9a)) * **WebAPI:** Apply SQLite WAL and busy_timeout PRAGMAs to Quartz ADO.NET connections ([35e087e](35e087e)) * **Web-UI:** Fix 2FA verification code pop-up not showing and instead telling the user their credentials are invalid ([a811e1d](a811e1d)) * **WebAPI:** Fix connections not completing when they time-out ([68f91c7](68f91c7)) * **Web-UI:** Fix the Plex server connectable count, it will now show a Plex server finished when connectable or all fail ([1b27097](1b27097)) * **Web-UI:** Fixed the invalid verification code input for 2FA not showing the validity ([7d80657](7d80657)) * **WebAPI:** Prevent metadata insert races ([f1d0b4e](f1d0b4e)) * **WebAPI:** Use DbContext factory for Plex inspection ([0bb1c87](0bb1c87))
## [0.38.3](v0.38.2...v0.38.3) (2026-07-20) ### Bug Fixes * **WebAPI:** Add correct DbMetadata to QuartzSqliteConnectionProvider ([b553ed9](b553ed9)) * **WebAPI:** Apply SQLite WAL and busy_timeout PRAGMAs to Quartz ADO.… ([#620](#620)) ([fcebb9a](fcebb9a)) * **WebAPI:** Apply SQLite WAL and busy_timeout PRAGMAs to Quartz ADO.NET connections ([35e087e](35e087e)) * **Web-UI:** Fix 2FA verification code pop-up not showing and instead telling the user their credentials are invalid ([a811e1d](a811e1d)) * **WebAPI:** Fix connections not completing when they time-out ([68f91c7](68f91c7)) * **Web-UI:** Fix the Plex server connectable count, it will now show a Plex server finished when connectable or all fail ([1b27097](1b27097)) * **Web-UI:** Fixed the invalid verification code input for 2FA not showing the validity ([7d80657](7d80657)) * **WebAPI:** Prevent metadata insert races ([f1d0b4e](f1d0b4e)) * **WebAPI:** Use DbContext factory for Plex inspection ([0bb1c87](0bb1c87))
…NET connections
Quartz's AdoJobStore opens raw ADO.NET SqliteConnection objects that
bypass the EF Core SqliteConnectionEnhancer interceptor pipeline. Without
busy_timeout, concurrent writes fail instantly with SQLITE_BUSY
('database is locked') during MisfireHandler.Manage().
Add QuartzSqliteConnectionProvider — a custom IDbProvider that hooks
StateChange to call SqliteConnectionEnhancer.ApplyRuntimePragmas() on
every connection Open(), applying the same PRAGMAs (WAL, busy_timeout,
synchronous, cache_size, etc.) as EF Core connections.
Summary by CodeRabbit