Skip to content

Sockets: add the Qt ServerSocket and the loopback test harness - #1787

Merged
Mysticial merged 6 commits into
mainfrom
sockets-server-loopback
Nov 1, 2026
Merged

Mysticial merged 6 commits into
mainfrom
sockets-server-loopback

Conversation

@pa-devbot

@pa-devbot pa-devbot commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Adds the Qt ServerSocket backend plus the loopback test harness.

This is the minimal first piece of the server-socket work, split out so
the backends can be reviewed/merged independently (see the two follow-on
PRs that are based on this one).

What's here

  • ServerSocket_Qt: a QTcpServer on the Qt event thread pool,
    mirroring ClientSocket_Qt. One connection at a time; extra clients
    are rejected via on_connect_rejected.
  • Sockets_Tests.cpp/.h: loopback tests that combine a ClientSocket
    and a ServerSocket on 127.0.0.1. The test logic is written against
    the abstract classes and instantiated per backend; this PR enables the
    Qt backend on all platforms.
  • Documents that ServerSocket::close_connection() is safe to call from
    a listener callback (close() is not, it joins the worker threads).

Scenarios (each as its own test): echo round trip, zero-length send,
1 MiB transfer with integrity check, rapid reconnects, send/close race,
reject a second client, port-in-use start failure.

Not in this PR (land in the PRs based on it):

  • POSIX backend + its test instantiation
  • WinSocket backend + its test instantiation

Result: every platform runs Qt here, and once the follow-ons land,
every platform runs 2 of the 3 backends with CI covering all 3.

Note (rebuild on current main): the implementation now lives in ServerSocket_Qt.cpp, mirroring the ClientSocket_Qt header/.cpp split (#1795).

Update (AGENTS.md + formatting):

  • Migrated off std::mutex / std::condition_variable to the
    PokemonAutomation::Mutex / ConditionVariable wrappers
    (Common/Cpp/Concurrency/) in ServerSocket_Qt.h and the test
    harness's Waiter/port-rng lock. ClientSocket_Qt.h (already
    merged) had the same one-member violation in its SendData
    struct; that follow-up migration is included here too.
  • Fixed ServerSocket_Qt.cpp indentation left over from the
    header/.cpp split (method bodies had kept their in-class 8-space
    indent).

Update (completion lifetime): the send() / close_connection()
5 s waits are now safe on expiry. The completions were stack-local,
so if the wait timed out and the event thread delivered the queued
call afterwards, the handler wrote through a dead stack pointer
(stack-use-after-return — reproduced under ASAN by stalling the
event thread in on_receive_data). The signals now pass
QSharedPointer<SendData> / QSharedPointer<CloseCompletion> by
value: the queued call's copy keeps the payload alive for as long as
the call is pending or running, and if close() destroys the
QTcpServer first, Qt discards the queued call and the caller's
copy is the last reference (no UAF, no leak). Added a regression
test, "Sockets (Qt) - Completion lifetime under event-thread stall"
(~12 s): it stalls the event thread in an on_receive_data callback
for 6 s, checks close_connection() times out at ~5 s instead of
hanging, and that the late handler still restores LISTENING.

@pa-devbot
pa-devbot force-pushed the sockets-server-loopback branch 2 times, most recently from 936fa57 to 8f44f29 Compare October 31, 2026 22:58
@pa-devbot pa-devbot changed the title Sockets: add ServerSocket (Qt + POSIX) with loopback tests Sockets: add the Qt ServerSocket and the loopback test harness Oct 31, 2026
@pa-devbot
pa-devbot force-pushed the sockets-server-loopback branch from 82d5b0f to 544b9fb Compare November 1, 2026 02:44
Implements ServerSocket_Qt (a QTcpServer on the Qt event thread pool,
mirroring ClientSocket_Qt; the implementation lives in
ServerSocket_Qt.cpp, following the split of the client backends) and
adds loopback tests that combine a ClientSocket and a ServerSocket
against each other on 127.0.0.1.

The test logic is written against the abstract
AbstractClientSocket/AbstractServerSocket classes and is instantiated
per backend; this PR instantiates the Qt backend on all platforms. The
POSIX and WinSocket backends (and their test instantiations) land in
separate PRs on top of this one, so that every platform runs 2 of the
3 backends and CI as a whole covers all 3.

Scenarios: echo round trip, zero-length sends, a 1 MiB transfer with
integrity check, rapid reconnect cycles, a send/close race, rejection
of a second client, and a port-in-use start failure.

Also documents that ServerSocket::close_connection() is safe to call
from within a listener callback (close() is not: it joins the worker
threads).
AGENTS.md forbids using std::mutex/std::condition_variable directly;
use the PokemonAutomation::Mutex/ConditionVariable wrappers from
Common/Cpp/Concurrency instead. Migrate the Qt server's completion
structs and the test harness's Waiter/rng lock.
The method bodies kept their in-class 8-space indentation from the
monolithic header; un-dedent them to file scope (and drop the stray
blank lines the split left behind).
The 5 s completion waits in send()/close_connection() were only safe
while the queued call ran within the window: the completion objects
were stack-local, so if the wait expired and the event thread
delivered the call later, on_send/on_close_connection wrote through
a dead stack pointer (stack-use-after-return; reproduced under
ASAN by stalling the event thread in on_receive_data for 6 s).

The signals now pass QSharedPointer<SendData>/QSharedPointer<CloseCompletion>
by value: the queued call's own copy keeps the payload alive for as
long as the call is pending or running (no UAF after a timed-out
caller), and if close() destroys the QTcpServer first, Qt discards
the queued call and the caller's copy is the last reference (no
leak). The 5 s bounds remain as plain safety valves.

Also add the regression test 'Sockets (Qt) - Completion lifetime
under event-thread stall', which stalls the event thread inside
on_receive_data, verifies close_connection() times out at ~5 s
instead of hanging, and that the late handler still restores
LISTENING. Takes ~12 s to run.
@Mysticial
Mysticial merged commit 2d81dee into main Nov 1, 2026
7 checks passed
@Mysticial
Mysticial deleted the sockets-server-loopback branch November 1, 2026 09:35
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