195 lines
3.9 KiB
Go
195 lines
3.9 KiB
Go
|
// Copyright (C) 2019 Storj Labs, Inc.
|
||
|
// See LICENSE for copying information
|
||
|
|
||
|
package sync2
|
||
|
|
||
|
import (
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"sync"
|
||
|
"sync/atomic"
|
||
|
)
|
||
|
|
||
|
type tee struct {
|
||
|
buffer ReadAtWriteAtCloser
|
||
|
open *int64
|
||
|
|
||
|
mu sync.Mutex
|
||
|
nodata sync.Cond
|
||
|
noreader sync.Cond
|
||
|
|
||
|
maxRead int64
|
||
|
write int64
|
||
|
|
||
|
writerDone bool
|
||
|
writerErr error
|
||
|
}
|
||
|
|
||
|
// NewTeeFile returns a tee that uses file-system to offload memory
|
||
|
func NewTeeFile(readers int, tempdir string) ([]PipeReader, PipeWriter, error) {
|
||
|
tempfile, err := ioutil.TempFile(tempdir, "tee")
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
handles := int64(readers + 1) // +1 for the writer
|
||
|
|
||
|
buffer := &offsetFile{
|
||
|
file: tempfile,
|
||
|
open: &handles,
|
||
|
}
|
||
|
|
||
|
return newTee(buffer, readers, &handles)
|
||
|
}
|
||
|
|
||
|
func newTee(buffer ReadAtWriteAtCloser, readers int, open *int64) ([]PipeReader, PipeWriter, error) {
|
||
|
tee := &tee{
|
||
|
buffer: buffer,
|
||
|
open: open,
|
||
|
}
|
||
|
tee.nodata.L = &tee.mu
|
||
|
tee.noreader.L = &tee.mu
|
||
|
|
||
|
teeReaders := make([]PipeReader, readers)
|
||
|
for i := 0; i < readers; i++ {
|
||
|
teeReaders[i] = &teeReader{tee: tee}
|
||
|
}
|
||
|
|
||
|
return teeReaders, teeWriter{tee}, nil
|
||
|
}
|
||
|
|
||
|
type teeReader struct {
|
||
|
tee *tee
|
||
|
pos int64
|
||
|
closed int32
|
||
|
}
|
||
|
|
||
|
type teeWriter struct{ tee *tee }
|
||
|
|
||
|
// Read reads from the tee returning io.EOF when writer is closed or bufSize is reached.
|
||
|
//
|
||
|
// It will block if the writer has not provided the data yet.
|
||
|
func (reader *teeReader) Read(data []byte) (n int, err error) {
|
||
|
tee := reader.tee
|
||
|
tee.mu.Lock()
|
||
|
|
||
|
// fail fast on writer error
|
||
|
if tee.writerErr != nil && tee.writerErr != io.EOF {
|
||
|
tee.mu.Unlock()
|
||
|
return 0, tee.writerErr
|
||
|
}
|
||
|
|
||
|
toRead := int64(len(data))
|
||
|
end := reader.pos + toRead
|
||
|
|
||
|
if end > tee.maxRead {
|
||
|
tee.maxRead = end
|
||
|
tee.noreader.Broadcast()
|
||
|
}
|
||
|
|
||
|
// wait until we have any data to read
|
||
|
for reader.pos >= tee.write {
|
||
|
// has the writer finished?
|
||
|
if tee.writerDone {
|
||
|
tee.mu.Unlock()
|
||
|
return 0, tee.writerErr
|
||
|
}
|
||
|
|
||
|
// ok, let's wait
|
||
|
tee.nodata.Wait()
|
||
|
}
|
||
|
|
||
|
// how much there's available for reading
|
||
|
canRead := tee.write - reader.pos
|
||
|
if toRead > canRead {
|
||
|
toRead = canRead
|
||
|
}
|
||
|
readAt := reader.pos
|
||
|
tee.mu.Unlock()
|
||
|
|
||
|
// read data
|
||
|
readAmount, err := tee.buffer.ReadAt(data[:toRead], readAt)
|
||
|
reader.pos += int64(readAmount)
|
||
|
|
||
|
return readAmount, err
|
||
|
}
|
||
|
|
||
|
// Write writes to the buffer returning io.ErrClosedPipe when limit is reached
|
||
|
//
|
||
|
// It will block until at least one reader require the data.
|
||
|
func (writer teeWriter) Write(data []byte) (n int, err error) {
|
||
|
tee := writer.tee
|
||
|
tee.mu.Lock()
|
||
|
|
||
|
// have we closed already
|
||
|
if tee.writerDone {
|
||
|
tee.mu.Unlock()
|
||
|
return 0, io.ErrClosedPipe
|
||
|
}
|
||
|
|
||
|
for tee.write > tee.maxRead {
|
||
|
// are all readers already closed?
|
||
|
if atomic.LoadInt64(tee.open) <= 1 {
|
||
|
tee.mu.Unlock()
|
||
|
return 0, io.ErrClosedPipe
|
||
|
}
|
||
|
// wait until new data is required by any reader
|
||
|
tee.noreader.Wait()
|
||
|
}
|
||
|
|
||
|
writeAt := tee.write
|
||
|
tee.mu.Unlock()
|
||
|
|
||
|
// write data to buffer
|
||
|
writeAmount, err := tee.buffer.WriteAt(data, writeAt)
|
||
|
|
||
|
tee.mu.Lock()
|
||
|
// update writing head
|
||
|
tee.write += int64(writeAmount)
|
||
|
// wake up reader
|
||
|
tee.nodata.Broadcast()
|
||
|
tee.mu.Unlock()
|
||
|
|
||
|
return writeAmount, err
|
||
|
}
|
||
|
|
||
|
// Close implements io.Reader Close
|
||
|
func (reader teeReader) Close() error { return reader.CloseWithError(nil) }
|
||
|
|
||
|
// Close implements io.Writer Close
|
||
|
func (writer teeWriter) Close() error { return writer.CloseWithError(nil) }
|
||
|
|
||
|
// CloseWithError implements closing with error
|
||
|
func (reader teeReader) CloseWithError(reason error) (err error) {
|
||
|
tee := reader.tee
|
||
|
if atomic.CompareAndSwapInt32(&reader.closed, 0, 1) {
|
||
|
err = tee.buffer.Close()
|
||
|
}
|
||
|
|
||
|
tee.mu.Lock()
|
||
|
tee.noreader.Broadcast()
|
||
|
tee.mu.Unlock()
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// CloseWithError implements closing with error
|
||
|
func (writer teeWriter) CloseWithError(reason error) error {
|
||
|
if reason == nil {
|
||
|
reason = io.EOF
|
||
|
}
|
||
|
|
||
|
tee := writer.tee
|
||
|
tee.mu.Lock()
|
||
|
if tee.writerDone {
|
||
|
tee.mu.Unlock()
|
||
|
return io.ErrClosedPipe
|
||
|
}
|
||
|
tee.writerDone = true
|
||
|
tee.writerErr = reason
|
||
|
tee.nodata.Broadcast()
|
||
|
tee.mu.Unlock()
|
||
|
|
||
|
return tee.buffer.Close()
|
||
|
}
|