Documentation ¶
Overview ¶
Package poller is a file-descriptor multiplexer. It allows concurent Read and Write operations from and to multiple file-descriptors without allocating one OS thread for every blocked operation. It operates similarly to Go's netpoller (which multiplexes network connections) without requiring special support from the Go runtime. It can be used with tty devices, character devices, pipes, FIFOs, and any file-descriptor that is poll-able (can be used with select(2), epoll(7), etc.) In addition, package poller allows the user to set timeouts (deadlines) for read and write operations, and also allows for safe cancelation of blocked read and write operations; a Close from another go-routine safely cancels ongoing (blocked) read and write operations.
Typical usage
fd, err: = poller.Open("/dev/ttyXX", poller.O_RW) if err != nil { log.Fatal("Failed to open device:", err) } ... err = fd.SetReadDeadline(time.Now().Add(5 * time.Second)) if err != nil { log.Fatal("Failed to set R deadline:", err) } b := make([]byte, 10) n, err := fd.Read(b) if err != nil { log.Fatal("Read failed:", err) } ... n, err = fd.Write([]byte("Test")) if err != nil { log.Fatal("Write failed:", err) }
All operations on poller FDs are thread-safe; you can use the same FD from multiple go-routines. It is, for example, safe to close a file descriptor blocked on a Read or Write call from another go-routine.
Supported systems ¶
Linux systems are supported using an implementations based on epoll(7).
For other POSIX systems (that is, most Unix-like systems), a more portable fallback implementation based on select(2) is provided. It has the same semantics as the Linux epoll(7)-based implementation, but is expected to be of lower performance. The select(2)-based implementation uses CGo for some ancillary select-related operations.
Ideally, system-specific io-multiplexing or async-io facilities (e.g. kqueue(2), or /dev/poll(7)) should be used to provide higher performance implementations for other systems. Patches for this will be greatly appreciated.
If you wish, you can build package poller on Linux to use the select(2)-based implementation instead of the epoll(7) one. To do this define the build-tag "noepoll". Normally, there is no reason to do this.
Index ¶
- Constants
- Variables
- type FD
- func (fd *FD) Close() error
- func (fd *FD) CloseUnlocked() error
- func (fd *FD) Lock() error
- func (fd *FD) Read(p []byte) (n int, err error)
- func (fd *FD) SetDeadline(t time.Time) error
- func (fd *FD) SetReadDeadline(t time.Time) error
- func (fd *FD) SetWriteDeadline(t time.Time) error
- func (fd *FD) Sysfd() int
- func (fd *FD) Unlock()
- func (fd *FD) Write(p []byte) (n int, err error)
Constants ¶
const ( O_RW = (syscall.O_NOCTTY | syscall.O_CLOEXEC | syscall.O_RDWR | syscall.O_NONBLOCK) // Open file for read and write O_RO = (syscall.O_NOCTTY | syscall.O_CLOEXEC | syscall.O_RDONLY | syscall.O_NONBLOCK) // Open file for read O_WO = (syscall.O_NOCTTY | syscall.O_CLOEXEC | syscall.O_WRONLY | syscall.O_NONBLOCK) // Open file for write )
Flags to Open
Variables ¶
var ( // Use of closed poller file-descriptor. ErrClosed has // Closed() == true. ErrClosed error = mkErr(efClosed, "use of closed descriptor") // Operation timed-out. ErrTimeout has Timeout() == true and // Temporary() == true. ErrTimeout error = mkErr(efTimeout, "timeout/deadline expired") )
Errors returned by poller functions and methods. In addition to these, poller functions and methods may return the errors reported by the underlying system calls (open(2), read(2), write(2), etc.), as well as io.EOF and io.ErrUnexpectedEOF.
Functions ¶
This section is empty.
Types ¶
type FD ¶
type FD struct {
// contains filtered or unexported fields
}
FD is a poller file-descriptor. Typically a file-descriptor connected to a terminal, a pseudo terminal, a character device, a FIFO (named pipe), or any unix stream that can be used by the system's file-descriptor multiplexing mechanism (epoll(7), select(2), etc).
func NewFD ¶
NewFD initializes and returns a new poller file-descriptor from the given system (unix) file-descriptor. After calling NewFD the system file-descriptor must be used only through the poller.FD methods, not directly. NewFD sets the system file-descriptor to non-blocking mode.
func (*FD) Close ¶
Close the file descriptor. It is ok to call Close concurently with other operations (Read, Write, SetDeadline, etc) on the same file-descriptor. In this case ongoing operations will return with ErrClosed. Any subsequent operations (after Close) on the file-descriptor will also fail with ErrClosed.
func (*FD) CloseUnlocked ¶
CloseUnlocked is equivalent to (*FD).Close, with the difference that with CloseUnlocked the caller is responsible for locking the FD before calling it, and unlocking it after. It can be useful for performing clean-up operations (e.g. reset tty settings) atomically with Close.
func (*FD) Lock ¶
Lock the file-descriptor. It must be called before perfoming miscellaneous operations (e.g. ioctls) on the underlying system file descriptor. It protects the operations against concurent Close's. Typical usage is:
if err := fd.Lock(); err != nil { return err } deffer fd.Unlock() ... do ioctl on fd.Sysfd() ...
Notice that Read's and Write's *can* happen concurrently with misc operations on a locked FD---as this is not, necessarily, an error. Lock protects *only* against concurent Close's.
func (*FD) Read ¶
Read up to len(p) bytes into p. Returns the number of bytes read (0 <= n <= len(p)) and any error encountered. If some data is available but not len(p) bytes, Read returns what is available instead of waiting for more. Read is compatible with the Read method of the io.Reader interface. In addition Read honors the timeout set by (*FD).SetDeadline and (*FD).SetReadDeadline. If no data are read before the timeout expires Read returns with err == ErrTimeout (and n == 0). If the read(2) system-call returns 0, Read returns with err = io.EOF (and n == 0).
func (*FD) SetDeadline ¶
SetDeadline sets the deadline for both Read and Write operations on the file-descriptor. Deadlines are expressed as ABSOLUTE instances in time. For example, to set a deadline 5 seconds to the future do:
fd.SetDeadline(time.Now().Add(5 * time.Second))
This is equivalent to:
fd.SetReadDeadline(time.Now().Add(5 * time.Second)) fd.SetWriteDeadline(time.Now().Add(5 * time.Second))
A zero value for t, cancels (removes) the existing deadline.
func (*FD) SetReadDeadline ¶
SetReadDeadline sets the deadline for Read operations on the file-descriptor.
func (*FD) SetWriteDeadline ¶
SetWriteDeadline sets the deadline for Write operations on the file-descriptor.
func (*FD) Sysfd ¶
Sysfd returns the system file-descriptor associated with the given poller file-descriptor. See also (*FD).Lock.
func (*FD) Write ¶
Writes len(p) bytes from p to the file-descriptor. Returns the number of bytes written (0 <= n <= len(p)) and any error encountered that caused the write to stop early. Write returns a non-nil error if it returns n < len(p). Write is compatible with the Write method of the io.Writer interface. In addition Write honors the timeout set by (*FD).SetDeadline and (*FD).SetWriteDeadline. If less than len(p) data are writen before the timeout expires Write returns with err == ErrTimeout (and n < len(p)). If the write(2) system-call returns 0, Write returns with err == io.ErrUnexpectedEOF.
Write attempts to write the full amount requested by issuing multiple system calls if required. This sequence of system calls is NOT atomic. If multiple goroutines write to the same FD at the same time, their Writes may interleave. If you wish to protect against this, you can embed FD and use a mutex to guard Write method calls:
struct myFD { *poller.FD mu sync.Mutex } func (fd *myFD) Write(p []byte) (n int, err error) { fd.mu.Lock() n, err := fd.FD.Write(p) fd.mu.Unlock() return n, err }