github.com/glycerine/xcryptossh@v7.0.4+incompatible/halter.go (about) 1 package ssh 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 ) 8 9 // IdemCloseChan can have Close() called on it 10 // multiple times, and it will only close 11 // Chan once. 12 type IdemCloseChan struct { 13 Chan chan struct{} 14 closed bool 15 mut sync.Mutex 16 } 17 18 // Reinit re-allocates the Chan, assinging 19 // a new channel and reseting the state 20 // as if brand new. 21 func (c *IdemCloseChan) Reinit() { 22 c.mut.Lock() 23 defer c.mut.Unlock() 24 c.Chan = make(chan struct{}) 25 c.closed = false 26 } 27 28 // NewIdemCloseChan makes a new IdemCloseChan. 29 func NewIdemCloseChan() *IdemCloseChan { 30 return &IdemCloseChan{ 31 Chan: make(chan struct{}), 32 } 33 } 34 35 var ErrAlreadyClosed = fmt.Errorf("Chan already closed") 36 37 // Close returns ErrAlreadyClosed if it has been 38 // called before. It never closes IdemClose.Chan more 39 // than once, so it is safe to ignore the returned 40 // error value. Close() is safe for concurrent access by multiple 41 // goroutines. Close returns nil after the first time 42 // it is called. 43 func (c *IdemCloseChan) Close() error { 44 c.mut.Lock() 45 defer c.mut.Unlock() 46 if !c.closed { 47 close(c.Chan) 48 c.closed = true 49 return nil 50 } 51 return ErrAlreadyClosed 52 } 53 54 // IsClosed tells you if Chan is already closed or not. 55 func (c *IdemCloseChan) IsClosed() bool { 56 c.mut.Lock() 57 defer c.mut.Unlock() 58 return c.closed 59 } 60 61 // Halter helps shutdown a goroutine, and manage 62 // overall lifecycle of a resource. 63 type Halter struct { 64 65 // ready is closed when 66 // the resouce embedding the Halter is ready. 67 ready IdemCloseChan 68 69 // The owning goutine should call MarkDone() as its last 70 // actual once it has received the ReqStop() signal. 71 // Err, if any, should be set before Done is called. 72 done IdemCloseChan 73 74 // Other goroutines call RequestStop() in order 75 // to request that the owning goroutine stop immediately. 76 // The owning goroutine should select on ReqStopChan() 77 // in order to recognize shutdown requests. 78 reqStop IdemCloseChan 79 80 // Err represents the "return value" of the 81 // function launched in the goroutine. 82 // To avoid races, it should be read only 83 // after Done has been closed. Goroutine 84 // functions should SetErr() (if non nil) 85 // prior to calling MarkDone(). 86 err error 87 errmut sync.Mutex 88 89 upstream map[*Halter]*RunStatus // notify when done. 90 downstream map[*Halter]*RunStatus // send reqStop when we are reqStop 91 mut sync.Mutex 92 } 93 94 func (h *Halter) Err() (err error) { 95 h.errmut.Lock() 96 err = h.err 97 h.errmut.Unlock() 98 return 99 } 100 101 func (h *Halter) SetErr(err error) { 102 h.errmut.Lock() 103 h.err = err 104 h.errmut.Unlock() 105 } 106 107 func (h *Halter) addUpstream(u *Halter) { 108 h.mut.Lock() 109 h.upstream[u] = nil 110 h.mut.Unlock() 111 } 112 113 func (h *Halter) removeUpstream(u *Halter) { 114 h.mut.Lock() 115 delete(h.upstream, u) 116 h.mut.Unlock() 117 } 118 119 // AddDownstream is the public API. To 120 // prevent infinite loops, always use 121 // AddDownstream: it will automatically 122 // inform the d that h is now upstream. 123 // There is no need to call d.addUpstream(h), 124 // as AddDownstream will do that automatically. 125 // 126 func (h *Halter) AddDownstream(d *Halter) { 127 h.mut.Lock() 128 h.downstream[d] = nil 129 h.mut.Unlock() 130 d.addUpstream(h) 131 } 132 133 func (h *Halter) RemoveDownstream(d *Halter) { 134 h.mut.Lock() 135 delete(h.downstream, d) 136 h.mut.Unlock() 137 d.removeUpstream(h) 138 } 139 140 // RunStatus provides lifecycle snapshots. 141 type RunStatus struct { 142 143 // lifecycle 144 Ready bool 145 StopRequested bool 146 Done bool 147 148 // can be waited on for finish. 149 // Once closed, call Status() 150 // again to get any Err that 151 // was the cause/leftover. 152 DoneCh <-chan struct{} 153 154 // final error if any. 155 Err error 156 } 157 158 func (h *Halter) Status() (r *RunStatus) { 159 // don't hold locks here! 160 r = &RunStatus{} 161 r.Ready = h.ready.IsClosed() 162 r.StopRequested = h.reqStop.IsClosed() 163 r.Done = h.done.IsClosed() 164 if r.Done { 165 r.Err = h.Err() 166 } 167 r.DoneCh = h.done.Chan 168 return 169 } 170 171 func NewHalter() *Halter { 172 return &Halter{ 173 ready: *NewIdemCloseChan(), 174 done: *NewIdemCloseChan(), 175 reqStop: *NewIdemCloseChan(), 176 upstream: make(map[*Halter]*RunStatus), 177 downstream: make(map[*Halter]*RunStatus), 178 } 179 } 180 181 func (h *Halter) ReqStopChan() chan struct{} { 182 return h.reqStop.Chan 183 } 184 185 func (h *Halter) DoneChan() chan struct{} { 186 return h.done.Chan 187 } 188 189 func (h *Halter) ReadyChan() chan struct{} { 190 return h.ready.Chan 191 } 192 193 // RequestStop closes the h.ReqStop channel 194 // if it has not already done so. Safe for 195 // multiple goroutine access. 196 func (h *Halter) RequestStop() { 197 h.reqStop.Close() 198 199 // recursively tell dowstream 200 h.mut.Lock() 201 for d := range h.downstream { 202 d.RequestStop() 203 } 204 h.mut.Unlock() 205 } 206 207 func (h *Halter) waitForDownstreamDone() { 208 h.mut.Lock() 209 for d := range h.downstream { 210 select { 211 case <-d.DoneChan(): 212 d.removeUpstream(h) 213 //case <-time.After(10 * time.Second): 214 // panic(fmt.Sprintf("Halter.waitForDownsreamDone waited over 10 seconds. len=%v", len(h.downstream))) 215 } 216 } 217 h.mut.Unlock() 218 } 219 220 // MarkReady closes the h.ready channel 221 // if it has not already done so. Safe for 222 // multiple goroutine access. 223 func (h *Halter) MarkReady() { 224 h.ready.Close() 225 } 226 227 // MarkDone closes the h.DoneChan() channel 228 // if it has not already done so. Safe for 229 // multiple goroutine access. MarkDone 230 // returns only once all downstream 231 // Halters have called MarkDone. See 232 // MarkDoneNoBlock for an alternative. 233 // 234 func (h *Halter) MarkDone() { 235 h.RequestStop() 236 h.waitForDownstreamDone() 237 h.done.Close() 238 } 239 240 // MarkDoneNoBlock doesn't wait for 241 // downstream goroutines to be done 242 // before it returns. 243 func (h *Halter) MarkDoneNoBlock() { 244 h.RequestStop() 245 h.done.Close() 246 } 247 248 // IsStopRequested returns true iff h.ReqStop has been Closed(). 249 func (h *Halter) IsStopRequested() bool { 250 return h.reqStop.IsClosed() 251 } 252 253 // IsDone returns true iff h.Done has been Closed(). 254 func (h *Halter) IsDone() bool { 255 return h.done.IsClosed() 256 } 257 258 func (h *Halter) IsReady() bool { 259 return h.ready.IsClosed() 260 } 261 262 // MAD provides a link between context.Context 263 // and Halter. 264 // MAD stands for mutual assured destruction. 265 // When ctx is cancelled, then halt will be too. 266 // When halt is done, then cancelctx will be called. 267 func MAD(ctx context.Context, cancelctx context.CancelFunc, halt *Halter) { 268 go func() { 269 cchan := ctx.Done() 270 hchan1 := halt.reqStop.Chan 271 hchan2 := halt.done.Chan 272 cDone := false 273 hDone := false 274 for { 275 select { 276 case <-cchan: 277 halt.reqStop.Close() 278 halt.done.Close() 279 cDone = true 280 cchan = nil 281 case <-hchan1: 282 hDone = true 283 if cancelctx != nil { 284 cancelctx() 285 } 286 cancelctx = nil 287 hchan1 = nil 288 hchan2 = nil 289 case <-hchan2: 290 hDone = true 291 if cancelctx != nil { 292 cancelctx() 293 } 294 cancelctx = nil 295 hchan1 = nil 296 hchan2 = nil 297 } 298 if cDone && hDone { 299 return 300 } 301 } 302 }() 303 }