github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"gopkg.in/juju/worker.v1"
    11  	"gopkg.in/juju/worker.v1/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(NewLock())
    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  			w := &gate{lock: lock}
    35  			w.tomb.Go(func() error {
    36  				<-w.tomb.Dying()
    37  				return nil
    38  			})
    39  			return w, nil
    40  		},
    41  		Output: func(in worker.Worker, out interface{}) error {
    42  			inWorker, _ := in.(*gate)
    43  			if inWorker == nil {
    44  				return errors.Errorf("in should be a *gate; is %#v", in)
    45  			}
    46  			switch outPointer := out.(type) {
    47  			case *Unlocker:
    48  				*outPointer = inWorker.lock
    49  			case *Waiter:
    50  				*outPointer = inWorker.lock
    51  			case *Lock:
    52  				*outPointer = inWorker.lock
    53  			default:
    54  				return errors.Errorf("out should be a *Unlocker, *Waiter, *Lock; is %#v", out)
    55  			}
    56  			return nil
    57  		},
    58  	}
    59  }
    60  
    61  // NewLock returns a new Lock for the gate manifold, suitable for
    62  // passing to ManifoldEx. It can be safely unlocked and monitored by
    63  // code running inside or outside of the dependency engine.
    64  func NewLock() Lock {
    65  	return &lock{
    66  		// mu and ch are shared across all workers started by the returned manifold.
    67  		// In normal operation, there will only be one such worker at a time; but if
    68  		// multiple workers somehow run in parallel, mu should prevent panic and/or
    69  		// confusion.
    70  		mu: new(sync.Mutex),
    71  		ch: make(chan struct{}),
    72  	}
    73  }
    74  
    75  // Lock implements of Unlocker and Waiter
    76  type lock struct {
    77  	mu *sync.Mutex
    78  	ch chan struct{}
    79  }
    80  
    81  // Unlock implements Unlocker.
    82  func (l *lock) Unlock() {
    83  	l.mu.Lock()
    84  	defer l.mu.Unlock()
    85  	select {
    86  	case <-l.ch:
    87  	default:
    88  		close(l.ch)
    89  	}
    90  }
    91  
    92  // Unlocked implements Waiter.
    93  func (l *lock) Unlocked() <-chan struct{} {
    94  	return l.ch
    95  }
    96  
    97  // IsUnlocked implements Waiter.
    98  func (l *lock) IsUnlocked() bool {
    99  	select {
   100  	case <-l.ch:
   101  		return true
   102  	default:
   103  		return false
   104  	}
   105  }
   106  
   107  // gate implements a degenerate worker that holds a Lock.
   108  type gate struct {
   109  	tomb tomb.Tomb
   110  	lock Lock
   111  }
   112  
   113  // Kill is part of the worker.Worker interface.
   114  func (w *gate) Kill() {
   115  	w.tomb.Kill(nil)
   116  }
   117  
   118  // Wait is part of the worker.Worker interface.
   119  func (w *gate) Wait() error {
   120  	return w.tomb.Wait()
   121  }