Skip to content

ClientSocket_Qt: give signal completions shared ownership - #1822

Merged
Mysticial merged 1 commit into
mainfrom
client-socket-qt-shared-completion
Nov 1, 2026
Merged

Mysticial merged 1 commit into
mainfrom
client-socket-qt-shared-completion

Conversation

@pa-devbot

Copy link
Copy Markdown
Collaborator

Follow-up to the Mac Intel CI crash investigation (#1811, #1812, #1817, #1819).

The issue. ClientSocket_Qt::send() kept the completion object stack-local and passed a raw pointer through a queued Qt signal:

SendData send_data;                       // stack local
emit internal_send(&send_data);           // queued to the socket's event thread
send_data.cv.wait_for(lg, 5s, ...);       // the only thing keeping it alive

If the event thread delivers the queued call after the 5 s wait expires, the slot runs sock->write(...), then locks send_data->lock, writes send_data->bytes_sent/send_data->done, and calls send_data->cv.notify_all() — all through a dead stack pointer on the calling thread's stack (stack-use-after-return). The corrupted bytes then explode later where that stack region is reused.

This is the same issue that ServerSocket_Qt had and already fixed (shared-ownership completions, see the QSharedPointer<SendData>/QSharedPointer<CloseCompletion> in ServerSocket_Qt.cpp and its commit history): the server's send()/close_connection() wait on the same 5 s pattern, but their payloads are heap-owned. ClientSocket_Qt::send() was the one remaining raw-pointer crossing in the Qt socket backends.

The fix. The signal now passes QSharedPointer<SendData> by value; the queued call's own copy keeps the payload alive until it runs (or is discarded with the socket). The 5 s wait stays — it still bounds the call when close() destroys the socket before the queued event runs, and with shared ownership the expiry is now safe.

Context. The Mac Intel runner has been crashing intermittently in Test_Sockets_RapidReconnect<ClientSocket_Qt, ServerSocket_Qt> (backtraces on #1812/#1819 show the fault in a Waiter::wait_for stack frame in that test). This closes the one known way for the test's socket plumbing to corrupt a pool thread's stack. Whether it accounts for every crash will show in CI: this test hammers send() 50× per run, and the crash has been hitting it most reliably.

The 5 s completion wait in send() was only safe while the queued call
ran within the window: the completion object was stack-local, so if the
wait expired and the event thread delivered the queued internal_send()
later, the slot wrote through a dead stack pointer
(stack-use-after-return) - the same issue ServerSocket_Qt had, fixed
the same way in the ServerSocket_Qt work.

The signal now passes QSharedPointer<SendData> by value: the queued
call's own copy keeps the payload alive until it runs (or is discarded
with the socket), so the wait's expiry is safe.
@Mysticial
Mysticial merged commit ddeb0f1 into main Nov 1, 2026
6 of 7 checks passed
@Mysticial
Mysticial deleted the client-socket-qt-shared-completion branch November 1, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants