Go: os: add File.SyscallConn method to permit frobbing file descriptor

Created on 10 Mar 2018  路  25Comments  路  Source: golang/go

Since go 1.9, os.Files are backed by a mechanism similar to netpoll, whenever possible. This means that you can set read and write deadlines on them, and that you can safely Close() a file from another goroutine while Read() and / or Write() calls are active.

For working with device nodes (/dev/XXX files, like serial ports /dev/ttySx, but not only) this is very useful.

Sadly it turns out one cannot use this new feature, for a trivial and equally frustrating reason:

Occasionally one needs to access the underlying file-descriptor of a device in order to do a trivial ioctl() or something similar. But calling File.Fd(), sets the underlying filedes to blocking mode and disables (forever) all the cool poller support (deadlines, etc).

One cannot turn the filedes to nonblocking mode himself, because he would also need to set internal File / pfd attributes which he has no access to.

Alternatively, one cannot start with a filedes (i.e. use syscall.Open) and convert it to a file, using NewFile() since Files created with NewFile are not considered pollable.

So please: Provide a way to get a File's underlying filedes without setting it to non-blocking mode, and (most importantly) without loosing all the poller goodies. A new method like File.RawFd() would be perfectly adequate and trivial to implement.

I believe my case (accessing pollable device nodes) is exactly one of those for which the poller support was added to File's. So I should be able to use it.

FrozenDueToAge NeedsFix help wanted

Most helpful comment

@cpuguy83 Yes, this is committed to tip and will be in the next beta release.

All 25 comments

An alternative solution would be for NewFile() to test if the given fd is in non-blocking mode, in which case it would convert it to a pollable file. Otherwise, it would convert it to a non-pollable file, as it, unconditionally, does today. Actually, doing both would be nice...

If you want, I could make the changes to os.NewFile() and / or add the File.RawFd() method myself and submit a patch...

@npat-efault thank you for filing and for articulating your motivations for this issue.

You might be interested in this issue https://github.com/golang/go/issues/22939 that was accepted for Go1.11 in which the proposal was to add an API to construct an *os.File with a non-blocking file descriptor. Perhaps that issue might solve your problem without having to add another method, plus it is just waiting on implementation so perhaps you might be interested?

In regards to https://github.com/golang/go/issues/24331#issuecomment-371998136

An alternative solution would be for NewFile() to test if the given fd is in non-blocking mode, in which case it would convert it to a pollable file. Otherwise, it would convert it to a non-pollable file, as it, unconditionally, does today. Actually, doing both would be nice...

@ianlancetaylor had the exact same thoughts in https://github.com/golang/go/issues/22939#issuecomment-348408705

Perhaps if os.NewFile sees that the descriptor is already in non-blocking mode, it should try adding it to the poller.

And now to the question for clarity, your issue says
os: File.Fd() sets underlying filedes to non-blocking.
However the context of it is that we are setting filedes to blocking mode by invoking *File.Fd().

Did you mean to say instead either of these options below?

  • os: File.Fd() set underlying filedes to blocking mode
  • proposal: os: provide a method on *File to access filedes without setting it to blocking mode
  • proposal: os: add a *File.RawFd method

What about the users that rely on the current behavior of file.Fd()?

Sorry, this was a typo. I fixed the title.

My proposal is:

  • Leave File.Fd() as is, since users may depend on it

and one or, better, both of:

  1. Change NewFile() to convert an already-nonblocking filedes to a pollable file
  2. Add a File.RawFd() method that returns the underlying filedes without any conversion from / to blocking / non-blocking

Technically 1 above could be said that it breaks backwards compatibility (in some sense), but if we agree that this break is not one we care about (the original behavior was not useful anyway), and if we agree that this is the way to go, I could post a (or two for 1 and 2) CLs in a couple of days or sooner.

If we don't want to break backward compatibility in no way whatsoever by changing the behavior of NewFile(), then we could define a NewFile1() with the new behavior (yes, it's not the prettiest thing, but if you are so strongly set about backwards compatibility, you have to abide a bit of ugliness).

Change https://golang.org/cl/100077 mentions this issue: os: NewFile called with nonblock fd, tries to return pollable file

Submitted CL https://golang.org/cl/100077 for your consideration...

/cc @ianlancetaylor for a decision

I thought we'd done (1) ("Change NewFile() to convert an already-nonblocking filedes to a pollable file") already. /cc @ianlancetaylor

Re (2) I think adding SyscallConn is probably the next step instead of RawFd, but we don't need that yet.

@rsc

I agree... though a bit cumbersome to use, SyscallCon() is the "right way" to provide access to the underlying fd for misc operations (ioctls and stuff). This is how it's done for network connections, and there is no reason for it to be different for other "connection-like" file-descriptors. Furthermore it provides synchronization against concurrent Close() calls (which is something the user should provide himself in the RawFd() case).

I could start looking into the implementation of this, if there are no other takers...

I'm currently having to use gross syscall.Select signaling to work around concurrent .Read() and .Close() when dealing with a /dev/net/tun that I call .Fd() on. With https://github.com/golang/go/issues/22939 taken care of, having .RawFd() is the remaining issue before I can remove the syscall.Select hack.

I could start looking into the implementation of this, if there are no other takers...

@npat-efault all yours :) Thank you for offering to work on it!

How's it going @npat-efault? Kindly paging you as the Go1.12 cycle is a couple of weeks away from ending.

Change https://golang.org/cl/155517 mentions this issue: os: add SyscallConn method for os.File

I won't vote against this because I don't want a fight, but I wish it didn't go in to the os package as it encourages ugly I/O patterns that until now were only available through the syscall package, where they belong (if anywhere). Promoting this to the os package endorses them, and that's what I object to.

Copying from the issue (should have replied here in the first place).

This is a pattern we already adopted for the net package. I don't see it quite as you do. The ugly I/O patterns still use the syscall package. I don't think we're really endorsing them. What we're doing here is permitting people to use the os package for normal use while dropping down to the syscall package when they have to, instead of saying that if they have one ugly syscall use then they have to use the syscall package for everything, and can never use the os package.

You're right of course that it is overly complicated but it's hard to just wish away the weird stuff that people want to do.

@ianlancetaylor,

Can you please clarify what's the scope of the API? Does the API cover only a file, I mean, a byte-sequence, allowing partial reads/writes on a message that indicates an EOF-like signal, or more widely including networking stuff?

This is a pattern we already adopted for the net package.

Well, not exactly the same. The net package takes more information, e.g., a network parameter that indicates a set of communication capabilities, from API users to ensure correct behavior on work with the kernel, and provides more information for fault localization.

@mikioh The API under discussion here already exists and is already used by the net package. It's https://golang.org/pkg/syscall/#Conn and https://golang.org/pkg/syscall/#RawConn. This issue is about implementing the same API in the os package, by adding a method to os.File that returns a syscall.Conn. You say that it's not exactly the same as what we do in the net package, but it is exactly the same.

Very happy to see this land. This should improve things for TUN/TAP implementations quite a bit. Thanks a lot.

@ianlancetaylor,

Thanks for the clarification. I see your comments "implementing the same API in the os package" and "exactly the same as what we do in the net package" and don't see a comment like "both package implementations are same." That means that os.File.SyscallConn and net.{TCP,UDP,IP,Unix}Conn.SyscallConn may return different error values and methods of syscall.RawConn may return different error values, depending on the package returning the syscall.RawConn.

@mikioh That is true: the error values are not the same. It's not obvious to me that it makes sense for them to be the same, since the net package error values return an address, in the net.OpError.Source field, which does not exist for the os package error values.

I see this is in the 1.12 milestone. Is there a plan to have this in the next beta or pushed to 1.13?

@cpuguy83 Yes, this is committed to tip and will be in the next beta release.

Change https://golang.org/cl/156839 mentions this issue: doc: go1.12: mention os.File.SyscallConn

Was this page helpful?
0 / 5 - 0 ratings