github.com/haraldrudell/parl@v0.4.176/perrors/errorglue/send-nb.go (about) 1 /* 2 © 2021–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package errorglue 7 8 import ( 9 "fmt" 10 "os" 11 "sync" 12 "sync/atomic" 13 ) 14 15 // SendNb implements non-blocking send using a thread and buffer up to size of int 16 type SendNb struct { 17 errCh chan error // may be nil: check using method HasChannel 18 19 sqLock sync.Mutex 20 sendQueue []error // inside lock 21 hasThread bool // inside lock 22 waitCh chan struct{} // before go statement 23 24 isShutdown atomic.Bool // atomic: check using method: IsShutdown 25 } 26 27 // NewSendNb returns a buffered Send object using errCh 28 // - errCh may be nil 29 func NewSendNb(errCh chan error) (sc *SendNb) { 30 return &SendNb{errCh: errCh} 31 } 32 33 // Send sends an error on the error channel. Non-blocking. Thread-safe. 34 // - if err is nil, nothing is done 35 // - if SendNb was not initialized with non-zero channel, nothing is done 36 func (sc *SendNb) Send(err error) { 37 if !sc.canSend(err) { 38 return // no-data not-initialized is-shutdown return 39 } 40 41 sc.sqLock.Lock() 42 defer sc.sqLock.Unlock() 43 44 if !sc.canSend(err) { 45 return // no-data not-initialized is-shutdown return 46 } 47 48 // put err in send queue 49 sc.sendQueue = append(sc.sendQueue, err) 50 if sc.hasThread { 51 return // thread is already reading from queue 52 } 53 54 // launch thread 55 sc.hasThread = true 56 sc.waitCh = make(chan struct{}) 57 go sc.sendThread() // send err in new thread 58 } 59 60 func (sc *SendNb) HasChannel() (hasChannel bool) { 61 return sc.errCh != nil 62 } 63 64 func (sc *SendNb) IsShutdown() (isShutdown bool) { 65 return sc.isShutdown.Load() 66 } 67 68 // Shutdown closes the channel exactly once. Thread-safe 69 func (sc *SendNb) Shutdown() { 70 if sc.isShutdown.Load() { 71 return // already shutdown 72 } 73 74 // ensure channel completes send 75 if sc.shutdown() { 76 select { 77 case <-sc.errCh: // release thread from send wait 78 <-sc.waitCh // wait for thread to exit 79 case <-sc.waitCh: // or wait until thread has exited 80 } 81 } 82 83 if sc.errCh != nil { 84 close(sc.errCh) 85 } 86 } 87 88 func (sc *SendNb) shutdown() (hasThread bool) { 89 sc.sqLock.Lock() 90 defer sc.sqLock.Unlock() 91 92 // block further Send invocation and thread sends 93 if sc.isShutdown.Swap(true) { 94 return // already shutdown 95 } 96 97 hasThread = sc.hasThread 98 99 // drop any send queue 100 sc.sendQueue = nil 101 102 return 103 } 104 105 func (sc *SendNb) canSend(err error) (canSend bool) { 106 return err != nil && // nothing to send 107 sc.errCh != nil && // not initialized to send errors 108 !sc.isShutdown.Load() // is shutdown 109 } 110 111 func (sc *SendNb) sendThread() { 112 defer sc.closeWaitCh() 113 defer RecoverThread("send on error channel", func(err error) { 114 fmt.Fprintln(os.Stderr, err) 115 }) 116 117 var hasValue bool 118 var err error 119 for { 120 if hasValue, err = sc.getErrFromSendQueue(); !hasValue { 121 return // queue empty return: exit thread 122 } 123 124 sc.errCh <- err 125 } 126 } 127 128 func (sc *SendNb) getErrFromSendQueue() (hasValue bool, err error) { 129 sc.sqLock.Lock() 130 defer sc.sqLock.Unlock() 131 132 if hasValue = len(sc.sendQueue) > 0; !hasValue { 133 sc.hasThread = false 134 return 135 } 136 137 err = sc.sendQueue[0] 138 copy(sc.sendQueue, sc.sendQueue[1:]) 139 lastIndex := len(sc.sendQueue) - 1 140 sc.sendQueue[lastIndex] = nil 141 sc.sendQueue = sc.sendQueue[:lastIndex] 142 return 143 } 144 145 func (sc *SendNb) closeWaitCh() { 146 sc.sqLock.Lock() 147 defer sc.sqLock.Unlock() 148 149 close(sc.waitCh) 150 sc.hasThread = false 151 }