github.com/haraldrudell/parl@v0.4.176/pslices/range-ch.go (about) 1 /* 2 © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package pslices 7 8 import ( 9 "github.com/haraldrudell/parl/internal/cyclebreaker" 10 ) 11 12 // RangeCh is a range-able channel based on a thread-safe slice 13 // - the slice is provided to the New function 14 // - each RangeCh has an internal thread that runs until: 15 // - — the slice is out of items 16 // - — Close is invoked 17 // - RangeCh.Ch() must be either read until close or RangeCh.Close must be invoked 18 // - Close discards items 19 // - — 20 // - it is unclear whether pairing an updatable slice with an unbuffered channel is useful 21 // - — unlike a buffered channel, RangeCh is unbound 22 // - — RangeCh costs 1 thread maintaining the unbuffered channel 23 // - — channels are somewhat inefficient by processing only one element at a time 24 // - — controlling allocations by processing single elements, slices of elements or 25 // slices of slices of elements 26 // - — RangeCh sends any available elements then closes the channel and exits 27 type RangeCh[T any] struct { 28 // the range-able channel sending data 29 // - closes on end-of-data 30 // - unbuffered 31 // - send and close by rangeChWriteThread 32 ch chan T 33 // isClosed indicates that [RangeCh.Close] has been invoked 34 // - the channel ch is about to close 35 // - isClosed.Close is idempotent 36 // - synchronization mechanic to exit rangeChWriteThread 37 // - observable 38 isClosed *cyclebreaker.Awaitable 39 // isExit is synchronization mechanic that rangeChWriteThread has exit 40 // - on isExit closing, ch is already closed 41 isExit cyclebreaker.AwaitableCh 42 } 43 44 // NewRangeCh returns a range-able channel based on a [ThreadSafeSlice] 45 // - the Ch channel must be either read until it closes or Close must be invoked 46 // - Ch is a channel that can be used in a Go for-range clause 47 // - — 48 // - for can range over: array slice string map or channel 49 // - the only dynamic range source is channel which costs a thread sending on and closing the channel 50 func NewRangeCh[T any](tss *ThreadSafeSlice[T]) (rangeChan *RangeCh[T]) { 51 var isExit = make(chan struct{}) 52 r := RangeCh[T]{ 53 ch: make(chan T), 54 isClosed: cyclebreaker.NewAwaitable(), 55 isExit: isExit, 56 } 57 58 // launch sending thread 59 go rangeChWriteThread(tss, r.ch, r.isClosed.Ch(), isExit) 60 61 return &r 62 } 63 64 // Ch returns the range-able channel sending values, thread-safe 65 func (r *RangeCh[T]) Ch() (ch <-chan T) { return r.ch } 66 67 // Close closes the ch channel, thread-safe, idempotent 68 // - Close may discard a pending item and causes the thread to stop reading from the slice 69 // - Close does not return until Ch is closed and the thread have exited 70 func (r *RangeCh[T]) Close() { 71 72 // cause rangeChWriteThread to exit 73 r.isClosed.Close() 74 75 // wait for rangeChWriteThread to exit 76 <-r.isExit 77 } 78 79 // State returns [RangeCh] state 80 // - isCloseInvoked is true if [RangeCh.Close] has been invoked, but Close may not have completed 81 // - isChClosed means [RangeCh.Ch] has closed and resources are released 82 // - exitCh allows to wait for Close complete 83 func (r *RangeCh[T]) State() (isCloseInvoked, isChClosed bool, exitCh <-chan struct{}) { 84 isCloseInvoked = r.isClosed.IsClosed() 85 exitCh = r.isExit 86 select { 87 case <-exitCh: 88 isChClosed = true 89 default: 90 } 91 return 92 } 93 94 // rangeChWriteThread writes elements to r.ch until end or Close 95 // - typically rangeChWriteThread will block in channel send 96 // - infallible: runtime errors printed to standard error 97 func rangeChWriteThread[T any]( 98 tss *ThreadSafeSlice[T], 99 ch chan<- T, 100 isClosedCh cyclebreaker.AwaitableCh, 101 isExit chan struct{}, 102 ) { 103 var err error 104 defer cyclebreaker.Recover(func() cyclebreaker.DA { return cyclebreaker.A() }, &err, cyclebreaker.Infallible) 105 defer cyclebreaker.Closer(isExit, &err) 106 defer cyclebreaker.CloserSend(ch, &err) 107 108 var index int 109 for { 110 111 // get element to send 112 // - non-blocking 113 // - hasValue false when out of values 114 var element, hasValue = tss.Get(index) 115 if !hasValue { 116 return // end of items return 117 } 118 index++ 119 120 // write thread normally blocks here 121 select { 122 case ch <- element: 123 case <-isClosedCh: 124 return // [RangeCh.Close] invoked 125 } 126 } 127 }