Skip to content

ObjectDisposedException on pooled connection reuse after GSS encryption fallback (10.0.2 regression from #6431) #6506

@Justin-MPA

Description

@Justin-MPA

Description

PR #6431 (released in 10.0.2) introduced a regression where pooled connections are corrupted after a successful GSS encryption fallback. The first query succeeds, but any subsequent query that reuses the pooled connection throws ObjectDisposedException on ManualResetEventSlim.Reset() in ResetCancellation().

This is not latency-dependent and is 100% reproducible with any proxy that rejects GSS session encryption (PlanetScale, Supavisor).

Reproduction

Minimal — no EF Core, no high latency, no concurrency required:

using Npgsql;

// Any PostgreSQL proxy that rejects GSS session encryption
var cs = "Host=aws-us-east-2-1.pg.psdb.cloud;Database=postgres;Username=...;Password=...;Port=5432;SSL Mode=Require;Trust Server Certificate=true";

await using var ds = new NpgsqlDataSourceBuilder(cs).EnableDynamicJson().Build();

// First query — OK (connector opens, GSS fails, retries without GSS, succeeds)
await using (var conn = await ds.OpenConnectionAsync())
    await conn.ExecuteScalarAsync("SELECT 1");

// Second query — FAILS (reuses pooled connector with corrupted ManualResetEventSlim)
await using (var conn = await ds.OpenConnectionAsync())
    await conn.ExecuteScalarAsync("SELECT 1"); // ObjectDisposedException

Stack Trace

System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Threading.ManualResetEventSlim'.
   at System.Threading.ManualResetEventSlim.Reset()
   at Npgsql.Internal.NpgsqlConnector.ResetCancellation()
   at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)

Version Comparison

Version Behavior
10.0.0 Works — Break() clears the pool after GSS failure, corrupted connector is evicted and never reused
10.0.2 Fails — #6431 correctly stopped clearing the pool, but the connector that went through GSS-fail-retry has a disposed ReadingPrependedMessagesMRE
10.0.2 + GssEncryptionMode=Disable Works — GSS negotiation skipped entirely

Root Cause Analysis

In OpenCore(), when GSS encryption fails with GssEncryptionMode.Prefer:

  1. Exception is caught by the when (gssEncMode == GssEncryptionMode.Prefer || ...) filter
  2. conn.Cleanup() is called — disposes stream/buffers but not ReadingPrependedMessagesMRE
  3. OpenCore() retries recursively with GssEncryptionMode.Disable
  4. Retry succeeds, connector enters pool in ConnectorState.Ready
  5. On reuse, ResetCancellation()ReadingPrependedMessagesMRE.Reset()ObjectDisposedException

Before #6431, this was masked: Break() always called DataSource.Clear() during connection establishment failures, which incremented _clearCounter. When the connector was returned to the pool, Return() saw the counter mismatch and called CloseConnector() → the corrupted connector was never reused.

After #6431, Break() skips DataSource.Clear() when state == ConnectorState.Connecting (to avoid unnecessarily clearing the pool on retriable failures). This is correct behavior, but it exposes the underlying issue: the connector's ReadingPrependedMessagesMRE is somehow disposed during the GSS-fail-retry cycle despite Cleanup() not touching it.

Proposed Fix

The ReadingPrependedMessagesMRE is declared as readonly and initialized once in the field initializer. Cleanup() does not dispose it — only FullCleanup() does. Yet it is disposed after the retry. This suggests either:

  1. An indirect disposal path triggered by Cleanup() (e.g., stream disposal triggering a callback that calls Break()FullCleanup())
  2. A race between the retry and another thread

Suggested approach: After Cleanup() in the GSS retry path, reinitialize the ReadingPrependedMessagesMRE to ensure it is in a valid state before the recursive OpenCore() call. Alternatively, make the field non-readonly and recreate it in Cleanup().

Workaround

Set GssEncryptionMode=Disable in the connection string for proxies that don't support GSS session encryption.

Environment

  • Npgsql: 10.0.2
  • .NET: 10.0
  • OS: Windows 11
  • Database proxies tested: PlanetScale, Supavisor (reported by others)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions