github.com/blend/go-sdk@v1.20220411.3/async/latch.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package async 9 10 import ( 11 "sync" 12 "sync/atomic" 13 ) 14 15 // NewLatch creates a new latch. 16 func NewLatch() *Latch { 17 l := new(Latch) 18 l.Reset() 19 return l 20 } 21 22 /* 23 Latch is a helper to coordinate goroutine lifecycles, specifically waiting for goroutines to start and end. 24 25 The lifecycle is generally as follows: 26 27 0 - stopped - goto 1 28 1 - starting - goto 2 29 2 - started - goto 3 30 3 - stopping - goto 0 31 32 Control flow is coordinated with chan struct{}, which acts as a semaphore but can only 33 alert (1) listener as it is buffered. 34 35 In order to start a `stopped` latch, you must call `.Reset()` first to initialize channels. 36 */ 37 type Latch struct { 38 sync.Mutex 39 40 state int32 41 42 starting chan struct{} 43 started chan struct{} 44 stopping chan struct{} 45 stopped chan struct{} 46 } 47 48 // Reset resets the latch. 49 func (l *Latch) Reset() { 50 l.Lock() 51 atomic.StoreInt32(&l.state, LatchStopped) 52 l.starting = make(chan struct{}, 1) 53 l.started = make(chan struct{}, 1) 54 l.stopping = make(chan struct{}, 1) 55 l.stopped = make(chan struct{}, 1) 56 l.Unlock() 57 } 58 59 // CanStart returns if the latch can start. 60 func (l *Latch) CanStart() bool { 61 return atomic.LoadInt32(&l.state) == LatchStopped 62 } 63 64 // CanStop returns if the latch can stop. 65 func (l *Latch) CanStop() bool { 66 return atomic.LoadInt32(&l.state) == LatchStarted 67 } 68 69 // IsStarting returns if the latch state is LatchStarting 70 func (l *Latch) IsStarting() bool { 71 return atomic.LoadInt32(&l.state) == LatchStarting 72 } 73 74 // IsStarted returns if the latch state is LatchStarted. 75 func (l *Latch) IsStarted() bool { 76 return atomic.LoadInt32(&l.state) == LatchStarted 77 } 78 79 // IsStopping returns if the latch state is LatchStopping. 80 func (l *Latch) IsStopping() bool { 81 return atomic.LoadInt32(&l.state) == LatchStopping 82 } 83 84 // IsStopped returns if the latch state is LatchStopped. 85 func (l *Latch) IsStopped() (isStopped bool) { 86 return atomic.LoadInt32(&l.state) == LatchStopped 87 } 88 89 // NotifyStarting returns the starting signal. 90 // It is used to coordinate the transition from stopped -> starting. 91 // There can only be (1) effective listener at a time for these events. 92 func (l *Latch) NotifyStarting() (notifyStarting <-chan struct{}) { 93 l.Lock() 94 notifyStarting = l.starting 95 l.Unlock() 96 return 97 } 98 99 // NotifyStarted returns the started signal. 100 // It is used to coordinate the transition from starting -> started. 101 // There can only be (1) effective listener at a time for these events. 102 func (l *Latch) NotifyStarted() (notifyStarted <-chan struct{}) { 103 l.Lock() 104 notifyStarted = l.started 105 l.Unlock() 106 return 107 } 108 109 // NotifyStopping returns the should stop signal. 110 // It is used to trigger the transition from running -> stopping -> stopped. 111 // There can only be (1) effective listener at a time for these events. 112 func (l *Latch) NotifyStopping() (notifyStopping <-chan struct{}) { 113 l.Lock() 114 notifyStopping = l.stopping 115 l.Unlock() 116 return 117 } 118 119 // NotifyStopped returns the stopped signal. 120 // It is used to coordinate the transition from stopping -> stopped. 121 // There can only be (1) effective listener at a time for these events. 122 func (l *Latch) NotifyStopped() (notifyStopped <-chan struct{}) { 123 l.Lock() 124 notifyStopped = l.stopped 125 l.Unlock() 126 return 127 } 128 129 // Starting signals the latch is starting. 130 // This is typically done before you kick off a goroutine. 131 func (l *Latch) Starting() { 132 if l.IsStarting() { 133 return 134 } 135 atomic.StoreInt32(&l.state, LatchStarting) 136 l.starting <- struct{}{} 137 } 138 139 // Started signals that the latch is started and has entered 140 // the `IsStarted` state. 141 func (l *Latch) Started() { 142 if l.IsStarted() { 143 return 144 } 145 atomic.StoreInt32(&l.state, LatchStarted) 146 l.started <- struct{}{} 147 } 148 149 // Stopping signals the latch to stop. 150 // It could also be thought of as `SignalStopping`. 151 func (l *Latch) Stopping() { 152 if l.IsStopping() { 153 return 154 } 155 atomic.StoreInt32(&l.state, LatchStopping) 156 l.stopping <- struct{}{} 157 } 158 159 // Stopped signals the latch has stopped. 160 func (l *Latch) Stopped() { 161 if l.IsStopped() { 162 return 163 } 164 atomic.StoreInt32(&l.state, LatchStopped) 165 l.stopped <- struct{}{} 166 } 167 168 // WaitStarted triggers `Starting` and waits for the `Started` signal. 169 func (l *Latch) WaitStarted() { 170 if !l.CanStart() { 171 return 172 } 173 started := l.NotifyStarted() 174 l.Starting() 175 <-started 176 } 177 178 // WaitStopped triggers `Stopping` and waits for the `Stopped` signal. 179 func (l *Latch) WaitStopped() { 180 if !l.CanStop() { 181 return 182 } 183 stopped := l.NotifyStopped() 184 l.Stopping() 185 <-stopped 186 }