On 7/26/16 4:05 PM, Matt Harden wrote:
> Select and epool actually aren't useful for fd's that are opened on
> regular files. As I understand it, they always indicate those files
> are ready for I/O.
That may well be the case for regular files. I'm talking in particular
about filehandles which are connected to a TTY, which is a character
device. Pipes (another strong possibility for standard input/output)
are also not regular files. In general it's up to the operating system
what kind of file it is you get when you call open(2), or what any file
descriptor you receive on process start-up is connected to.
As a thought experiment I half mocked up a module for this called
chanio; some highlights:
// Reader declares extra methods over an io.Reader which allow data to
// be read from channels and not just via the Read() method.
type Reader interface {
io.Reader
ReadChan() <-chan []byte
ReadError() <-chan error
}
// Writer declares extra methods over an io.Writer which allow data to
// be read from channels and not just via the Writer() method.
type Writer interface {
io.Writer
WriteChan() chan<- []byte
WriteError() <-chan error
}
// WrappedReader wraps a regular io.Reader and satisfies the Reader
// (and ReadCloser) interface
type WrappedReader struct {
rBuf []byte
blockSz int
readErr chan error
R io.Reader
rChan chan []byte
}
func (wr *WrappedReader) wrapRead(r io.Reader, chanSz, blockSz int) {
wr.R = r
wr.blockSz = blockSz
wr.buf = nil
wr.rChan = make(chan []byte, chanSz)
wr.readErr = make(chan error)
go wr.readLoop()
}
func (wr *WrappedReader) readLoop() {
var n int
var err error
for {
block := make([]byte, wr.blockSz)
n, err = wr.R.Read(block)
if err != nil {
wr.readErr <- err
// TODO: some errors are expected eg EINTR
close(wr.rChan)
return
}
if n != 0 {
wr.rChan <- block[:n]
}
}
}
// Read allows a channel IO object to be used wherever a normal Reader
is used
func (wr *WrappedReader) Read(buf []byte) (int, error) {
// buffered read
if len(wr.rBuf) > len(buf) {
buf[0:len(buf)] = wr.rBuf[0:len(buf)]
wr.rBuf = wr.rBuf[len(buf):]
return len(buf), nil
} else if len(wr.rBuf) > 0 {
// partial read
n := len(wr.rBuf)
buf[0:n] = wr.rBuf
wr.rBuf = nil
return n, nil
}
// no data ready - read from the channel
select {
case block := <-wr.ReadChan():
wr.rBuf = append(wr.rBuf, block)
return wr.Read(buf)
case err := <-wr.ReadError():
return -1, err
}
}
func WrapReader(reader io.Reader) Reader {
wr := &WrappedReader{}
wr.wrapRead(reader, 1, 1024)
return wr
}
func (wr *WrappedReader) ReadChan() <-chan []byte {
return wr.rChan
}
func (wr *WrappedReader) ReadError() <-chan error {
return wr.readErr
}
This might be a bit less awkward as a common API than having to use Read
everywhere. Clearly it would be better if there was a way to create one
of these chanio.Reader objects from something with a native select(2)
backing for channel reads, which could be a different constructor.
Thoughts?
Sam