From 9f35f059c769e1910e772c53def025728381af83 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 22 Jul 2022 22:02:58 +0200 Subject: [PATCH 1/2] deps: cherry-pick libuv/libuv@abb109f Original commit log follows: darwin: remove EPROTOTYPE error workaround (libuv/libuv#3405) It's been reported in the past that OS X 10.10, because of a race condition in the XNU kernel, sometimes returns a transient EPROTOTYPE error when trying to write to a socket. Libuv handles that by retrying the operation until it succeeds or fails with a different error. Recently it's been reported that current versions of the operating system formerly known as OS X fail permanently with EPROTOTYPE under certain conditions, resulting in an infinite loop. Because Apple isn't exactly forthcoming with bug fixes or even details, I'm opting to simply remove the workaround and have the error bubble up. Refs: https://github.com/libuv/libuv/pull/482 Fixes: https://github.com/nodejs/node/issues/43916 --- deps/uv/src/unix/stream.c | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/deps/uv/src/unix/stream.c b/deps/uv/src/unix/stream.c index 5858258d2868c3..c5cd6ddc0b627e 100644 --- a/deps/uv/src/unix/stream.c +++ b/deps/uv/src/unix/stream.c @@ -58,20 +58,6 @@ struct uv__stream_select_s { fd_set* swrite; size_t swrite_sz; }; - -/* Due to a possible kernel bug at least in OS X 10.10 "Yosemite", - * EPROTOTYPE can be returned while trying to write to a socket that is - * shutting down. If we retry the write, we should get the expected EPIPE - * instead. - */ -# define RETRY_ON_WRITE_ERROR(errno) (errno == EINTR || errno == EPROTOTYPE) -# define IS_TRANSIENT_WRITE_ERROR(errno, send_handle) \ - (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOBUFS || \ - (errno == EMSGSIZE && send_handle != NULL)) -#else -# define RETRY_ON_WRITE_ERROR(errno) (errno == EINTR) -# define IS_TRANSIENT_WRITE_ERROR(errno, send_handle) \ - (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOBUFS) #endif /* defined(__APPLE__) */ static void uv__stream_connect(uv_stream_t*); @@ -866,17 +852,17 @@ static int uv__try_write(uv_stream_t* stream, do n = sendmsg(uv__stream_fd(stream), &msg, 0); - while (n == -1 && RETRY_ON_WRITE_ERROR(errno)); + while (n == -1 && errno == EINTR); } else { do n = uv__writev(uv__stream_fd(stream), iov, iovcnt); - while (n == -1 && RETRY_ON_WRITE_ERROR(errno)); + while (n == -1 && errno == EINTR); } if (n >= 0) return n; - if (IS_TRANSIENT_WRITE_ERROR(errno, send_handle)) + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOBUFS) return UV_EAGAIN; return UV__ERR(errno); From 67d28ae03f76a7223741add30a9b879420ace970 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 22 Jul 2022 22:02:58 +0200 Subject: [PATCH 2/2] deps: cherry-pick libuv/libuv@3a7b955 Original commit log follows: darwin: translate EPROTOTYPE to ECONNRESET (libuv/libuv#3413) macOS versions 10.10 and 10.15 - and presumbaly 10.11 to 10.14, too - have a bug where a race condition causes the kernel to return EPROTOTYPE because the socket isn't fully constructed. It's probably the result of the peer closing the connection and that is why libuv translates it to ECONNRESET. Previously, libuv retried until the EPROTOTYPE error went away but some VPN software causes the same behavior except the error is permanent, not transient, turning the retry mechanism into an infinite loop. Refs: https://github.com/libuv/libuv/pull/482 Refs: https://github.com/libuv/libuv/pull/3405 Fixes: https://github.com/nodejs/node/issues/43916 --- deps/uv/src/unix/stream.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/deps/uv/src/unix/stream.c b/deps/uv/src/unix/stream.c index c5cd6ddc0b627e..b5b05a6a4a737f 100644 --- a/deps/uv/src/unix/stream.c +++ b/deps/uv/src/unix/stream.c @@ -865,6 +865,20 @@ static int uv__try_write(uv_stream_t* stream, if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOBUFS) return UV_EAGAIN; +#ifdef __APPLE__ + /* macOS versions 10.10 and 10.15 - and presumbaly 10.11 to 10.14, too - + * have a bug where a race condition causes the kernel to return EPROTOTYPE + * because the socket isn't fully constructed. It's probably the result of + * the peer closing the connection and that is why libuv translates it to + * ECONNRESET. Previously, libuv retried until the EPROTOTYPE error went + * away but some VPN software causes the same behavior except the error is + * permanent, not transient, turning the retry mechanism into an infinite + * loop. See https://github.com/libuv/libuv/pull/482. + */ + if (errno == EPROTOTYPE) + return UV_ECONNRESET; +#endif /* __APPLE__ */ + return UV__ERR(errno); }