Wait out socket backpressure when streaming fds across a clone - #297
Closed
doanbaotrung wants to merge 1 commit into
Closed
Wait out socket backpressure when streaming fds across a clone#297doanbaotrung wants to merge 1 commit into
doanbaotrung wants to merge 1 commit into
Conversation
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.
There was a problem hiding this comment.
1 issue found across 1 file
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/runtime/forkipc.c">
<violation number="1" location="src/runtime/forkipc.c:1554">
P1: If the parent endpoint's `setsockopt` fails, `sys_clone` continues with an unprotected `ipc_sock`; a child that exits during the handshake can then raise the host's default SIGPIPE and terminate elfuse instead of returning a clone error. Restore fail-closed handling for either option, or otherwise guarantee SIGPIPE suppression before sending.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Comment on lines
+1554
to
+1557
| setsockopt(sock_fds[0], SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, | ||
| sizeof(nosigpipe)); | ||
| setsockopt(sock_fds[1], SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, | ||
| sizeof(nosigpipe)); |
There was a problem hiding this comment.
P1: If the parent endpoint's setsockopt fails, sys_clone continues with an unprotected ipc_sock; a child that exits during the handshake can then raise the host's default SIGPIPE and terminate elfuse instead of returning a clone error. Restore fail-closed handling for either option, or otherwise guarantee SIGPIPE suppression before sending.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/runtime/forkipc.c, line 1554:
<comment>If the parent endpoint's `setsockopt` fails, `sys_clone` continues with an unprotected `ipc_sock`; a child that exits during the handshake can then raise the host's default SIGPIPE and terminate elfuse instead of returning a clone error. Restore fail-closed handling for either option, or otherwise guarantee SIGPIPE suppression before sending.</comment>
<file context>
@@ -1549,21 +1549,12 @@ int64_t sys_clone(hv_vcpu_t vcpu,
- 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,
</file context>
Suggested change
| setsockopt(sock_fds[0], SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, | |
| sizeof(nosigpipe)); | |
| setsockopt(sock_fds[1], SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, | |
| sizeof(nosigpipe)); | |
| 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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
Summary by cubic
Waits out socket backpressure when streaming SCM_RIGHTS fds during clone, preventing EMSGSIZE failures on macOS. Treats EMSGSIZE from a fixed-size chunk as backpressure: waits for POLLOUT and retries the same chunk. Also makes
SO_NOSIGPIPEbest-effort so the clone does not fail if the option cannot be applied.fork_ipc_write_allsemantics. Child death still surfaces as POLLHUP.fork_ipc_send_fdsretry path on EMSGSIZE, and the removal of hard failure onsetsockopt(SO_NOSIGPIPE); option is applied on both ends when available but does not gate clone success.Written for commit 51c07dc. Summary will update on new commits.