Skip to content

Sockets: make the Qt server's close() and close_connection() cancel in-flight sends - #1832

Merged
Mysticial merged 1 commit into
mainfrom
qt-server-close-cancel
Nov 4, 2026
Merged

Mysticial merged 1 commit into
mainfrom
qt-server-close-cancel

Conversation

@pa-devbot

Copy link
Copy Markdown
Collaborator

Task #4 from the close()-cancel plan: proper refactor of the Qt server so close() and close_connection() cancel in-flight sends, per the contract specified in #1830.

Behavior

  • close() and close_connection() no longer wait for in-flight send() calls to finish. They cancel them: their callers return promptly with the number of bytes actually written so far (best effort). close() then destroys the QTcpServer on the event thread as before (that destruction is inherent to the object's lifetime, so close() still blocks until it happens).
  • close_connection() keeps its 5 s bound on the queued close itself: that bound is about reaching the event thread for state consistency (pinned by the completion-lifetime test), not about the sends.
  • The infinite-default send() no longer carries the 5 s safety bound: done is now guaranteed to be set by either the event-thread write loop (deadline / socket error / Qt's userspace acceptance) or the cancel, so the wait is always ended. Finite timeouts keep their timeout + 1 s queueing margin.
  • The write loop now reports a running byte count, so a close mid-write reports the bytes written so far instead of always 0.

How it works

  • In-flight SendData objects are tracked in a list under m_lock (replaces the old in-flight counter). send() adds its entry before emitting the queued write.
  • close() and close_connection() claim the list and mark every entry done + cancelled, waking the send() callers. The queued write loops see the flag and finish without writing.
  • Lifetime is unchanged: the shared QSharedPointer keeps each packet alive until its queued call runs (or is discarded with the server); the event loop is FIFO, so a queued write still runs before the destruction and just no-ops.

Test

New Qt-only test: with the server's event thread blocked for 6 s on the first received data (a 256 KB trigger from a client), a send() started on the test thread queues behind the stall, and a close_connection() from a worker thread must wake the send within a few milliseconds of starting (it asserts the lag between close_connection() starting and the send returning). Without the cancel, the send would only wake when the 6 s stall ends and the queued write finally runs. The test then polls for the resulting LISTENING state within the shared 60 s budget, since close_connection() may return with the close still queued behind the stall (its 5 s bound).

Note for the merge: this touches Sockets_Tests.cpp in the same region as #1831 (the test registrations are adjacent), so it may need a rebase onto main if #1831 merges first. The production-code changes are disjoint from #1831.

Full local suite green (20,330 / 0 / 3). No changes to the POSIX/Win backends.

…n-flight sends

close() and close_connection() now cancel the in-flight send() calls
instead of waiting for them to finish: their callers return promptly
with the number of bytes actually written so far (best effort), and
only then does close() destroy the QTcpServer (that destruction still
has to run on the event thread, so close() itself blocks until the
server is destroyed - same as before). close_connection() keeps its
5 s bound on the queued close itself: that bound is about reaching the
event thread for state consistency, not about the sends.

How it works:

- In-flight SendData objects are tracked in a list under m_lock
  (replacing the old send-in-flight counter). send() adds its entry
  before emitting the queued write, re-checking under the same lock
  that the connection is still attached: the packet copy can take a
  while for large payloads, and without the re-check a send could
  register after close() already cancelled the (empty) list and queued
  the server's destruction - its queued write would then be posted
  after the destruction event, Qt would silently discard it, and the
  infinite-timeout wait would never end. With the re-check, a send
  either registers before the cancel (and is cancelled, waking the
  wait) or sees the detached socket and returns 0 without queueing
  anything.
- close() and close_connection() claim the list and mark every entry
  done + cancelled, waking the send() callers. The queued write loops
  see the flag and finish without writing.
- The infinite default no longer carries the 5 s safety bound:
  "done" is now guaranteed to be set by either the event-thread write
  loop (deadline / socket error / Qt's userspace acceptance) or the
  cancel, so the wait is always ended. Finite timeouts keep their
  "timeout + 1 s" queueing margin.
- The write loop now reports a running byte count, so a close mid-write
  reports the bytes written so far instead of always 0.

Also adds a test: with the server's event thread blocked for 6 s on the
first received data, a send() started on the test thread queues behind
the stall, and a close_connection() from a worker thread must wake the
send within a few milliseconds of starting. Without the cancel, the
send would only wake when the stall ends and the queued write finally
runs.
@pa-devbot
pa-devbot force-pushed the qt-server-close-cancel branch from 10eaada to 1bdff22 Compare November 4, 2026 04:01
@Mysticial
Mysticial merged commit e2ae301 into main Nov 4, 2026
7 checks passed
@Mysticial
Mysticial deleted the qt-server-close-cancel branch November 4, 2026 09:02
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