github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/fortress/fortress.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package fortress
     5  
     6  import (
     7  	"sync"
     8  
     9  	"github.com/juju/errors"
    10  	"launchpad.net/tomb"
    11  )
    12  
    13  // fortress coordinates between clients that access it as a Guard and as a Guest.
    14  type fortress struct {
    15  	tomb         tomb.Tomb
    16  	guardTickets chan guardTicket
    17  	guestTickets chan guestTicket
    18  }
    19  
    20  // newFortress returns a new, locked, fortress. The caller is responsible for
    21  // ensuring it somehow gets Kill()ed, and for handling any error returned by
    22  // Wait().
    23  func newFortress() *fortress {
    24  	f := &fortress{
    25  		guardTickets: make(chan guardTicket),
    26  		guestTickets: make(chan guestTicket),
    27  	}
    28  	go func() {
    29  		defer f.tomb.Done()
    30  		f.tomb.Kill(f.loop())
    31  	}()
    32  	return f
    33  }
    34  
    35  // Kill is part of the worker.Worker interface.
    36  func (f *fortress) Kill() {
    37  	f.tomb.Kill(nil)
    38  }
    39  
    40  // Wait is part of the worker.Worker interface.
    41  func (f *fortress) Wait() error {
    42  	return f.tomb.Wait()
    43  }
    44  
    45  // Unlock is part of the Guard interface.
    46  func (f *fortress) Unlock() error {
    47  	return f.allowGuests(true, nil)
    48  }
    49  
    50  // Lockdown is part of the Guard interface.
    51  func (f *fortress) Lockdown(abort Abort) error {
    52  	return f.allowGuests(false, abort)
    53  }
    54  
    55  // Visit is part of the Guest interface.
    56  func (f *fortress) Visit(visit Visit, abort Abort) error {
    57  	result := make(chan error)
    58  	select {
    59  	case <-f.tomb.Dying():
    60  		return errors.New("fortress worker shutting down")
    61  	case <-abort:
    62  		return ErrAborted
    63  	case f.guestTickets <- guestTicket{visit, result}:
    64  		return <-result
    65  	}
    66  }
    67  
    68  // allowGuests communicates Guard-interface requests to the main loop.
    69  func (f *fortress) allowGuests(allowGuests bool, abort Abort) error {
    70  	result := make(chan error)
    71  	select {
    72  	case <-f.tomb.Dying():
    73  		return errors.New("fortress worker shutting down")
    74  	case f.guardTickets <- guardTicket{allowGuests, abort, result}:
    75  		return <-result
    76  	}
    77  }
    78  
    79  // loop waits for a Guard to unlock the fortress, and then runs visit funcs in
    80  // parallel until a Guard locks it down again; at which point, it waits for all
    81  // outstanding visits to complete, and reverts to its original state.
    82  func (f *fortress) loop() error {
    83  	var active sync.WaitGroup
    84  	defer active.Wait()
    85  
    86  	// guestTickets will be set on Unlock and cleared at the start of Lockdown.
    87  	var guestTickets <-chan guestTicket
    88  	for {
    89  		select {
    90  		case <-f.tomb.Dying():
    91  			return tomb.ErrDying
    92  		case ticket := <-guestTickets:
    93  			active.Add(1)
    94  			go ticket.complete(active.Done)
    95  		case ticket := <-f.guardTickets:
    96  			// guard ticket requests are idempotent; it's not worth building
    97  			// the extra mechanism needed to (1) complain about abuse but
    98  			// (2) remain comprehensible and functional in the face of aborted
    99  			// Lockdowns.
   100  			if ticket.allowGuests {
   101  				guestTickets = f.guestTickets
   102  			} else {
   103  				guestTickets = nil
   104  			}
   105  			go ticket.complete(active.Wait)
   106  		}
   107  	}
   108  }
   109  
   110  // guardTicket communicates between the Guard interface and the main loop.
   111  type guardTicket struct {
   112  	allowGuests bool
   113  	abort       Abort
   114  	result      chan<- error
   115  }
   116  
   117  // complete unconditionally sends a single value on ticket.result; either nil
   118  // (when the desired state is reached) or ErrAborted (when the ticket's Abort
   119  // is closed). It should be called on its own goroutine.
   120  func (ticket guardTicket) complete(waitLockedDown func()) {
   121  	var result error
   122  	defer func() {
   123  		ticket.result <- result
   124  	}()
   125  
   126  	done := make(chan struct{})
   127  	go func() {
   128  		// If we're locking down, we should wait for all Visits to complete.
   129  		// If not, Visits are already being accepted and we're already done.
   130  		if !ticket.allowGuests {
   131  			waitLockedDown()
   132  		}
   133  		close(done)
   134  	}()
   135  	select {
   136  	case <-done:
   137  	case <-ticket.abort:
   138  		result = ErrAborted
   139  	}
   140  }
   141  
   142  // guestTicket communicates between the Guest interface and the main loop.
   143  type guestTicket struct {
   144  	visit  Visit
   145  	result chan<- error
   146  }
   147  
   148  // complete unconditionally sends any error returned from the Visit func, then
   149  // calls the finished func. It should be called on its own goroutine.
   150  func (ticket guestTicket) complete(finished func()) {
   151  	defer finished()
   152  	ticket.result <- ticket.visit()
   153  }