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  }