dotnet / dotnet/SqlClient

WaitHandle replacement activation failure loses the original pooled connection

Open
#4,548 2 comments 0 reactions 1 assignee Claimed by @mdaigle View on GitHub
:new: Triage Needed
Dominant language
C#
Stars
989
Forks
340
Avg merge
4d 19h
Merged PRs (30d)
72

Description

### Describe the bug

When `WaitHandleDbConnectionPool.ReplaceConnection` creates a replacement successfully but the replacement throws during activation, `CreateObject` has already removed the original connection from `_objectList` and added the replacement. `PrepareConnection` then returns the failed replacement to the idle pool.

The original connection remains alive and checked out but is no longer tracked by `_objectList`, while the failed replacement remains pooled instead of being disposed. Pool ownership and metrics no longer describe the physical connections accurately, and the original connection cannot be reclaimed through `ReclaimEmancipatedObjects`.

If you are seeing an exception, include the full exceptions details (message and stack trace).

```
Exception message: The replacement's activation exception is propagated.
Stack trace: N/A
```

### To reproduce

Use a test connection factory whose first connection activates normally and whose second connection opens successfully but throws from `Activate`. Acquire the first connection, then call `WaitHandleDbConnectionPool.ReplaceConnection`.

```c#
DbConnectionInternal original = GetConnection(pool, owner);
factory.FailReplacementActivation = true;
Assert.Throws(() =>
pool.ReplaceConnection(owner, original, timeout));
```

After the exception, the failed replacement is not disposed and is counted as free, while the live original is absent from the pool's tracking list.

### Expected behavior

Dispose the failed replacement, restore the original connection to the pool's tracking list, and leave the original checked out for the caller's reconnect retry. Hard, soft, pooled, free, and active connection counters should reflect exactly that state.

### Further technical details

Microsoft.Data.SqlClient version: current `main`
.NET target: .NET 9.0 reproduction; shared pool code also targets .NET 8.0 and .NET Framework 4.6.2
SQL Server version: Not applicable; deterministic unit-test reproduction uses a fake connection factory
Operating system: macOS; issue is platform-independent

**Additional context**

`ChannelDbConnectionPool.ReplaceConnection` already preserves the original until the replacement is fully activated. The WaitHandle implementation should provide the same rollback behavior.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.