github.com/haraldrudell/parl@v0.4.176/pio/read-write-closer-slice.go (about)

     1  /*
     2  © 2021–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  // ReadWriteCloserSlice is a read-writer with a slice as intermediate storage. thread-safe.
     7  package pio
     8  
     9  import (
    10  	"io"
    11  	"io/fs"
    12  	"sync"
    13  
    14  	"github.com/haraldrudell/parl/perrors"
    15  )
    16  
    17  // ReadWriteCloserSlice is a read-writer with a slice as intermediate storage. thread-safe.
    18  //   - Close closes the writer side indicating no further data will be added
    19  //   - Write and Close may return error that can be checked: errors.Is(err, pio.ErrFileAlreadyClosed)
    20  //   - read will eventually return io.EOF after a Close
    21  //   - there are no other errors
    22  type ReadWriteCloserSlice struct {
    23  	dataLock sync.Mutex
    24  	isClosed bool
    25  	data     []byte
    26  
    27  	readerCond sync.Cond
    28  }
    29  
    30  var _ io.ReadWriteCloser = &ReadWriteCloserSlice{}
    31  
    32  func NewReadWriteCloserSlice() (readWriteCloser *ReadWriteCloserSlice) {
    33  	return &ReadWriteCloserSlice{readerCond: *sync.NewCond(&sync.Mutex{})}
    34  }
    35  
    36  // Write saves data in slice and returns all bytes written or ErrFileAlreadyClosed
    37  func (r *ReadWriteCloserSlice) Write(p []byte) (n int, err error) {
    38  	r.dataLock.Lock()
    39  	defer r.dataLock.Unlock()
    40  
    41  	if r.isClosed {
    42  		err = perrors.ErrorfPF("%w", fs.ErrClosed)
    43  		return // closed return
    44  	}
    45  
    46  	// consume data
    47  	r.data = append(r.data, p...)
    48  	n = len(p)
    49  
    50  	return // good write return
    51  }
    52  
    53  // Read returns at most len(p) bytes read in n and possibly io.EOF
    54  //   - Read is blocking
    55  //   - n may be less than len(p)
    56  //   - if len(p) > 0, non-error return will have n > 0
    57  func (r *ReadWriteCloserSlice) Read(p []byte) (n int, err error) {
    58  	r.readerCond.L.Lock()
    59  	defer r.readerCond.L.Unlock()
    60  
    61  	for {
    62  
    63  		var haveData bool
    64  		if haveData, n, err = r.read(p); haveData || err != nil {
    65  			return // data read or or error return
    66  		}
    67  
    68  		// wait for write or close
    69  		r.readerCond.Wait()
    70  	}
    71  }
    72  
    73  func (r *ReadWriteCloserSlice) Buffer() (buffer []byte) {
    74  	return r.data
    75  }
    76  
    77  func (r *ReadWriteCloserSlice) read(p []byte) (haveData bool, n int, err error) {
    78  	r.dataLock.Lock()
    79  	defer r.dataLock.Unlock()
    80  
    81  	// check for EOF or no data
    82  	data := r.data
    83  	d := len(data)
    84  	if haveData = d > 0; !haveData {
    85  		if r.isClosed {
    86  			err = io.EOF
    87  			return // eof return: haveData false, err io.EOF
    88  		}
    89  		haveData = len(p) == 0
    90  		return // zero-bytes requested return: haveData true, otherwise haveData false
    91  	}
    92  
    93  	// copy one or more bytes
    94  	copy(p, data)
    95  
    96  	n = len(p)
    97  	if d <= n {
    98  
    99  		// all data consumed
   100  		n = d             // N is bytes read
   101  		r.data = data[:0] // empty buffer
   102  		return            // all data submitted return
   103  	}
   104  
   105  	// only len(p) bytes of data was consumed
   106  	// n already has the shorter len(p) value
   107  	r.data = data[n:] // remove consumed bytes from data
   108  	return            // p filled return
   109  }
   110  
   111  // Close closes thw Write part, may return ErrFileAlreadyClosed
   112  func (r *ReadWriteCloserSlice) Close() (err error) {
   113  	var doBroadcast bool
   114  	defer func() {
   115  		if doBroadcast {
   116  			r.readerCond.Broadcast()
   117  		}
   118  	}()
   119  
   120  	r.dataLock.Lock()
   121  	defer r.dataLock.Unlock()
   122  
   123  	if r.isClosed {
   124  		err = perrors.ErrorfPF("%w", fs.ErrClosed)
   125  		return // closed return
   126  	}
   127  
   128  	r.isClosed = true
   129  	doBroadcast = true
   130  
   131  	return
   132  }