github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/hook/peeker.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package hook
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"gopkg.in/tomb.v1"
     9  
    10  	"github.com/juju/juju/state/watcher"
    11  )
    12  
    13  // Peeker maintains a Source, and allows an external client to inspect
    14  // and consume, or reject, the head of the queue.
    15  type Peeker interface {
    16  	// Peeks returns a channel on which Peeks are delivered. The receiver of a
    17  	// Peek must Consume or Reject the Peek before further peeks will be sent.
    18  	Peeks() <-chan Peek
    19  	// Stop stops the Peeker's Source, and prevents any further Peeks from
    20  	// being delivered.
    21  	Stop() error
    22  }
    23  
    24  // Peek exists to support Peeker and has no independent meaning or existence.
    25  // Receiving a Peek from a Peeker's Peek channel implies acceptance of the
    26  // responsibility to either Consume or Reject the Peek.
    27  type Peek interface {
    28  	// HookInfo returns information about the hook at the head of the queue.
    29  	HookInfo() Info
    30  	// Consume pops the hook from the head of the queue and makes new Peeks
    31  	// available.
    32  	Consume()
    33  	// Reject makes new Peeks available.
    34  	Reject()
    35  }
    36  
    37  // NewPeeker returns a new Peeker providing a view of the supplied source
    38  // (of which it takes ownership).
    39  func NewPeeker(source Source) Peeker {
    40  	p := &peeker{
    41  		peeks: make(chan Peek),
    42  	}
    43  	go func() {
    44  		defer p.tomb.Done()
    45  		defer close(p.peeks)
    46  		defer watcher.Stop(source, &p.tomb)
    47  		p.tomb.Kill(p.loop(source))
    48  	}()
    49  	return p
    50  }
    51  
    52  // peeker implements Peeker.
    53  type peeker struct {
    54  	tomb  tomb.Tomb
    55  	peeks chan Peek
    56  }
    57  
    58  // Peeks is part of the Peeker interface.
    59  func (p *peeker) Peeks() <-chan Peek {
    60  	return p.peeks
    61  }
    62  
    63  // Stop is part of the Peeker interface.
    64  func (p *peeker) Stop() error {
    65  	p.tomb.Kill(nil)
    66  	return p.tomb.Wait()
    67  }
    68  
    69  // loop delivers events from the source's Changes channel to its Update method,
    70  // continually, unless a Peek is active.
    71  func (p *peeker) loop(source Source) error {
    72  	for {
    73  		var next *peek
    74  		var peeks chan Peek
    75  		if !source.Empty() {
    76  			peeks = p.peeks
    77  			next = &peek{
    78  				source: source,
    79  				done:   make(chan struct{}),
    80  			}
    81  		}
    82  		select {
    83  		case <-p.tomb.Dying():
    84  			return tomb.ErrDying
    85  		case peeks <- next:
    86  			select {
    87  			case <-p.tomb.Dying():
    88  			case <-next.done:
    89  			}
    90  		case change, ok := <-source.Changes():
    91  			if !ok {
    92  				return errors.New("hook source stopped providing updates")
    93  			}
    94  			if err := change.Apply(); err != nil {
    95  				return errors.Trace(err)
    96  			}
    97  		}
    98  	}
    99  }
   100  
   101  // peek implements Peek.
   102  type peek struct {
   103  	source Source
   104  	done   chan struct{}
   105  }
   106  
   107  // HookInfo is part of the Peek interface.
   108  func (p *peek) HookInfo() Info {
   109  	return p.source.Next()
   110  }
   111  
   112  // Consume is part of the Peek interface.
   113  func (p *peek) Consume() {
   114  	p.source.Pop()
   115  	close(p.done)
   116  }
   117  
   118  // Reject is part of the Peek interface.
   119  func (p *peek) Reject() {
   120  	close(p.done)
   121  }