-
Notifications
You must be signed in to change notification settings - Fork 271
Improve proxy mount options #2286
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import ( | |
| "net/http" | ||
| "net/netip" | ||
| "os/exec" | ||
| "strings" | ||
| "sync" | ||
| "time" | ||
|
|
||
|
|
@@ -231,10 +232,25 @@ func (a *API) SetData(ctx context.Context, logger zerolog.Logger, data PostInitJ | |
| return nil | ||
| } | ||
|
|
||
| var nfsOptions = strings.Join([]string{ | ||
| // wait for data to be sent to proxy server before returning. | ||
| // async might cause issues if the sandbox is shut down suddenly. | ||
| "sync", | ||
|
|
||
| "rsize=1048576", // 1 MB read buffer | ||
| "wsize=1048576", // 1 MB write buffer | ||
| "mountproto=tcp", // nfs proxy only supports tcp | ||
| "mountport=2049", // nfs proxy only supports mounting on port 2049 | ||
| "proto=tcp", // nfs proxy only supports tcp | ||
| "port=2049", // nfs proxy only supports mounting on port 2049 | ||
| "nfsvers=3", // nfs proxy is nfs version 3 | ||
| "noacl", // no reason for acl in the sandbox | ||
| }, ",") | ||
|
|
||
| func (a *API) setupNfs(ctx context.Context, nfsTarget, path string) { | ||
| commands := [][]string{ | ||
| {"mkdir", "-p", path}, | ||
| {"mount", "-v", "-t", "nfs", "-o", "fg,hard,mountproto=tcp,mountport=2049,proto=tcp,port=2049,nfsvers=3,noacl", nfsTarget, path}, | ||
| {"mount", "-v", "-t", "nfs", "-o", "fg,hard," + nfsOptions, nfsTarget, path}, | ||
| } | ||
|
|
||
| for _, command := range commands { | ||
|
Comment on lines
+235
to
256
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 This PR introduces Extended reasoning...Bug: hard + sync NFS mount creates indefinite I/O hang riskWhat the bug is and how it manifests Before this PR, the mount command used fg,hard,mountproto=tcp,... with no explicit async/sync flag, defaulting to NFS async mode. With async, writes complete as soon as they are buffered in the client page cache; the kernel retries delivery to the server in the background. This PR introduces an explicit sync option, which changes write semantics entirely: every write() syscall now blocks until the NFS proxy sends an acknowledgement. Combined with the pre-existing hard mount option (which means the client retries indefinitely and never returns an error to the application), any process performing write I/O on the mount point will block indefinitely if the NFS proxy becomes unavailable. The specific code path In packages/envd/internal/api/init.go (lines 235-256), nfsOptions is constructed with "sync" as the first entry. The setupNfs function then passes "fg,hard," + nfsOptions to the mount command. The hard flag was present before this PR; the sync flag is new. Together they produce a mount with both hard and sync active. Why existing code does not prevent it There is no timeout, watchdog, or sandbox-level health check that would detect and recover from a process stuck in D state. The NFS hard option is explicitly designed to prevent the kernel from ever returning EIO or ETIMEDOUT to the application. Adding sync on top means the kernel cannot complete the write optimistically by buffering; it must wait for the server. There is no timeo, retrans, or intr option to bound the wait. Impact If the NFS proxy crashes, is restarted, or a network partition occurs after a volume is mounted — all plausible in a sandbox environment — every sandbox process that subsequently attempts a write will enter uninterruptible sleep (D state). These processes cannot be killed with SIGKILL. The only recovery is a full VM termination. This turns a transient infrastructure hiccup into a permanent sandbox freeze. Step-by-step proof of the hang
How to fix it Replace hard with soft and add explicit timeo and retrans values: e.g., sync,soft,timeo=100,retrans=3,rsize=1048576,wsize=1048576,.... With soft, after retrans failed retries the kernel returns EIO to the application instead of retrying forever. sync can be kept to preserve the data-durability intent. The soft + timeo + retrans combination is the most portable and operationally safe choice. |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NFS sync option not removed despite PR discussion agreement
Medium Severity
The
syncNFS mount option appears to still be present despite the author agreeing in the PR discussion to remove it ("Despite the performance gains, I'll remove that one"). Thesyncoption forces synchronous writes, which can significantly degrade NFS throughput (2-3x slower than the defaultasync), partially negating the performance gains from thersize/wsizebuffer increases. This contradicts the PR's stated goal of improving performance by 5-10%.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol.
data corruption overrules speed, we're done here.