Skip to content

proposal: os: add O_NONBLOCK #73676

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

Open
qmuntal opened this issue May 12, 2025 · 10 comments
Open

proposal: os: add O_NONBLOCK #73676

qmuntal opened this issue May 12, 2025 · 10 comments

Comments

@qmuntal
Copy link
Member

qmuntal commented May 12, 2025

Proposal Details

Passing syscall.O_NONBLOCK (only defined on Unix OSes) to os.Open opens the file descriptor in nonblocking mode. This flag is mostly useful when working with named pipes, as they are normally used for asynchronous inter-process communication. There are 680 hits of syscall.O_NONBLOCK on GitHub (prompt).

It is currently not possible to use the os package to open a named pipe for non-blocking I/O (aka overlapped I/O) on Windows, as the syscall package doesn't define O_NONBLOCK on that port.

The os package will start supporting overlapped I/O starting in Go 1.25 (see CL 661795 and #19098), yet there will be no way to open a file for overlapped I/O without using the x/sys/windows (or syscall) packages. Note that, on Windows, it is not possible to change a file descriptor to be non-blocking once it has been created, that property must be specified up-front.

I propose to promote syscall.O_NONBLOCK to the os package and map it to FILE_FLAG_OVERLAPPED on Windows. We could also just define syscall.O_NONBLOCK for Windows, but the syscall API is frozen and IMO that flag makes sense to be in the os package, as it will now be portable across most of the supported OSes (except on plan9 and wasi?).

@golang/windows @golang/proposal-review

@adonovan
Copy link
Member

Would it be possible to achieve the same goal by implementing the existing syscall.SetNonblock(fd Handle, blocking bool) function, which is currently a no-op? That wouldn't require a proposal.

You might want to implement this proposal nonetheless, but SetNonblock may be a way to make immediate progress.

@qmuntal
Copy link
Member Author

qmuntal commented May 12, 2025

Would it be possible to achieve the same goal by implementing the existing syscall.SetNonblock(fd Handle, blocking bool) function, which is currently a no-op? That wouldn't require a proposal.

Unfortunately that function can't be implemented on Windows. One can only set the blocking mode when creating the file descriptor (aka handle). There is no reliable way to change it later on.

@adonovan adonovan moved this to Incoming in Proposals May 12, 2025
@qmuntal
Copy link
Member Author

qmuntal commented May 12, 2025

@neild this new flag would also affect the os.Root.OpenFile flag, as it accepts the same paramters as os.CreateFile.

@neild
Copy link
Contributor

neild commented May 12, 2025

What would the user-visible effect of setting O_NONBLOCK be? Are there subtly different behaviors on Unix and Windows systems? How would we document the flag?

@qmuntal
Copy link
Member Author

qmuntal commented May 13, 2025

What would the user-visible effect of setting O_NONBLOCK be?

On Windows, the main effect is that I/O will not block and will be cancelable, either for named pipes and normal files. On Linux, that will make os.OpenFile (and friends) not to wait for the other side of the named pipe to connect and will also make named pipes use non-blocking cancelable I/O without having to call syscall.SetNonblock, although this last thing is already happening when using the os package.

Are there subtly different behaviors on Unix and Windows systems?

I can think on several differences:

  • On Unix, one can change the blocking mode after the file descriptor has been created, while that's not possible on Windows. This makes passing O_NONBLOCK to os.OpenFile somehow a more relevant action on Windows, as that can't be undone. Some code down the line might not be prepared to handle overlapped I/O (for example because it is using direct syscalls), so it won't be able to read or write the file.
  • On Unix, when opening a named pipe with O_NONBLOCK, the open syscall doesn't block waiting for the other end to connect. On Windows, that's the behavior you always get with CreateFileW, you can't instruct it to wait for the other end of the pipe (you have to use ConnectNamedPipe to achieve that). So using O_NONBLOCK would make the code more portable.
  • The types of files that support nonblocking I/O might be different depending on the OS. For example, Windows supports overlapped I/O for disk files, pipes and sockets, but not for consoles. On Linux, nonblocking I/O is mostly targeted for pipes and special devices.

How would we document the flag?

I don't think the documentation of the flag should go into that much detail, as this features are well documented online. Here is my proposal (syscall.O_NONBLOCK should be replaced with an unexported value on Windows):

package os

const O_NONBLOCK = syscall.O_NONBLOCK // open for nonblocking I/O in Unix and for overlapped I/O on Windows.

@apparentlymart
Copy link

Is it currently true that including O_NONBLOCK on Unix can cause operations to fail with the error syscall.EAGAIN/syscall.EWOULDBLOCK if the operation cannot complete immediately? (That's the behavior I'd expect from the underlying system calls, but I've not personally tried this when working through the os package APIs.)

If so, would you expect similar situations on Windows to emulate that Unix-like behavior -- returning exactly the same error code -- or would someone writing portable code need to write defensive error handling to properly handle this situation?

Is it justified to add a higher-level error value (similar to fs.ErrNotExist) that is guaranteed to be usable with errors.Is to recognize the "would block" situation across all platforms, without having to import anything from syscall?

@neild
Copy link
Contributor

neild commented May 13, 2025

I'm afraid I still don't really understand when you want to set O_NONBLOCK in a Go program. (I know it's useful, I just don't understand when.)

Are there programs that run on Unix and Windows that will want to set O_NONBLOCK on both?

A documentation concern is that users may reasonably expect that setting O_NONBLOCK on a file will cause reads and writes to return immediately if they can make no progress. I don't believe that's how non-blocking files work in the runtime, however; the underlying FD may be non-blocking, but Read and Write are still blocking operations. (Am I correct about that?)

@adonovan
Copy link
Member

A documentation concern is that users may reasonably expect that setting O_NONBLOCK on a file will cause reads and writes to return immediately if they can make no progress. I don't believe that's how non-blocking files work in the runtime, however; the underlying FD may be non-blocking, but Read and Write are still blocking operations. (Am I correct about that?)

In effect, yes: Open on UNIX always creates non-blocking files, even without NONBLOCK, since the runtime poller requires it. (See newFile in os/file_unix.go, which calls SetNonblock if not already done at open(2).)

@neild
Copy link
Contributor

neild commented May 13, 2025

My main concerns are:

  1. If we have os.O_NONBLOCK, it shouldn't have completely different effects on different platforms. If this is a case where Unix programs might want to set O_NONBLOCK under some circumstances, and Windows programs might want to set it in entirely different circumstances, then that seems confusing.
  2. "O_NONBLOCK" is a very confusing name if it doesn't actually make a file non-blocking. "The underlying file descriptor is nonblocking, but all file operations are still blocking" is not obvious at all. This isn't that bad when it's stuffed off in syscall, but the os package aspires to offer a bit more abstraction.

I'm not necessarily opposed to os.O_NONBLOCK if it has a suitable cross-platform meaning. We'll just need to be careful about the documentation.

syscall.O_OVERLAPPED might be an alternative, if the meanings are substantially different between Unix and Windows.

@qmuntal
Copy link
Member Author

qmuntal commented May 14, 2025

Is it currently true that including O_NONBLOCK on Unix can cause operations to fail with the error syscall.EAGAIN/syscall.EWOULDBLOCK if the operation cannot complete immediately?

@apparentlymart Yes, but the os.File implementation deals with those errors by waiting until the operation is ready to be consumed, so that is transparent to the user.

syscall.O_OVERLAPPED might be an alternative, if the meanings are substantially different between Unix and Windows.

Your gut feeling @neild may be right: we don't understand how os.O_NONBLOCK would be used as a portable flag, even more because until Go 1.25 os.File won't support overlapped I/O. I'm OK postponing os.O_NONBLOCK until we have more data if there is an easy way to create an overlapped os.File on Windows. syscall.O_OVERLAPPED would work.

I'm afraid I still don't really understand when you want to set O_NONBLOCK in a Go program.

O_NONBLOCK is mostly useful when working with named pipes. On Unix, you would use it in case you don't want the open syscall to block a system thread waiting for the other end of the pipe to connect. On Windows, in case you don't want I/O operations to block a system thread while running.

If we have os.O_NONBLOCK, it shouldn't have completely different effects on different platforms. If this is a case where Unix programs might want to set O_NONBLOCK under some circumstances, and Windows programs might want to set it in entirely different circumstances, then that seems confusing.

I get your point now. Setting O_NONBLOCK makes in fact the code more portable, as it aligns the Windows and Unix behaviors: both will do I/O in a nonblocking (which is not the case on Windows without the flag) manner and non will wait until the other end of the pipe is connected (which is not the case on Unix without the flag).

On the other hand, a Unix developer might want to set O_NONBLOCK just to make the open call non-blocking, but still want the file descriptor to act in a blocking manner, therefore call syscall.SetNonblock to make it blocking. That's not possible on Windows, so it would be a limitation on what one can do with os.O_NONBLOCK.

"O_NONBLOCK" is a very confusing name if it doesn't actually make a file non-blocking.

I don't see how that could happen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Incoming
Development

No branches or pull requests

5 participants