Repository navigation
Don't dispose a DbDataSource the message store does not own - #5097
Merged
Merged
Conversation
MessageDatabase.DisposeAsync() has disposed its DbDataSource unconditionally since 81bb1ce. Disposing an NpgsqlDataSource aborts every physical connection rented from it, so stopping a host tore down a data source the application passed to PersistMessagesWithPostgresql(NpgsqlDataSource) or registered as a tenant database, and with IntegrateWithWolverine() it disposed Marten's own data source, which Marten itself stopped doing in JasperFx/marten#4874. MessageDatabase now takes an ownsDataSource flag, exposed as OwnsDataSource like Weasel's PostgresqlDatabase.OwnsDataSource, and disposes the data source only when Wolverine built it from a connection string. A data source from the caller or from Marten is left to its owner. The MySQL and SQLite stores follow the same rule; SQL Server always builds its own.
There was a problem hiding this comment.
🟢 Approval recommended
Ownership propagation and conditional disposal are consistent, with focused regression coverage and no unresolved blocking findings.
No open issues 🎉
What changed in this PR
This PR makes Wolverine’s relational message stores respect data-source ownership, preserving shared sources when stores shut down.
Changes:
- Disposes data sources only when Wolverine owns them.
- Propagates ownership through PostgreSQL, MySQL, SQLite, and Marten integration.
- Documents ownership and adds shutdown regression tests.
| File | Description |
|---|---|
| src/Persistence/Wolverine.Sqlite/SqliteMessageStore.cs | Accepts the ownership flag. |
| src/Persistence/Wolverine.Sqlite/SqliteConfigurationExtensions.cs | Documents caller ownership. |
| src/Persistence/Wolverine.Sqlite/SqliteBackedPersistence.cs | Determines main-source ownership. |
| src/Persistence/Wolverine.RDBMS/MessageDatabase.cs | Tracks ownership and conditionally disposes sources. |
| src/Persistence/Wolverine.Postgresql/PostgresqlTenantedMessageStore.cs | Preserves caller-owned tenant sources. |
| src/Persistence/Wolverine.Postgresql/PostgresqlMessageStore.cs | Passes ownership through constructors. |
| src/Persistence/Wolverine.Postgresql/PostgresqlConfigurationExtensions.cs | Documents caller ownership. |
| src/Persistence/Wolverine.Postgresql/PostgresqlBackedPersistence.cs | Determines ownership and documents tenant sources. |
| src/Persistence/Wolverine.Marten/WolverineOptionsMartenExtensions.cs | Distinguishes shared and Wolverine-owned sources. |
| src/Persistence/Wolverine.Marten/MartenMessageDatabaseSource.cs | Preserves Marten-owned tenant sources. |
| src/Persistence/Wolverine.Marten/MartenIntegration.cs | Documents master-source ownership. |
| src/Persistence/Wolverine.Marten/AncillaryWolverineOptionsMartenExtensions.cs | Applies ownership rules to ancillary stores. |
| src/Persistence/SqliteTests/Bug_message_store_disposes_data_source_it_does_not_own.cs | Tests caller-owned source preservation. |
| src/Persistence/PostgresqlTests/Bugs/Bug_message_store_disposes_data_source_it_does_not_own.cs | Tests shared-source preservation and owned-source disposal. |
| src/Persistence/MySql/Wolverine.MySql/MySqlTenantedMessageStore.cs | Preserves caller-owned tenant sources. |
| src/Persistence/MySql/Wolverine.MySql/MySqlMessageStore.cs | Accepts the ownership flag. |
| src/Persistence/MySql/Wolverine.MySql/MySqlConfigurationExtensions.cs | Documents caller ownership. |
| src/Persistence/MySql/Wolverine.MySql/MySqlBackedPersistence.cs | Determines ownership and documents tenant sources. |
| src/Persistence/MySql/MySqlTests/Bug_message_store_disposes_data_source_it_does_not_own.cs | Tests caller-owned source preservation. |
| src/Persistence/MartenTests/Bugs/Bug_message_store_disposes_martens_data_source.cs | Tests shared-source and document-store usability. |
Review effort: Balanced
… Wolverine With this PR Wolverine no longer disposes a data source passed to PersistMessagesWithPostgresql or RegisterStaticTenantsByDataSource. multi_tenancy_with_static_tenants_and_data_sources_for_postgresql created four NpgsqlDataSources per test host and relied on Wolverine to dispose them. Its NpgsqlConnection.ClearAllPools() never reaches a data source's own pool, so every test leaked four pools. CIEfCoreNet10 then failed 46 tests with "53300: sorry, too many clients already". MultiTenancyCompliance.OwnedByTheTest registers such a data source and DisposeAsync disposes it after the host. Locally the class peaked at 500 Postgres connections (the server max) and failed 6 tests before, and peaks at 34 with all 20 passing after. ./build.sh CIEfCoreNet10: EfCoreTests.MultiTenancy 255 passed, 0 retries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
This was referenced Oct 21, 2026
This was referenced Oct 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MessageDatabase.DisposeAsync()has disposed itsDbDataSourceunconditionally since 81bb1ce (2024-03, "Ability to use DbDataSource all the way down in message persistence").NpgsqlDataSourceaborts every physical connection rented from it. A data source the application passes toPersistMessagesWithPostgresql(NpgsqlDataSource),RegisterStaticTenantsByDataSourceorRegisterTenants(ITenantedSource<NpgsqlDataSource>)is dead once the host stops, and anything still sharing it fails mid-operation. WithIntegrateWithWolverine()the message store sits on Marten's own data source, so Wolverine's teardown disposes it while theDocumentStoremay still be in use. That undoes Intermittent connection abort mid-operation, introduced in 9.11.0-alpha.1 marten#4874, where Marten stopped disposing a caller-supplied data source (Weasel addedPostgresqlDatabase.OwnsDataSourcefor it). The workaround so far has been to give Wolverine a connection string, and so a second pool.MessageDatabasetakes anownsDataSourceflag, exposed asOwnsDataSourcelike Weasel's, and disposes the data source only when it owns it. Wolverine owns what it builds from a connection string. It does not own a data source passed in by the caller (main store, tenants,MasterDataSource/MainDataSource) or anything from Marten. MySQL and SQLite follow the same rule. SQL Server always builds its own data source, so it is unchanged.MessageDatabaseconstructor stays and meansownsDataSource: true. The store constructors get an optional trailing parameter.DocumentStoreis in use, and that a data source Wolverine built is still disposed. The caller-owned tests fail onmain(ObjectDisposedException; for SQLite, a recording data source reports the disposal) and pass with this change.dotnet build wolverine.slnx -c Release -f net9.0is clean. FullPostgresqlTests,MySqlTestsandSqliteTestsruns fail the same tests with this change as onmain.MartenTestsfails onlyBug_2576, which fails onmaintoo. One thing I noticed along the way: ancillary message stores are never disposed (AncillaryMessageStoreis not disposable and the runtime does not disposeMessageStoreCollection), so their durability agents can outlive the host. I left that alone.