cloudflare / tableflip

Graceful process restarts in Go
BSD 3-Clause "New" or "Revised" License
2.88k stars 147 forks source link

ensure that inherited files are nonblocking #60

Closed lmb closed 3 years ago

lmb commented 3 years ago

The Go runtime has annoying behaviour around setting and clearing O_NONBLOCK: exec.Cmd.Start() ends up calling os.File.Fd() for any file in exec.Cmd.ExtraFiles. os.File.Fd() disables both the use of the runtime poller for the file and clears O_NONBLOCK from the underlying open file descriptor.

Disabling the runtime poller is bad because it makes operating on those files less efficient. We can work around this by always working on duplicate os.File. Since the library itself never reads or writes from the files it doesn't matter whether the runtime poller is enabled or not.

Clearing O_NONBLOCK is much more annoying, since it's shared by all fds pointing to the same open file descriptor. This can lead to hard to diagnose hangs in code invoking syscalls on the file descriptors, like read or accept. Even more troubling, File.Close doesn't interrupt these syscalls anymore. Depending on how the application is structured this can lead to indefinite hangs.

Fix this by writing a simplified wrapper around syscall.StartProcess. It turns out that we don't need most features of exec.Cmd anyways.

A full restart is needed to make files non-blocking again in deployed processes.