-
Notifications
You must be signed in to change notification settings - Fork 18.5k
Description
The RawConn interface enables us controlling, reading, and writing net.Conn and os.File with direct syscalls. Currently it is defined as:
type RawConn interface {
// Control invokes f on the underlying connection's file
// descriptor or handle.
// The file descriptor fd is guaranteed to remain valid while
// f executes but not after f returns.
Control(f func(fd uintptr)) error
// Read invokes f on the underlying connection's file
// descriptor or handle; f is expected to try to read from the
// file descriptor.
// If f returns true, Read returns. Otherwise Read blocks
// waiting for the connection to be ready for reading and
// tries again repeatedly.
// The file descriptor is guaranteed to remain valid while f
// executes but not after f returns.
Read(f func(fd uintptr) (done bool)) error
// Write is like Read but for writing.
Write(f func(fd uintptr) (done bool)) error
}
This definition has some inconvenience:
- Cannot pass more arguments to f, which may be needed by f. We have to pass a function literal as f, which binds context parameters inside it,
- Any error occurred in f cannot be captured directly, we have to store the error to outer variable err through a function literal (closure),
- Function literals escape to heap if they refer to outer variables.
To overcome these inconvenience, and keep backward compatible, I propose adding these 3 methods to RawConn interface:
ControlWith(f func(fd uintptr, arg unsafe.Pointer) error, arg unsafe.Pointer) error
ReadWith(f func(fd uintptr, arg unsafe.Pointer) (done bool, err error), arg unsafe.Pointer) error
WriteWith(f func(fd uintptr, arg unsafe.Pointer) (done bool, err error), arg unsafe.Pointer) error
Here, we add another argument, named arg, to f, so we can pass more info needed by f. We also add an error to the returned value of f to capture its errors, which is returned directly by these 3 methods as error, so no function literals are needed. Meanwhile, the real argument is passed to these 3 methods in addition to f, which is in turn passed to f when calling f in the implementation of these 3 methods.
By doing this, we eliminate all of the inconvenience, and pprof shows the function literals don't escape to heap while it can still get what it wants, improving the performance.
This proposal affects package net, os, syscall, and internal/poll, but the effect is small and limited. The main implementation is in internal/poll. Taking WriteWith (in package internal/poll, it will be called RawWriteWith) as an example:
func (fd *FD) RawWriteWith(f func(uintptr, unsafe.Pointer) (bool, error), arg unsafe.Pointer) error {
if err := fd.writeLock(); err != nil {
return err
}
defer fd.writeUnlock()
if err := fd.pd.prepareWrite(fd.isFile); err != nil {
return err
}
for {
if done, err := f(uintptr(fd.Sysfd), arg); done {
return err
}
if err := fd.pd.waitWrite(fd.isFile); err != nil {
return err
}
}
}
The semantics of these methods are consistent with existing Control, Read, and Write. The design is also consistent.
Any thoughts?