Repository navigation
Sockets: make the Qt client's close() cancel in-flight sends - #1831
Conversation
close() now cancels the in-flight send() calls instead of waiting for them to finish: it marks them done so their callers return promptly with the number of bytes actually written so far (best effort), then destroys the QTcpSocket as before. 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. - close() and connect() (which destroys the previous socket) claim the list and mark every entry done + cancelled, waking the send() callers. The queued write loops see the flag and finish without writing (or update the running byte count as they go, so a close mid-write reports the bytes written so far). - 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 can be unbounded. Finite timeouts keep their "timeout + 1 s" queueing margin. - The write loop reports its running byte count, so cancellation reports a best-effort count instead of always 0. Also adds a test: with the client's event thread blocked for 6 s in on_receive_data, a send started on the test thread queues behind the stall, and a close() 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).
|
I think it's a race in {
std::lock_guard<Mutex> lg(m_lock);
socket = m_socket; // (1) non-null, CONNECTED
}
...
send_data->packet.assign((const char*)data, bytes); // (2) 256 MiB copy, slow under load
...
{
std::lock_guard<Mutex> lg(m_lock);
m_inflight_sends.push_back(send_data); // (3)
}
emit internal_send(send_data); // (4)
// then waits on send_data->cv with no time bound (default timeout)If A possible fix: do the check and the registration under one lock, after the copy, so {
std::lock_guard<Mutex> lg(m_lock);
if (m_socket == nullptr){
return 0; // closed while we were copying
}
m_inflight_sends.push_back(send_data);
}( For a deterministic repro, a sleep between (2) and (3) longer than the test's 100 ms head start should make it hang every time. Not checked: whether the GUI |
Task #3 from the close()-cancel plan: proper refactor of the Qt client so
close()cancels in-flight sends, per the contract specified in #1830.Behavior
close()no longer waits for in-flightsend()calls to finish. It cancels them: their callers return promptly with the number of bytes actually written so far (best effort), and only then is theQTcpSocketdestroyed (that destruction still has to run on the event thread, soclose()itself blocks until the socket is destroyed — same as before).send()no longer carries the 5 s safety bound:doneis now guaranteed to be set by either the event-thread write loop (deadline / socket error / Qt's userspace acceptance) or the cancel inclose()/connect(), so the wait is always ended. Finite timeouts keep theirtimeout + 1 squeueing margin.How it works
SendDataobjects are tracked in a list underm_lock(replaces the old in-flight counter).send()adds its entry before emitting the queued write.close()andconnect()(which destroys the previous socket) claim the list and mark every entrydone+cancelled, waking thesend()callers. The queued write loops see the flag and finish without writing.QSharedPointerkeeps each packet alive until its queued call runs (or is discarded with the socket); 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 client's event thread blocked for 6 s in
on_receive_data, asend()started on the test thread queues behind the stall, and aclose()from a worker thread must wake the send within a few milliseconds of starting (it asserts the lag betweenclose()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.Full local suite green (20,330 / 0 / 3). No changes to the POSIX/Win backends or to the server (that's #4).