Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,63 @@ public DbConnectionInternal ReplaceConnection(
DbConnectionInternal oldConnection,
TimeoutTimer timeout)
{
throw new NotImplementedException();
SqlClientEventSource.Log.TryPoolerTraceEvent(
"<prov.DbConnectionPool.ReplaceConnection|RES|CPOOL> {0}, replacing connection.", Id);

// We replace oldConnection with a brand-new physical connection. oldConnection is left completely
// untouched until the replacement has been successfully activated, so any failure leaves it reusable
// by the caller's reconnect retry loop (SqlConnection.ReconnectAsync). oldConnection is checked out,
// so its pool slot is stable: nothing else can remove or steal it (Clear/Shutdown/Prune only drain
// idle connections). We only touch the slots once the replacement is live. While the new connection
// is activating the pool may briefly hold one physical connection over MaxPoolSize (the dead old one
// plus the new one), but oldConnection is dead anyway and the reserved slot count never exceeds the
// maximum.
// TODO: Prefer reusing an idle connection before establishing a new one
DbConnectionInternal newConnection = ConnectionFactory.CreatePooledConnection(owningObject, this, timeout);

try
{
// Stamp with the current generation so a later Clear() can discard it.
newConnection.ClearGeneration = _clearGeneration;

lock (newConnection)
{
// PostPop requires a lock on the connection.
newConnection.PostPop(owningObject);
}

// Activate under the old connection's ambient transaction (FR-002/FR-003).
// TODO: Full transaction enlistment support (Story 2).
newConnection.ActivateConnection(oldConnection.EnlistedTransaction);

bool replaced = _connectionSlots.TryReplace(oldConnection, newConnection);

if (!replaced)
{
// This should never happen because oldConnection is checked out and its slot is stable.
// Still, we need to check or we could vend a connection that is not tracked by the pool.
// TODO: error types and localization
throw new InvalidOperationException("Connection is no longer in the pool and cannot be replaced.");
}
Comment on lines +276 to +282
}
catch
{
// Dispose the brand-new connection; it never took a slot. oldConnection is untouched.
newConnection.DeactivateConnection();
newConnection.Dispose();
throw;
}

oldConnection.DeactivateConnection();
oldConnection.Dispose();

// A hard connect is already recorded by the connection factory.
SqlClientDiagnostics.Metrics.SoftConnectRequest();

SqlClientEventSource.Log.TryPoolerTraceEvent(
"<prov.DbConnectionPool.ReplaceConnection|RES|CPOOL> {0}, connection replaced successfully.", Id);

return newConnection;
}

/// <inheritdoc />
Expand Down Expand Up @@ -765,10 +821,11 @@ private async Task<DbConnectionInternal> GetInternalConnection(
/// </summary>
/// <param name="owningObject">The owning DbConnection instance.</param>
/// <param name="connection">The DbConnectionInternal to be activated.</param>
/// <param name="transaction">The transaction to enlist the connection in, or null to activate cleanly.</param>
/// <exception cref="Exception">
/// Thrown when any exception occurs during connection activation.
/// </exception>
private void PrepareConnection(DbConnection owningObject, DbConnectionInternal connection)
private void PrepareConnection(DbConnection owningObject, DbConnectionInternal connection, Transaction? transaction = null)
{
lock (connection)
{
Expand All @@ -778,8 +835,7 @@ private void PrepareConnection(DbConnection owningObject, DbConnectionInternal c

try
{
//TODO: pass through transaction
connection.ActivateConnection(null);
connection.ActivateConnection(transaction);
}
catch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,26 @@ internal bool TryRemove(DbConnectionInternal connection)
return false;
}

/// <summary>
/// Atomically replaces an existing connection with a new one in the same slot.
/// The reservation count is unchanged because the slot is reused.
/// </summary>
/// <param name="oldConnection">The connection currently occupying the slot.</param>
/// <param name="newConnection">The connection to place into the slot.</param>
/// <returns>True if the old connection was found and replaced; otherwise, false.</returns>
internal bool TryReplace(DbConnectionInternal oldConnection, DbConnectionInternal newConnection)
{
for (int i = 0; i < _connections.Length; i++)
{
if (Interlocked.CompareExchange(ref _connections[i], newConnection, oldConnection) == oldConnection)
{
return true;
}
}

return false;
}

/// <summary>
/// Attempts to reserve a spot in the collection.
/// </summary>
Expand Down
Loading
Loading