@@ -47,6 +47,8 @@ public ServerSession(ILogger logger, IConnectionPoolMetadata pool)
4747 public int ActiveCommandId { get ; private set ; }
4848 public int CancellationTimeout { get ; private set ; }
4949 public int ConnectionId { get ; set ; }
50+ public string ? ServerUuid { get ; set ; }
51+ public long ? ServerId { get ; set ; }
5052 public byte [ ] ? AuthPluginData { get ; set ; }
5153 public long CreatedTimestamp { get ; }
5254 public ConnectionPool ? Pool { get ; }
@@ -117,6 +119,14 @@ public void DoCancel(ICancellableCommand commandToCancel, MySqlCommand killComma
117119 return ;
118120 }
119121
122+ // Verify server identity before executing KILL QUERY to prevent cancelling on the wrong server
123+ var killSession = killCommand . Connection ! . Session ;
124+ if ( ! VerifyServerIdentity ( killSession ) )
125+ {
126+ Log . IgnoringCancellationForDifferentServer ( m_logger , Id , killSession . Id , ServerUuid , killSession . ServerUuid , ServerId , killSession . ServerId ) ;
127+ return ;
128+ }
129+
120130 // NOTE: This command is executed while holding the lock to prevent race conditions during asynchronous cancellation.
121131 // For example, if the lock weren't held, the current command could finish and the other thread could set ActiveCommandId
122132 // to zero, then start executing a new command. By the time this "KILL QUERY" command reached the server, the wrong
@@ -137,6 +147,26 @@ public void AbortCancel(ICancellableCommand command)
137147 }
138148 }
139149
150+ private bool VerifyServerIdentity ( ServerSession otherSession )
151+ {
152+ // If server UUID is available, use it as the primary identifier (most unique)
153+ if ( ! string . IsNullOrEmpty ( ServerUuid ) && ! string . IsNullOrEmpty ( otherSession . ServerUuid ) )
154+ {
155+ return string . Equals ( ServerUuid , otherSession . ServerUuid , StringComparison . Ordinal ) ;
156+ }
157+
158+ // Fall back to server ID if UUID is not available
159+ if ( ServerId . HasValue && otherSession . ServerId . HasValue )
160+ {
161+ return ServerId . Value == otherSession . ServerId . Value ;
162+ }
163+
164+ // If no server identification is available, allow the operation to proceed
165+ // This maintains backward compatibility with older MySQL versions
166+ Log . NoServerIdentificationForVerification ( m_logger , Id , otherSession . Id ) ;
167+ return true ;
168+ }
169+
140170 public bool IsCancelingQuery => m_state == State . CancelingQuery ;
141171
142172 public async Task PrepareAsync ( IMySqlCommand command , IOBehavior ioBehavior , CancellationToken cancellationToken )
@@ -635,6 +665,9 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
635665 ConnectionId = newConnectionId ;
636666 }
637667
668+ // Get server identification for KILL QUERY verification
669+ await GetServerIdentificationAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
670+
638671 m_payloadHandler . ByteHandler . RemainingTimeout = Constants . InfiniteTimeout ;
639672 return redirectionUrl ;
640673 }
@@ -1951,6 +1984,90 @@ private async Task GetRealServerDetailsAsync(IOBehavior ioBehavior, Cancellation
19511984 }
19521985 }
19531986
1987+ private async Task GetServerIdentificationAsync ( IOBehavior ioBehavior , CancellationToken cancellationToken )
1988+ {
1989+ Log . GettingServerIdentification ( m_logger , Id ) ;
1990+ try
1991+ {
1992+ PayloadData payload ;
1993+
1994+ // Try to get both server_uuid and server_id if server supports server_uuid (MySQL 5.6+)
1995+ if ( ! ServerVersion . IsMariaDb && ServerVersion . Version >= ServerVersions . SupportsServerUuid )
1996+ {
1997+ payload = SupportsQueryAttributes ? s_selectServerIdWithAttributesPayload : s_selectServerIdNoAttributesPayload ;
1998+ await SendAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
1999+
2000+ // column count: 2
2001+ _ = await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
2002+
2003+ // @@server_uuid and @@server_id columns
2004+ _ = await ReceiveReplyAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
2005+ _ = await ReceiveReplyAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
2006+
2007+ if ( ! SupportsDeprecateEof )
2008+ {
2009+ payload = await ReceiveReplyAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
2010+ _ = EofPayload . Create ( payload . Span ) ;
2011+ }
2012+
2013+ // first (and only) row
2014+ payload = await ReceiveReplyAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
2015+
2016+ var reader = new ByteArrayReader ( payload . Span ) ;
2017+ var length = reader . ReadLengthEncodedIntegerOrNull ( ) ;
2018+ var serverUuid = length != - 1 ? Encoding . UTF8 . GetString ( reader . ReadByteString ( length ) ) : null ;
2019+ length = reader . ReadLengthEncodedIntegerOrNull ( ) ;
2020+ var serverId = ( length != - 1 && Utf8Parser . TryParse ( reader . ReadByteString ( length ) , out long id , out _ ) ) ? id : default ( long ? ) ;
2021+
2022+ ServerUuid = serverUuid ;
2023+ ServerId = serverId ;
2024+
2025+ Log . RetrievedServerIdentification ( m_logger , Id , serverUuid , serverId ) ;
2026+ }
2027+ else
2028+ {
2029+ // Fall back to just server_id for older versions or MariaDB
2030+ payload = SupportsQueryAttributes ? s_selectServerIdOnlyWithAttributesPayload : s_selectServerIdOnlyNoAttributesPayload ;
2031+ await SendAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
2032+
2033+ // column count: 1
2034+ _ = await ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
2035+
2036+ // @@server_id column
2037+ _ = await ReceiveReplyAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
2038+
2039+ if ( ! SupportsDeprecateEof )
2040+ {
2041+ payload = await ReceiveReplyAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
2042+ _ = EofPayload . Create ( payload . Span ) ;
2043+ }
2044+
2045+ // first (and only) row
2046+ payload = await ReceiveReplyAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
2047+
2048+ var reader = new ByteArrayReader ( payload . Span ) ;
2049+ var length = reader . ReadLengthEncodedIntegerOrNull ( ) ;
2050+ var serverId = ( length != - 1 && Utf8Parser . TryParse ( reader . ReadByteString ( length ) , out long id , out _ ) ) ? id : default ( long ? ) ;
2051+
2052+ ServerUuid = null ;
2053+ ServerId = serverId ;
2054+
2055+ Log . RetrievedServerIdentification ( m_logger , Id , null , serverId ) ;
2056+ }
2057+
2058+ // OK/EOF payload
2059+ payload = await ReceiveReplyAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
2060+ if ( OkPayload . IsOk ( payload . Span , this ) )
2061+ OkPayload . Verify ( payload . Span , this ) ;
2062+ else
2063+ EofPayload . Create ( payload . Span ) ;
2064+ }
2065+ catch ( MySqlException ex )
2066+ {
2067+ Log . FailedToGetServerIdentification ( m_logger , ex , Id ) ;
2068+ }
2069+ }
2070+
19542071 private void ShutdownSocket ( )
19552072 {
19562073 Log . ClosingStreamSocket ( m_logger , Id ) ;
@@ -2182,6 +2299,10 @@ protected override void OnStatementBegin(int index)
21822299 private static readonly PayloadData s_sleepWithAttributesPayload = QueryPayload . Create ( true , "SELECT SLEEP(0) INTO @__MySqlConnector__Sleep;"u8 ) ;
21832300 private static readonly PayloadData s_selectConnectionIdVersionNoAttributesPayload = QueryPayload . Create ( false , "SELECT CONNECTION_ID(), VERSION();"u8 ) ;
21842301 private static readonly PayloadData s_selectConnectionIdVersionWithAttributesPayload = QueryPayload . Create ( true , "SELECT CONNECTION_ID(), VERSION();"u8 ) ;
2302+ private static readonly PayloadData s_selectServerIdNoAttributesPayload = QueryPayload . Create ( false , "SELECT @@server_uuid, @@server_id;"u8 ) ;
2303+ private static readonly PayloadData s_selectServerIdWithAttributesPayload = QueryPayload . Create ( true , "SELECT @@server_uuid, @@server_id;"u8 ) ;
2304+ private static readonly PayloadData s_selectServerIdOnlyNoAttributesPayload = QueryPayload . Create ( false , "SELECT @@server_id;"u8 ) ;
2305+ private static readonly PayloadData s_selectServerIdOnlyWithAttributesPayload = QueryPayload . Create ( true , "SELECT @@server_id;"u8 ) ;
21852306
21862307 private readonly ILogger m_logger ;
21872308#if NET9_0_OR_GREATER
0 commit comments