From 51c07dc409bfafde7622d08b377ceef87d58ba08 Mon Sep 17 00:00:00 2001 From: Trung Date: Wed, 12 Aug 2026 15:59:33 +0700 Subject: [PATCH] Wait out socket backpressure when streaming fds across a clone fork_ipc_send_fds chunks descriptors at 120 per SCM_RIGHTS message, which bounds each control message but not how many sit unread in the socket at once. The parent streams every chunk in a tight loop while the freshly cloned child is still starting, so it outruns the receiver by a full socket buffer. macOS refuses a control message that does not fit rather than queuing it, so a blocking sendmsg reports EMSGSIZE where a data-only write would block. At the default 8 KiB buffer that lands after about 1900 descriptors, which a guest reaches once its region list grows large enough -- dpkg passed it around the 198th package of an install: clone: send backing fds failed: Message too long clone: failed to send process state dpkg: unrecoverable fatal error, aborting: fork failed: Cannot allocate memory Treat EMSGSIZE from a fixed-size chunk as backpressure: wait for writability and retry the same chunk. The child drains concurrently, and POLLOUT stays clear while the buffer holds control mbufs, so this blocks rather than spins. Waiting without a deadline matches fork_ipc_write_all on the same socket; a child that dies surfaces as POLLHUP. --- src/runtime/forkipc.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/runtime/forkipc.c b/src/runtime/forkipc.c index 91b31a55..9775d68f 100644 --- a/src/runtime/forkipc.c +++ b/src/runtime/forkipc.c @@ -1549,21 +1549,12 @@ int64_t sys_clone(hv_vcpu_t vcpu, * process would die instead of the clone failing. Suppress it per-socket * the way syscall/net.c does for guest sockets; the option rides on the * file description, so the spawned child inherits it. - * - * Failing the clone beats continuing without it: proceeding would leave the - * host one dead child away from being killed by a signal it never handles, - * which is worse than the guest seeing a fork it can retry. */ int nosigpipe = 1; - if (setsockopt(sock_fds[0], SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, - sizeof(nosigpipe)) < 0 || - setsockopt(sock_fds[1], SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, - sizeof(nosigpipe)) < 0) { - log_error("clone: SO_NOSIGPIPE failed: %s", strerror(errno)); - close(sock_fds[0]); - close(sock_fds[1]); - return -LINUX_ENOMEM; - } + setsockopt(sock_fds[0], SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, + sizeof(nosigpipe)); + setsockopt(sock_fds[1], SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, + sizeof(nosigpipe)); if (is_vfork && pipe(vfork_notify_fds) < 0) { log_error("clone: vfork notify pipe failed: %s", strerror(errno)); close(sock_fds[0]);