@@ -2236,7 +2236,16 @@ private bool TryOpen(TaskCompletionSource<DbConnectionInternal> retry, SqlConnec
22362236 return result ;
22372237 }
22382238
2239- private bool TryOpenInner ( TaskCompletionSource < DbConnectionInternal > retry )
2239+ /// <summary>
2240+ /// Completes the inner open/replace operation and initializes parser state for the active inner connection.
2241+ /// </summary>
2242+ /// <param name="retry">Retry continuation used by async open paths.</param>
2243+ /// <returns><see langword="true"/> when open initialization completed synchronously; otherwise <see langword="false"/>.</returns>
2244+ /// <remarks>
2245+ /// The inner connection is snapshotted after the open call so downstream parser access uses a single observed
2246+ /// instance and does not rely on a second racy read of <see cref="InnerConnection"/>.
2247+ /// </remarks>
2248+ internal bool TryOpenInner ( TaskCompletionSource < DbConnectionInternal > retry )
22402249 {
22412250 // Create a single TimeoutTimer that represents the overall connection-open budget for this
22422251 // attempt. The timer is threaded through every layer (inner connection state transitions,
@@ -2408,11 +2417,32 @@ internal void RemoveWeakReference(object value)
24082417
24092418 // ClosedBusy->Closed (never opened)
24102419 // Connecting->Closed (exception during open, return to previous closed state)
2420+ /// <summary>
2421+ /// Finalizes a non-evented transition from a transitional inner state to a stable closed state.
2422+ /// </summary>
2423+ /// <param name="to">The target inner connection state object.</param>
2424+ /// <remarks>
2425+ /// This path is restricted to transition sources that intentionally serialize external callers while state is
2426+ /// being updated. A compare-and-swap prevents stale writers from overwriting a newer inner connection state.
2427+ /// </remarks>
24112428 internal void SetInnerConnectionTo ( DbConnectionInternal to )
24122429 {
24132430 Debug . Assert ( _innerConnection != null , "null InnerConnection" ) ;
24142431 Debug . Assert ( to != null , "to null InnerConnection" ) ;
2415- _innerConnection = to ;
2432+
2433+ DbConnectionInternal originalInnerConnection = _innerConnection ;
2434+ if ( originalInnerConnection != DbConnectionClosedBusy . SingletonInstance &&
2435+ originalInnerConnection != DbConnectionClosedConnecting . SingletonInstance )
2436+ {
2437+ // SetInnerConnectionTo is only valid while leaving a known transitional state.
2438+ throw ADP . InternalConnectionError ( ADP . ConnectionError . InvalidSourceStateForSetInnerConnectionTo ) ;
2439+ }
2440+
2441+ if ( originalInnerConnection != Interlocked . CompareExchange ( ref _innerConnection , to , originalInnerConnection ) )
2442+ {
2443+ // Reject stale writers when another thread already advanced the state machine.
2444+ throw ADP . InternalConnectionError ( ADP . ConnectionError . FailedToCommitSetInnerConnectionTo ) ;
2445+ }
24162446 }
24172447
24182448 // Closed->Connecting: prevent set_ConnectionString during Open
@@ -2430,14 +2460,37 @@ internal bool SetInnerConnectionFrom(DbConnectionInternal to, DbConnectionIntern
24302460
24312461 // OpenBusy->Closed (previously opened)
24322462 // Connecting->Open
2463+ /// <summary>
2464+ /// Finalizes an evented state transition from a transitional inner state to a stable target state.
2465+ /// </summary>
2466+ /// <param name="to">The target inner connection state object.</param>
2467+ /// <remarks>
2468+ /// This path is intentionally restricted to the expected transition sources (<see cref="DbConnectionOpenBusy"/>
2469+ /// and <see cref="DbConnectionClosedConnecting"/>). A compare-and-swap enforces that the transition commits only
2470+ /// if no concurrent writer has already changed <see cref="_innerConnection"/>.
2471+ /// </remarks>
24332472 internal void SetInnerConnectionEvent ( DbConnectionInternal to )
24342473 {
24352474 Debug . Assert ( _innerConnection != null , "null InnerConnection" ) ;
24362475 Debug . Assert ( to != null , "to null InnerConnection" ) ;
24372476
2438- ConnectionState originalState = _innerConnection . State & ConnectionState . Open ;
2477+ DbConnectionInternal originalInnerConnection = _innerConnection ;
2478+ if ( originalInnerConnection != DbConnectionOpenBusy . SingletonInstance &&
2479+ originalInnerConnection != DbConnectionClosedConnecting . SingletonInstance )
2480+ {
2481+ // Evented transitions are valid only from the two expected transitional sources.
2482+ throw ADP . InternalConnectionError ( ADP . ConnectionError . InvalidSourceStateForSetInnerConnectionEvent ) ;
2483+ }
2484+
2485+ ConnectionState originalState = originalInnerConnection . State & ConnectionState . Open ;
24392486 ConnectionState currentState = to . State & ConnectionState . Open ;
24402487
2488+ if ( originalInnerConnection != Interlocked . CompareExchange ( ref _innerConnection , to , originalInnerConnection ) )
2489+ {
2490+ // Ensure event and close-count side effects are tied to a committed transition only.
2491+ throw ADP . InternalConnectionError ( ADP . ConnectionError . FailedToCommitSetInnerConnectionEvent ) ;
2492+ }
2493+
24412494 if ( ( originalState != currentState ) && ( ConnectionState . Closed == currentState ) )
24422495 {
24432496 unchecked
@@ -2446,8 +2499,6 @@ internal void SetInnerConnectionEvent(DbConnectionInternal to)
24462499 }
24472500 }
24482501
2449- _innerConnection = to ;
2450-
24512502 if ( ConnectionState . Closed == originalState && ConnectionState . Open == currentState )
24522503 {
24532504 OnStateChange ( DbConnectionInternal . StateChangeOpen ) ;
@@ -2574,9 +2625,10 @@ private void ConnectionString_Set(ConnectionPoolKey key)
25742625 flag = SetInnerConnectionFrom ( DbConnectionClosedBusy . SingletonInstance , connectionInternal ) ;
25752626 if ( flag )
25762627 {
2628+ // Publish option/pool metadata before releasing the busy state back to a stable closed state.
25772629 _userConnectionOptions = connectionOptions ;
25782630 _poolGroup = poolGroup ;
2579- _innerConnection = DbConnectionClosedNeverOpened . SingletonInstance ;
2631+ SetInnerConnectionTo ( DbConnectionClosedNeverOpened . SingletonInstance ) ;
25802632 }
25812633 }
25822634 if ( ! flag )
0 commit comments