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?).
--
Edit: Updated proposal:
package syscall
const O_OVERLAPPED = 0x04000 // the file or device is being opened or created for asynchronous I/O.
This const will also be added to golang.org/x/sys/windows.
See https://github.com/golang/go/issues/73676#issuecomment-2918713564.
@golang/windows @golang/proposal-review
Comment From: adonovan
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.
Comment From: qmuntal
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.
Comment From: qmuntal
@neild this new flag would also affect the os.Root.OpenFile flag, as it accepts the same paramters as os.CreateFile.
Comment From: neild
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?
Comment From: qmuntal
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_NONBLOCKtoos.OpenFilesomehow 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, theopensyscall doesn't block waiting for the other end to connect. On Windows, that's the behavior you always get withCreateFileW, you can't instruct it to wait for the other end of the pipe (you have to use ConnectNamedPipe to achieve that). So usingO_NONBLOCKwould 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.
Comment From: apparentlymart
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?
Comment From: neild
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?)
Comment From: adonovan
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).)
Comment From: neild
My main concerns are:
- 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. - "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 theospackage 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.
Comment From: qmuntal
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.
Comment From: aclements
This proposal has been added to the active column of the proposals project and will now be reviewed at the weekly proposal review meetings. — aclements for the proposal review group
Comment From: neild
"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.
My intuitive understanding of "nonblocking file" is that reading from the file when there is no data available returns immediately. So if f is a non-blocking pipe, n, err := f.Read(buf) will return n == 0 && err != nil when no data is available. That's not what happens, though, because os.File operations are always blocking, regardless of whether the underlying file descriptor is or not.
So I don't think we should have a symbol named os.O_NONBLOCK, because a reasonable interpretation of what it does is wrong.
Reading through this issue again, I think there are a couple things that this proposal is trying to address.
First: On Windows there should be a way to open a file that uses overlapped I/O. Adding a symbol to the syscall package seems like a reasonable way to achieve this (maybe syscall.O_OVERLAPPED). One question here: Is there a way to do this automatically? You mentioned that you might do this to avoid I/O operations blocking a system thread while running. On Unix, we always create non-blocking files. Should we always use overlapped I/O on Windows (possible just for named pipes)?
Second (I think): There should be a portable way to open a named pipe that doesn't block until the other side of the pipe has connected. Maybe there should be a new os package API for this. I don't think we should call it os.O_NONBLOCK, because as described above that seems confusing, but perhaps some other name? os.O_OPEN_NAMED_PIPE_IMMEDIATELY? os.O_IMMEDIATE?
Comment From: qmuntal
My intuitive understanding of "nonblocking file" is that reading from the file when there is no data available returns immediately. So if f is a non-blocking pipe, n, err := f.Read(buf) will return n == 0 && err != nil when no data is available. That's not what happens, though, because os.File operations are always blocking, regardless of whether the underlying file descriptor is or not.
Oh, I get your point now. I see how os.O_NONBLOCK is indeed counterintuitive!
On Windows there should be a way to open a file that uses overlapped I/O. Adding a symbol to the syscall package seems like a reasonable way to achieve this (maybe syscall.O_OVERLAPPED)
That makes sense. I'll change the proposal title and content to propose syscall.O_OVERLAPPED instead.
One question here: Is there a way to do this automatically?
This was the goal of #19098. The problem is that we can't downgrade the file handle to non-overlapped when returning it from File.Fd, and callers might not be prepared to handle overlapped handles. See this comment: https://github.com/golang/go/issues/19098#issuecomment-300878859. On the other hand, we now have the GODEBUG mechanism to opt-out from breaking changes, so we could revisit this approach. Anyway, syscall.O_OVERLAPPED was value on its own, so let's leave using it by default on named pipes for another proposal.
Second (I think): There should be a portable way to open a named pipe that doesn't block until the other side of the pipe has connected.
This was more like a nice side effect of os.O_NONBLOCK than something I really want to have in os. Writing portable code when working with named pipes is hard given the different API semantics provided on Unix and Windows. Let's leave this for a future proposal that tackles named pipes holistically (if that's possible).
Comment From: aclements
Given that the updated proposal is to add a flag to syscall, how does syscall.O_OVERLAPPED relate to syscall.FILE_FLAG_OVERLAPPED?
Comment From: neild
syscall.O_OVERLAPPED is a flag passed to os.OpenFile, and syscall.FILE_FLAG_OVERLAPPED is a flag passed to syscall.CreateFile (which is a direct wrapper of CreateFileA).
Or to put it another way, you would pass syscall.O_OVERLAPPED to os.OpenFile to make it pass FILE_FLAG_OVERLAPPED to the underlying syscall.
An almost-certainly-terrible idea which just occurred to me is that perhaps we could make these part of the same bit set.
All the FILE_FLAG_* flags appear to use the high 12 bits of a uint64:
https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
The made-up os.O_* flag values for Windows use the low 16 bits:
https://go.googlesource.com/go/+/refs/tags/go1.24.4/src/syscall/types_windows.go#35
So we could just support os.OpenFile(name, syscall.FLAG_FILE_OVERLAPPED, mode). We could even pass through other reasonable Windows flags such as FILE_FLAG_DELETE_ON_CLOSE.
This would become exciting if Windows defines any flags conflicting with the invented O_ values, although we could possibly work around the problem so long as we never need to support more than 64 flags in total.
Comment From: qmuntal
An almost-certainly-terrible idea which just occurred to me is that perhaps we could make these part of the same bit set.
I like this terrible idea 😄
All the FILE_FLAG_* flags appear to use the high 12 bits of a uint64:
It uses the high 16 bits of a uint32 (not uint64). It's just that there are some undocumented flags. The low 16 bits are reserved for the file attributes (which roughtly correspond to file permissions on POSIX).
This would become exciting if Windows defines any flags conflicting with the invented O_ values, although we could possibly work around the problem so long as we never need to support more than 64 flags in total.
Fun fact: The CreateFile API can't accept flags other than the ones already defined, as the dwFlagsAndAttributes can't hold more values. This is why CreateFile2 was defined, it separates the attributes from the flags so both can hold more values.
Comment From: neild
So the revised idea would be:
On Windows, os.OpenFile passes through bits 16-31 of its flags parameter (the high bits of a uint32) to CreateFile. We avoid putting any synthetic O_ constants in those bits. This allows passing FILE_FLAG_* values directly to OpenFile.
The syscall package currently defines the following O_ constants that conflict with those bits:
O_CLOEXEC = 0x80000
o_DIRECTORY = 0x100000 // used by internal/syscall/windows
o_NOFOLLOW_ANY = 0x20000000 // used by internal/syscall/windows
o_OPEN_REPARSE = 0x40000000 // used by internal/syscall/windows
o_WRITE_ATTRS = 0x80000000 // used by internal/syscall/windows
The lowercase o_ ones are internal, and I think we can move those into bits 33-63. (OpenFile takes an int parameter, but I think the internal o_ constants are only passed to internal functions that we can specify as taking a uint64.)
That leaves O_CLOEXEC, which I think we might need to redefine. (I don't see a FILE_FLAG_* value using 0x80000 in the OpenFile documentation, but from what you say those bits are entirely in use.) There seem to be some unused bits in the low 16, so that's feasible. That won't break anyone who is using the symbol, but it's technically a backwards-incompatible change.
Comment From: apparentlymart
Given that the os.File abstraction makes certain assumptions about how the underlying filehandle is configured in order to cooperate with the runtime, is actually safe to blindly pass through flags to the underlying API?
That is, could any currently-defined flag cause the underlying handle to behave in a way that breaks the abstraction in a non-obvious way?
I'd personally feel inclined to constrain this only to flags that are known to be safe, with any others returning an error until enabled by a future proposal, but that is primarily me just being conservative because I'm not a Windows API expert; if those who know better feel certain that it's safe then of course I concede this point.
Comment From: neild
@aclements points out that since we unfortunately don't define actual types for the various types of flag bit, the symbol name prefix serves as a type. os.OpenFile(name, syscall.FILE_FLAG_OVERLAPPED, mode) may not be an actual type error, but it looks like one.
So how about this:
We define O_FILE_FLAG_ constants in the syscall package corresponding to every FILE_FLAG_ value we think it makes sense to pass to os.OpenFile. OpenFile passes these through to the underlying system call. We also make OpenFile on Windows return an error if its passed any bits it doesn't recognize.
So:
package syscall
const (
O_FILE_FLAG_OVERLAPPED = FILE_FLAG_OVERLAPPED
O_FILE_FLAG_DELETE_ON_CLOSE = FILE_FLAG_DELETE_ON_CLOSE
// etc.
)
Comment From: cherrymui
We also make OpenFile on Windows return an error if its passed any bits it doesn't recognize.
@adonovan points out that we're currently not returning an error if an unknown bit is passed on all platforms. It is probably better to make it consistent.
Comment From: aclements
In proposal review today, we concluded that it makes sense to check these bits on Windows since we're doing our own interpretation of them. But on UNIXes, people could easily (and safely!) be depending on std to pass all of the bits through to the OS, so it seems like an unnecessary and possibly breaking change to check the bits on UNIX platforms.
Comment From: aclements
Have all remaining concerns about this proposal been addressed?
The proposal is to add the following to package syscall and golang.org/x/sys/windows on Windows:
const (
O_FILE_FLAG_OVERLAPPED = FILE_FLAG_OVERLAPPED // the file or device is being opened or created for asynchronous I/O
O_FILE_FLAG_DELETE_ON_CLOSE = FILE_FLAG_DELETE_ON_CLOSE
)
We will also modify syscall.Open on Windows to return an error if any of the bits passed to the flags argument are not recognized by the Go runtime.
Are there any other FILE_FLAG_* flags we should also expose?
Comment From: qmuntal
Are there any other FILE_FLAG_* flags we should also expose?
These are the ones that I've used more:
- FILE_FLAG_OVERLAPPED (0x40000000)
- FILE_FLAG_DELETE_ON_CLOSE (0x04000000)
- FILE_FLAG_OPEN_REPARSE_POINT (0x00200000)
- FILE_FLAG_SEQUENTIAL_SCAN (0x08000000)
But there are just 11 documented flags. We can just expose them all.
We will also modify syscall.Open on Windows to return an error if any of the bits passed to the flags argument are not recognized by the Go runtime.
As a side note, os.Root doesn't use syscall.Open, but internal/syscall/windows.Openat. We will have to update that function too.
Comment From: aclements
Here are all of the flags.
FILE_FLAG_BACKUP_SEMANTICS FILE_FLAG_DELETE_ON_CLOSE FILE_FLAG_NO_BUFFERING FILE_FLAG_OPEN_NO_RECALL FILE_FLAG_OPEN_REPARSE_POINT FILE_FLAG_OVERLAPPED FILE_FLAG_POSIX_SEMANTICS FILE_FLAG_RANDOM_ACCESS FILE_FLAG_SESSION_AWARE FILE_FLAG_SEQUENTIAL_SCAN FILE_FLAG_WRITE_THROUGH
FILE_FLAG_NO_BUFFERING may be incompatible with Go. I'm not sure about FILE_FLAG_POSIX_SEMANTICS. Everything else looks at a glance like it should be fine.
Comment From: qmuntal
Operating on files opened with the FILE_FLAG_NO_BUFFERING flag does have some strict requirements, and that may make some Go APIs to now work well with those files. Still, I don't know why we should disallow that flag. Is the same as if one opens a file with one write permissions, and then tries to call File.Read.
Same applies to FILE_FLAG_POSIX_SEMANTICS.
Comment From: aclements
Sounds like we should just add all of them, then. 😀
Comment From: aclements
Oh, there's also:
FILE_FLAG_OPEN_REQUIRING_OPLOCK = 0x00040000
FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000
If I'm reading this right, those are flags to CreateFile2, which I think we don't use. But let me know if that's incorrect.
Comment From: aclements
The Windows syscall package only defines the following FILE_FLAG_* flags:
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
FILE_FLAG_OVERLAPPED = 0x40000000
I'm not really sure why it exposes any of these. Is the intent is that they can be passed to syscall.Open? If so, then probably we only want to define O_FILE_FLAG_* equivalents in syscall for these three and perhaps deprecate the FILE_FLAG_* ones (maybe a //go:fix inline?).
This isn't a problem in x/sys/windows because it defines all of the flags.
Comment From: mattn
When developing Windows applications in Go that require direct use of the API, it is often necessary to redefine undefined flags. While it may not be necessary to define everything, it is preferable to have all FILE_FLAG_XXX flags defined, I think.
Comment From: aclements
@mattn, they are all defined in x/sys/windows.
Comment From: aclements
I dug into the syscall.FILE_FLAG_* flags. It looks like these flags exist primarily because we use or used them internally. As it is, they are ignored by syscall.Open, which only accepts O_* flags and interprets them itself. By extension, that means they're currently ignored by functions in os. However, syscall.Open uses FILE_FLAG_OPEN_REPARSE_POINT and FILE_FLAG_BACKUP_SEMANTICS internally. The flags are accepted by syscall.CreateFile, which unlike syscall.Open on Windows, is just a thin wrapper around the Windows API. And we use FILE_FLAG_OVERLAPPED internally in internal/poll.
This all suggests these aren't really intended to be used by callers of syscall or os, though they probably are anyway.
So, the main change here is to accept all of these flags in syscall.Open, pass them through to the OS, and to return an error if there are any flags we don't recognize. Secondary, we add O_* = FILE_FLAG_* flags to x/sys/windows for all CreateFile flags. Finally, I suggest that we not add any O_ flags (or further FILE_FLAG_* flags) to syscall, and instead direct people toward x/sys/windows if they need them.
Does that all sound right?
Comment From: aclements
Have all remaining concerns about this proposal been addressed?
The proposal is to add the following flags to package golang.org/x/sys/windows:
const (
O_FILE_FLAG_OPEN_NO_RECALL = FILE_FLAG_OPEN_NO_RECALL
O_FILE_FLAG_OPEN_REPARSE_POINT = FILE_FLAG_OPEN_REPARSE_POINT
O_FILE_FLAG_SESSION_AWARE = FILE_FLAG_SESSION_AWARE
O_FILE_FLAG_POSIX_SEMANTICS = FILE_FLAG_POSIX_SEMANTICS
O_FILE_FLAG_BACKUP_SEMANTICS = FILE_FLAG_BACKUP_SEMANTICS
O_FILE_FLAG_DELETE_ON_CLOSE = FILE_FLAG_DELETE_ON_CLOSE
O_FILE_FLAG_SEQUENTIAL_SCAN = FILE_FLAG_SEQUENTIAL_SCAN
O_FILE_FLAG_RANDOM_ACCESS = FILE_FLAG_RANDOM_ACCESS
O_FILE_FLAG_NO_BUFFERING = FILE_FLAG_NO_BUFFERING
O_FILE_FLAG_OVERLAPPED = FILE_FLAG_OVERLAPPED
O_FILE_FLAG_WRITE_THROUGH = FILE_FLAG_WRITE_THROUGH
)
And to modify syscall.Open on Windows to 1. accept these flags as flags arguments and pass them to the OS and 2. return an error if any of the bits passed to the flags argument are not recognized by the Go runtime.
Comment From: aclements
Based on the discussion above, this proposal seems like a likely accept. — aclements for the proposal review group
The proposal is to add the following flags to package golang.org/x/sys/windows:
const (
O_FILE_FLAG_OPEN_NO_RECALL = FILE_FLAG_OPEN_NO_RECALL
O_FILE_FLAG_OPEN_REPARSE_POINT = FILE_FLAG_OPEN_REPARSE_POINT
O_FILE_FLAG_SESSION_AWARE = FILE_FLAG_SESSION_AWARE
O_FILE_FLAG_POSIX_SEMANTICS = FILE_FLAG_POSIX_SEMANTICS
O_FILE_FLAG_BACKUP_SEMANTICS = FILE_FLAG_BACKUP_SEMANTICS
O_FILE_FLAG_DELETE_ON_CLOSE = FILE_FLAG_DELETE_ON_CLOSE
O_FILE_FLAG_SEQUENTIAL_SCAN = FILE_FLAG_SEQUENTIAL_SCAN
O_FILE_FLAG_RANDOM_ACCESS = FILE_FLAG_RANDOM_ACCESS
O_FILE_FLAG_NO_BUFFERING = FILE_FLAG_NO_BUFFERING
O_FILE_FLAG_OVERLAPPED = FILE_FLAG_OVERLAPPED
O_FILE_FLAG_WRITE_THROUGH = FILE_FLAG_WRITE_THROUGH
)
And to modify syscall.Open on Windows to 1. accept these flags as flags arguments and pass them to the OS and 2. return an error if any of the bits passed to the flags argument are not recognized by the Go runtime.