github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/gate/manifold.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package gate 5 6 import ( 7 "sync" 8 9 "github.com/juju/errors" 10 "github.com/juju/worker/v3" 11 "github.com/juju/worker/v3/dependency" 12 "gopkg.in/tomb.v2" 13 ) 14 15 // Manifold returns a dependency.Manifold that wraps a single channel, shared 16 // across all workers returned by the start func; it can be used to synchronize 17 // operations across manifolds that lack direct dependency relationships. 18 // 19 // The output func accepts an out pointer to either an Unlocker or a Waiter. 20 func Manifold() dependency.Manifold { 21 return ManifoldEx(nil) 22 } 23 24 // ManifoldEx does the same thing as Manifold but takes the 25 // Lock which used to wait on or unlock the gate. This 26 // allows code running outside of a dependency engine managed worker 27 // to monitor or unlock the gate. 28 // 29 // TODO(mjs) - this can likely go away once all machine agent workers 30 // are running inside the dependency engine. 31 func ManifoldEx(lock Lock) dependency.Manifold { 32 return dependency.Manifold{ 33 Start: func(_ dependency.Context) (worker.Worker, error) { 34 // Need to assign a copy of the arg so we don't 35 // modify the variable in the closure when we get 36 // called a second time. 37 wLock := lock 38 if wLock == nil { 39 wLock = NewLock() 40 } 41 w := &gate{lock: wLock} 42 w.tomb.Go(func() error { 43 <-w.tomb.Dying() 44 return nil 45 }) 46 return w, nil 47 }, 48 Output: func(in worker.Worker, out interface{}) error { 49 inWorker, _ := in.(*gate) 50 if inWorker == nil { 51 return errors.Errorf("in should be a *gate; is %#v", in) 52 } 53 switch outPointer := out.(type) { 54 case *Unlocker: 55 *outPointer = inWorker.lock 56 case *Waiter: 57 *outPointer = inWorker.lock 58 case *Lock: 59 *outPointer = inWorker.lock 60 default: 61 return errors.Errorf("out should be a *Unlocker, *Waiter, *Lock; is %#v", out) 62 } 63 return nil 64 }, 65 } 66 } 67 68 // NewLock returns a new Lock for the gate manifold, suitable for 69 // passing to ManifoldEx. It can be safely unlocked and monitored by 70 // code running inside or outside of the dependency engine. 71 func NewLock() Lock { 72 return &lock{ 73 // mu and ch are shared across all workers started by the returned manifold. 74 // In normal operation, there will only be one such worker at a time; but if 75 // multiple workers somehow run in parallel, mu should prevent panic and/or 76 // confusion. 77 mu: new(sync.Mutex), 78 ch: make(chan struct{}), 79 } 80 } 81 82 // Lock implements of Unlocker and Waiter 83 type lock struct { 84 mu *sync.Mutex 85 ch chan struct{} 86 } 87 88 // Unlock implements Unlocker. 89 func (l *lock) Unlock() { 90 l.mu.Lock() 91 defer l.mu.Unlock() 92 select { 93 case <-l.ch: 94 default: 95 close(l.ch) 96 } 97 } 98 99 // Unlocked implements Waiter. 100 func (l *lock) Unlocked() <-chan struct{} { 101 return l.ch 102 } 103 104 // IsUnlocked implements Waiter. 105 func (l *lock) IsUnlocked() bool { 106 select { 107 case <-l.ch: 108 return true 109 default: 110 return false 111 } 112 } 113 114 // gate implements a degenerate worker that holds a Lock. 115 type gate struct { 116 tomb tomb.Tomb 117 lock Lock 118 } 119 120 // Kill is part of the worker.Worker interface. 121 func (w *gate) Kill() { 122 w.tomb.Kill(nil) 123 } 124 125 // Wait is part of the worker.Worker interface. 126 func (w *gate) Wait() error { 127 return w.tomb.Wait() 128 }