Skip to content

Let ClientImpl::stop() interrupt a request waiting on Windows - #2653

Merged
yhirose merged 1 commit into
yhirose:masterfrom
yosh-matsuda:client-stop-interrupt
Oct 30, 2026
Merged

yhirose merged 1 commit into
yhirose:masterfrom
yosh-matsuda:client-stop-interrupt

Conversation

@yosh-matsuda

Copy link
Copy Markdown
Contributor

Related to #1396. That issue is closed, but a comment of 2026-09-01 reports that Client::stop() still does not end a request on Windows with the latest version.

Problem

stop() ends a request in flight with shutdown() on its socket. On Linux and macOS this wakes the poll() that waits for the response. On Windows, shutdown() does not wake a WSAPoll() on the same socket. The request then waits until the server answers, the read timeout passes (300 s by default), or the OS drops the connection.

801bf2e fixed the same problem for SSEClient::stop(). This PR uses the same mechanism for ClientImpl::stop().

Change

  • stop() also sets the atomic flag stop_requested_ when a request is in flight.
  • send_() gives the stream of the request an interrupt check that reads the flag, with set_read_interrupt() from 801bf2e. A wait for data then ends within 100 ms on every platform.
  • The flag is cleared when a request starts while no other request is in flight. A redirect or a digest retry that is sent inside a request keeps the flag.
  • shutdown_socket() stays, so Linux and macOS still wake at once.
  • select_read_interruptible() now rounds its last wait up to a whole millisecond. poll() takes whole milliseconds, and the old code dropped the rest, so the wait could end up to 1 ms before the deadline. The SSE path had the same error. When every client request waits through this function, MaxTimeoutTest.ContentStream and MaxTimeoutTest.ContentStreamSSL fail with "Timeout exceeded by -1ms" on the Mbed TLS, wolfSSL, and BoringSSL jobs.

stop() sets the flag under socket_mutex_, and send_() counts the request as in flight under the same lock. Thus a stop() that comes after that point, but before the request starts to wait, is not lost. A stop() before that point closes the idle socket, as before.

Notes:

  • Each wait for data of a client request now wakes every 100 ms to read the flag. SSEClient uses the same slice.
  • Only the wait for data is interrupted. A wait to write does not change.
  • ClientImpl gets one new member, so abidiff reports an ABI change against v0.64.0.

Test

ClientStopTest.StopEndsWaitForResponse sends a request to a handler that does not answer. Another thread calls stop() once, and the test expects the request to end with Error::Read within 2 s.

  • The test alone, without the fix: "windows without SSL" and "windows with SSL" fail.
  • The fix without the rounding: the Windows jobs pass, but the Mbed TLS, wolfSSL, and BoringSSL jobs fail on MaxTimeoutTest.
  • Linux, local, clang 22 with ASan: the test passes in approximately 0.1 s, 30 of 30 runs. With shutdown_socket() removed from stop() to imitate Windows, the test fails without the fix and passes in approximately 0.2 s with the fix.

stop() shuts the socket down to end a request in flight, but on Windows
that does not wake a read waiting in WSAPoll (yhirose#1396). The request then
waited until the server answered, the read timeout passed, or the OS
dropped the connection.

stop() now also sets a flag, and send_() gives the request's stream an
interrupt check that reads it, as SSEClient::stop() does since 801bf2e.
The wait for data then ends within 100 ms on every platform. A request
started after stop() clears the flag. A redirect or a digest retry sent
inside the request keeps it.

select_read_interruptible() now rounds its last wait up to a whole
millisecond. poll() takes milliseconds, and the wait ended up to 1 ms
before the deadline, which MaxTimeoutTest caught once every client
request waited through this function.
@yhirose
yhirose merged commit a18895f into yhirose:master Oct 30, 2026
27 of 28 checks passed
@yhirose

yhirose commented Oct 30, 2026

Copy link
Copy Markdown
Owner

@yosh-matsuda thanks for the thorough investigation and fix!

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