Skip to content

Don't dispose a DbDataSource the message store does not own - #5097

Merged
jeremydmiller merged 2 commits into
JasperFx:mainfrom
lahma:fix/datasource-ownership
Oct 20, 2026
Merged

jeremydmiller merged 2 commits into
JasperFx:mainfrom
lahma:fix/datasource-ownership

Conversation

@lahma

@lahma lahma commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

MessageDatabase.DisposeAsync() has disposed its DbDataSource unconditionally since 81bb1ce (2024-03, "Ability to use DbDataSource all the way down in message persistence").

  • Why it matters. Disposing an NpgsqlDataSource aborts every physical connection rented from it. A data source the application passes to PersistMessagesWithPostgresql(NpgsqlDataSource), RegisterStaticTenantsByDataSource or RegisterTenants(ITenantedSource<NpgsqlDataSource>) is dead once the host stops, and anything still sharing it fails mid-operation. With IntegrateWithWolverine() the message store sits on Marten's own data source, so Wolverine's teardown disposes it while the DocumentStore may 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 added PostgresqlDatabase.OwnsDataSource for it). The workaround so far has been to give Wolverine a connection string, and so a second pool.
  • The rule now. MessageDatabase takes an ownsDataSource flag, exposed as OwnsDataSource like 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.
  • Compatibility. The existing protected MessageDatabase constructor stays and means ownsDataSource: true. The store constructors get an optional trailing parameter.
  • Tests. New tests check that a caller-supplied data source survives host stop (PostgreSQL main store and tenant, MySQL, SQLite), that Marten's data source survives both host stop and disposing the message store while the DocumentStore is in use, and that a data source Wolverine built is still disposed. The caller-owned tests fail on main (ObjectDisposedException; for SQLite, a recording data source reports the disposal) and pass with this change.
  • Verified locally (net10.0, Windows). dotnet build wolverine.slnx -c Release -f net9.0 is clean. Full PostgresqlTests, MySqlTests and SqliteTests runs fail the same tests with this change as on main. MartenTests fails only Bug_2576, which fails on main too. One thing I noticed along the way: ancillary message stores are never disposed (AncillaryMessageStore is not disposable and the runtime does not dispose MessageStoreCollection), so their durability agents can outlive the host. I left that alone.

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.
Copilot AI balanced review requested due to automatic review settings October 20, 2026 08:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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
@jeremydmiller
jeremydmiller merged commit 5b2c491 into JasperFx:main Oct 20, 2026
47 checks passed
@lahma
lahma deleted the fix/datasource-ownership branch October 20, 2026 14:59
This was referenced Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants