// Package bgreader provides a io.Reader that can optionally buffer reads in the background.
package bgreader import ( ) const ( StatusStopped = iota StatusRunning StatusStopping ) // BGReader is an io.Reader that can optionally buffer reads in the background. It is safe for concurrent use. type BGReader struct { r io.Reader cond *sync.Cond status int32 readResults []readResult } type readResult struct { buf *[]byte err error } // Start starts the backgrounder reader. If the background reader is already running this is a no-op. The background // reader will stop automatically when the underlying reader returns an error. func ( *BGReader) () { .cond.L.Lock() defer .cond.L.Unlock() switch .status { case StatusStopped: .status = StatusRunning go .bgRead() case StatusRunning: // no-op case StatusStopping: .status = StatusRunning } } // Stop tells the background reader to stop after the in progress Read returns. It is safe to call Stop when the // background reader is not running. func ( *BGReader) () { .cond.L.Lock() defer .cond.L.Unlock() switch .status { case StatusStopped: // no-op case StatusRunning: .status = StatusStopping case StatusStopping: // no-op } } // Status returns the current status of the background reader. func ( *BGReader) () int32 { .cond.L.Lock() defer .cond.L.Unlock() return .status } func ( *BGReader) () { := true for { := iobufpool.Get(8192) , := .r.Read(*) * = (*)[:] .cond.L.Lock() .readResults = append(.readResults, readResult{buf: , err: }) if .status == StatusStopping || != nil { .status = StatusStopped = false } .cond.L.Unlock() .cond.Broadcast() } } // Read implements the io.Reader interface. func ( *BGReader) ( []byte) (int, error) { .cond.L.Lock() defer .cond.L.Unlock() if len(.readResults) > 0 { return .readFromReadResults() } // There are no unread background read results and the background reader is stopped. if .status == StatusStopped { return .r.Read() } // Wait for results from the background reader for len(.readResults) == 0 { .cond.Wait() } return .readFromReadResults() } // readBackgroundResults reads a result previously read by the background reader. r.cond.L must be held. func ( *BGReader) ( []byte) (int, error) { := .readResults[0].buf var error := copy(, *) if == len(*) { = .readResults[0].err iobufpool.Put() if len(.readResults) == 1 { .readResults = nil } else { .readResults = .readResults[1:] } } else { * = (*)[:] .readResults[0].buf = } return , } func ( io.Reader) *BGReader { return &BGReader{ r: , cond: &sync.Cond{ L: &sync.Mutex{}, }, } }