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 }