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