launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/uniter/runlistener.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // The run listener is a worker go-routine that listens on either a unix
     5  // socket or a tcp connection for juju-run commands.
     6  
     7  package uniter
     8  
     9  import (
    10  	"net"
    11  	"net/rpc"
    12  	"sync"
    13  
    14  	"launchpad.net/juju-core/utils/exec"
    15  )
    16  
    17  const JujuRunEndpoint = "JujuRunServer.RunCommands"
    18  
    19  // A CommandRunner is something that will actually execute the commands and
    20  // return the results of that execution in the exec.ExecResponse (which
    21  // contains stdout, stderr, and return code).
    22  type CommandRunner interface {
    23  	RunCommands(commands string) (results *exec.ExecResponse, err error)
    24  }
    25  
    26  // RunListener is responsible for listening on the network connection and
    27  // seting up the rpc server on that net connection. Also starts the go routine
    28  // that listens and hands off the work.
    29  type RunListener struct {
    30  	listener net.Listener
    31  	server   *rpc.Server
    32  	closed   chan struct{}
    33  	closing  chan struct{}
    34  	wg       sync.WaitGroup
    35  }
    36  
    37  // The JujuRunServer is the entity that has the methods that are called over
    38  // the rpc connection.
    39  type JujuRunServer struct {
    40  	runner CommandRunner
    41  }
    42  
    43  // RunCommands delegates the actual running to the runner and populates the
    44  // response structure.
    45  func (r *JujuRunServer) RunCommands(commands string, result *exec.ExecResponse) error {
    46  	logger.Debugf("RunCommands: %q", commands)
    47  	runResult, err := r.runner.RunCommands(commands)
    48  	*result = *runResult
    49  	return err
    50  }
    51  
    52  // NewRunListener returns a new RunListener that is listening on the network
    53  // type and address passed in. If a valid RunListener is returned, is has the
    54  // go routine running, and should be closed by the creator when they are done
    55  // with it.
    56  func NewRunListener(runner CommandRunner, netType, localAddr string) (*RunListener, error) {
    57  	server := rpc.NewServer()
    58  	if err := server.Register(&JujuRunServer{runner}); err != nil {
    59  		return nil, mask(err)
    60  	}
    61  	listener, err := net.Listen(netType, localAddr)
    62  	if err != nil {
    63  		logger.Errorf("failed to listen on %s %s: %v", netType, localAddr, err)
    64  		return nil, err
    65  	}
    66  	runListener := &RunListener{
    67  		listener: listener,
    68  		server:   server,
    69  		closed:   make(chan struct{}),
    70  		closing:  make(chan struct{}),
    71  	}
    72  	go runListener.Run()
    73  	return runListener, nil
    74  }
    75  
    76  // Run accepts new connections until it encounters an error, or until Close is
    77  // called, and then blocks until all existing connections have been closed.
    78  func (s *RunListener) Run() (err error) {
    79  	logger.Debugf("juju-run listener running")
    80  	var conn net.Conn
    81  	for {
    82  		conn, err = s.listener.Accept()
    83  		if err != nil {
    84  			break
    85  		}
    86  		s.wg.Add(1)
    87  		go func(conn net.Conn) {
    88  			s.server.ServeConn(conn)
    89  			s.wg.Done()
    90  		}(conn)
    91  	}
    92  	logger.Debugf("juju-run listener stopping")
    93  	select {
    94  	case <-s.closing:
    95  		// Someone has called Close(), so it is overwhelmingly likely that
    96  		// the error from Accept is a direct result of the Listener being
    97  		// closed, and can therefore be safely ignored.
    98  		err = nil
    99  	default:
   100  	}
   101  	s.wg.Wait()
   102  	close(s.closed)
   103  	return
   104  }
   105  
   106  // Close immediately stops accepting connections, and blocks until all existing
   107  // connections have been closed.
   108  func (s *RunListener) Close() {
   109  	close(s.closing)
   110  	s.listener.Close()
   111  	<-s.closed
   112  	logger.Debugf("juju-run listener stopped")
   113  }