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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package fortress
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  
     9  	"github.com/juju/juju/worker"
    10  )
    11  
    12  // StartFunc starts a worker.Worker.
    13  type StartFunc func() (worker.Worker, error)
    14  
    15  // Occupy launches a Visit to fortress that creates a worker and holds the
    16  // visit open until the worker completes. Like most funcs that return any
    17  // Worker, the caller takes responsibility for its lifetime; be aware that
    18  // the responsibility is especially heavy here, because failure to clean up
    19  // the worker will block cleanup of the fortress.
    20  //
    21  // This may sound scary, but the alternative is to have multiple components
    22  // "responsible for" a single worker's lifetime -- and Fortress itself would
    23  // have to grow new concerns, of understanding and managing worker.Workers --
    24  // and that scenario ends up much worse.
    25  func Occupy(fortress Guest, start StartFunc, abort Abort) (worker.Worker, error) {
    26  
    27  	// Create two channels to communicate success and failure of worker
    28  	// creation; and a worker-running func that sends on exactly one
    29  	// of them, and returns only when (1) a value has been sent and (2)
    30  	// no worker is running. Note especially that it always returns nil.
    31  	started := make(chan worker.Worker, 1)
    32  	failed := make(chan error, 1)
    33  	task := func() error {
    34  		worker, err := start()
    35  		if err != nil {
    36  			failed <- err
    37  		} else {
    38  			started <- worker
    39  			worker.Wait() // ignore error: worker is SEP now.
    40  		}
    41  		return nil
    42  	}
    43  
    44  	// Start a goroutine to run the task func inside the fortress. If
    45  	// this operation succeeds, we must inspect started and failed to
    46  	// determine what actually happened; but if it fails, we can be
    47  	// confident that the task (which never fails) did not run, and can
    48  	// therefore return the failure without waiting further.
    49  	finished := make(chan error, 1)
    50  	go func() {
    51  		finished <- fortress.Visit(task, abort)
    52  	}()
    53  
    54  	// Watch all these channels to figure out what happened and inform
    55  	// the client. A nil error from finished indicates that there will
    56  	// be some value waiting on one of the other channels.
    57  	for {
    58  		select {
    59  		case err := <-finished:
    60  			if err != nil {
    61  				return nil, errors.Trace(err)
    62  			}
    63  		case err := <-failed:
    64  			return nil, errors.Trace(err)
    65  		case worker := <-started:
    66  			return worker, nil
    67  		}
    68  	}
    69  }