Repository navigation
Conversation
vaintroub
force-pushed
the
main-MDEV-38741
branch
2 times, most recently
from
October 30, 2026 02:49
c31cfc0 to
70e9cd4
Compare
The server notices that a client is gone only when it reads from or writes to the socket. While a statement runs, for example waiting for an InnoDB row lock, a disconnect of the client went unnoticed and the statement kept running and holding its locks. Watch the client socket from the end of login until the connection ends, or the client sends COM_QUIT, using only the events the OS reports on its own. When the client has gone, abort the running statement with the new KILL_DISCONNECT state, found by thread id like KILL does. An idle or quitting connection ignores it. KILL_DISCONNECT is statement-level, like KILL QUERY. When it does abort a statement, the connection ends after the error of that statement is sent, so the commands that the client pipelined behind it do not run. The reason, "Client disconnected", is logged once, and a failed write of the error is not reported. An orderly COM_QUIT, or a close without it, is handled as before. The global variable disconnect_watcher, ON by default, switches the watcher off. It affects the connections made after the change, and exists only where there is a watcher: Linux, Windows and the BSD systems, not the embedded server. Linux: an epoll set, polled by the connection accepting loop. TCP requests EPOLLRDHUP, as a close and a crashed client are only a FIN. A unix socket does not, it reports a close with EPOLLHUP. Windows sockets: WSAEventSelect(FD_CLOSE), with a wait per socket on a thread pool of our own that has a single thread, so the callbacks run one by one. Windows named pipes: the wait is for the exit of the local client process. A client that only closes the pipe is not noticed. BSD and macOS: kqueue, EV_EOF. Replication dump threads are watched too. mysqltest: dirty_close takes an optional "linger" (the default, a FIN) or "nolinger" (SO_LINGER 0, so the server gets a reset) argument. main.disconnect_lock_wait, disconnect_lock_wait_pipe (Windows) and sys_vars.disconnect_watcher_basic are new. The tests that depend on the watcher run with it ON and OFF. innodb.xa_recovery no longer needs the shortened lock wait timeout. rpl_ssl1 and rpl_password_boundaries wait for the dump thread of the slave to end before DROP USER, max_binlog_total_size and rpl_fail_register kill it only if it is still there. Assisted-By: Claude:claude-sonnet-5-5
vaintroub
force-pushed
the
main-MDEV-38741
branch
from
November 2, 2026 12:37
3423790 to
cbeff4c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Abort a running statement when its client disconnects. The server notices a gone client only when it reads from or writes to the socket, so a statement that waits for something else, like an InnoDB row lock, keeps running, and its transaction keeps owning its locks, while other connections wait for them.
Design
COM_QUIT(the watch is dropped when it is parsed, as the client closes its socket right after it, which is not a failure), using only events the OS reports on its own. The globaldisconnect_watchervariable (dynamic, ON by default, affects new connections) switches it off. It exists only where there is a watcher: Linux, Windows and the BSDs, not in the embedded server. The watcher finds the connection by thread id, likeKILLdoes, and touches theTHDonly throughawake_no_mutex().KILL_DISCONNECT, likeKILL QUERY. An idle or quitting connection ignores it, soCOM_QUIT, and a plain close, behave as before, with the usual "Aborted connection" line.ER_CONNECTION_KILLED), the reason "Client disconnected" is logged once, and the connection ends, so the commands that the client pipelined behind the statement do not run. The disconnect is kept inTHD::client_disconnecteduntil then, and a failed write of the error is not reported. If the kill aborts nothing, for example the statement had already finished, nothing happens.Platforms
EPOLLRDHUP, as a close and a crashed client are only a FIN. A unix socket does not, it reports a close withEPOLLHUP. The family is taken fromgetsockname(), asvio_type()hides the transport under TLSWSAEventSelect(FD_CLOSE)and a wait per socket, on a thread pool of our own with a single thread, so the callbacks run one by one like the epoll events. Polling with the AFD driver was tried and rejected: a pending poll makesselect(), which the vio layer waits with, lose the data notificationGetNamedPipeClientProcessId(), the client is impersonated if the process cannot be opened). Remote pipes are skippedEV_EOF. It passed on the aarch64-macos builder at an earlier revision of this PR, not tested locallyLimitations
ER_CONNECTION_KILLED), and its connection ended.Tests
main.disconnect_lock_wait(new): kills a waiting client over the default connection, over TCP closed with a FIN, and with a reset, and checks that the reason is logged once.main.disconnect_lock_wait_pipe(new, Windows): a real client is started on the pipe, waits for a lock, and is killed.sys_vars.disconnect_watcher_basic(new). The tests that depend on the watcher run with it ON and OFF (include/disconnect_watcher.combinations); with it OFF the lock wait is not aborted, so the test releases the lock instead (rdiff files).mysqltest:dirty_close <connection> [linger|nolinger].nolingerresets the TCP connection,linger, the default, is what it was.innodb.xa_recoveryno longer needs the shortened lock wait timeout.rpl_ssl1andrpl_password_boundarieswait for the dump thread of the slave to end,max_binlog_total_sizeandrpl_fail_registerkill it only if it is still there, as it may have ended already.The full
mainsuite was run on Windows and Linux (Debug). The only failures are in my environment: large pages on Windows, and on WSL a world-writable config file and a missing git revision.Cost
Connect and disconnect churn with libmariadb, 16 threads, 2000 connections per run, against the commit this PR is based on: no difference that is distinguishable from the noise (4 runs per cell). Plain TCP and AF_UNIX on Linux, and plain TCP on Windows, are within about 5%. With TLS, the handshake dominates (about 2000 conn/s on both systems) and the difference is within 2%. A watcher event still fires for about 12% of the connections on Linux and about a third on Windows, when the FIN arrives before the server has parsed
COM_QUIT. Waiting for the server's EOF before closing the socket in the client removes all of them, but it is not part of this PR.If the VIO refactoring in #5584 lands first,
vio_transport_type()andvio_handle()replace thegetsockname()family check and the directhPipeaccess.🤖 Generated with Claude Code