Skip to content

Commit c457bb6

Browse files
bmehta001Copilot
andcommitted
Prevent SIGPIPE from killing the test process on peer reset
BasicFuncTests.teardownDuringInFlightUpload_ShutsDownCleanly intermittently killed the whole test runner on macOS/iOS CI: the process exited with signal SIGPIPE (exit 141) and no crash backtrace, which XCTest reports as an unexpected exit/restart and a ~24s timeout rather than a test failure. Cause: the test HTTP server writes responses from the reactor thread via ::send() with no SIGPIPE protection. That test deliberately cancels an upload that is still in flight against the /slow/ endpoint, so NSURLSession resets the connection while the server is mid-response. ::send() then fails with EPIPE and raises SIGPIPE; the test process installs no handler, so the default disposition terminates it. The race is timing-dependent, which is why it looks flaky and only shows up on the slower Apple CI runners. Fix (test infrastructure only, no SDK behavior change): - Add Socket::setNoSigPipe() and apply SO_NOSIGPIPE to every accepted connection (Apple/BSD, where the option is per-socket). - Pass MSG_NOSIGNAL from Socket::send() on Linux, which has no SO_NOSIGPIPE. Both make a write to a reset peer return EPIPE, which the reactor already handles by closing the connection. Files changed: tests/common/SocketTools.hpp Validated locally on macOS (arm64, Debug): the test reproduced at ~30% (5/15 runs exited 141) before the fix and passed 30/30 after; the full FuncTests suite passes 40/40. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent c3c1ce3 commit c457bb6

1 file changed

Lines changed: 41 additions & 1 deletion

File tree

‎tests/common/SocketTools.hpp‎

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,33 @@ class Socket
288288
return (::setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&value), sizeof(value)) == 0);
289289
}
290290

291+
/**
292+
* Suppress SIGPIPE when writing to a socket whose peer has already gone away.
293+
*
294+
* The test HTTP server writes responses on the reactor thread. When a client
295+
* (e.g. NSURLSession on Apple) cancels an in-flight upload during teardown, the
296+
* connection can be reset before the response is flushed, so ::send() fails with
297+
* EPIPE and raises SIGPIPE. The test process installs no SIGPIPE handler, so the
298+
* default disposition terminates it - which surfaces as a silent, backtrace-less
299+
* test-runner exit/restart rather than a normal test failure.
300+
*
301+
* Apple/BSD only supports this per-socket via SO_NOSIGPIPE; Linux uses the
302+
* MSG_NOSIGNAL send() flag instead (see send() below).
303+
*/
304+
bool setNoSigPipe()
305+
{
306+
#ifdef SO_NOSIGPIPE
307+
if (m_sock == Invalid)
308+
{
309+
return false;
310+
}
311+
int value = 1;
312+
return (::setsockopt(m_sock, SOL_SOCKET, SO_NOSIGPIPE, &value, sizeof(value)) == 0);
313+
#else
314+
return true;
315+
#endif
316+
}
317+
291318
bool setNoDelay()
292319
{
293320
assert(m_sock != Invalid);
@@ -326,7 +353,14 @@ class Socket
326353
int send(void const* buffer, unsigned size)
327354
{
328355
assert(m_sock != Invalid);
329-
return static_cast<int>(::send(m_sock, reinterpret_cast<char const*>(buffer), size, 0));
356+
#if defined(MSG_NOSIGNAL)
357+
// Linux: ask the kernel to return EPIPE instead of raising SIGPIPE.
358+
int flags = MSG_NOSIGNAL;
359+
#else
360+
// Apple/Windows: handled by SO_NOSIGPIPE / not applicable.
361+
int flags = 0;
362+
#endif
363+
return static_cast<int>(::send(m_sock, reinterpret_cast<char const*>(buffer), size, flags));
330364
}
331365

332366
bool bind(SocketAddr const& addr)
@@ -361,6 +395,12 @@ class Socket
361395
socklen_t addrlen = sizeof(caddr);
362396
#endif
363397
csock = ::accept(m_sock, caddr, &addrlen);
398+
if (!csock.invalid())
399+
{
400+
// Accepted connections are written to from the reactor thread; a peer
401+
// that resets mid-response must not kill the test process via SIGPIPE.
402+
csock.setNoSigPipe();
403+
}
364404
return !csock.invalid();
365405
}
366406

0 commit comments

Comments
 (0)