github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/rpc/client.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package rpc
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/errors"
    10  )
    11  
    12  var ErrShutdown = errors.New("connection is shut down")
    13  
    14  func IsShutdownErr(err error) bool {
    15  	return errors.Cause(err) == ErrShutdown
    16  }
    17  
    18  // Call represents an active RPC.
    19  type Call struct {
    20  	Request
    21  	Params   interface{}
    22  	Response interface{}
    23  	Error    error
    24  	Done     chan *Call
    25  }
    26  
    27  // RequestError represents an error returned from an RPC request.
    28  type RequestError struct {
    29  	Message string
    30  	Code    string
    31  }
    32  
    33  func (e *RequestError) Error() string {
    34  	if e.Code != "" {
    35  		return e.Message + " (" + e.Code + ")"
    36  	}
    37  	return e.Message
    38  }
    39  
    40  func (e *RequestError) ErrorCode() string {
    41  	return e.Code
    42  }
    43  
    44  func (conn *Conn) send(call *Call) {
    45  	conn.sending.Lock()
    46  	defer conn.sending.Unlock()
    47  
    48  	// Register this call.
    49  	conn.mutex.Lock()
    50  	if conn.dead == nil {
    51  		panic("rpc: call made when connection not started")
    52  	}
    53  	if conn.closing || conn.shutdown {
    54  		call.Error = ErrShutdown
    55  		conn.mutex.Unlock()
    56  		call.done()
    57  		return
    58  	}
    59  	conn.reqId++
    60  	reqId := conn.reqId
    61  	conn.clientPending[reqId] = call
    62  	conn.mutex.Unlock()
    63  
    64  	// Encode and send the request.
    65  	hdr := &Header{
    66  		RequestId: reqId,
    67  		Request:   call.Request,
    68  		Version:   1,
    69  	}
    70  	params := call.Params
    71  	if params == nil {
    72  		params = struct{}{}
    73  	}
    74  
    75  	if err := conn.codec.WriteMessage(hdr, params); err != nil {
    76  		conn.mutex.Lock()
    77  		call = conn.clientPending[reqId]
    78  		delete(conn.clientPending, reqId)
    79  		conn.mutex.Unlock()
    80  		if call != nil {
    81  			call.Error = err
    82  			call.done()
    83  		}
    84  	}
    85  }
    86  
    87  func (conn *Conn) handleResponse(hdr *Header) error {
    88  	reqId := hdr.RequestId
    89  	conn.mutex.Lock()
    90  	call := conn.clientPending[reqId]
    91  	delete(conn.clientPending, reqId)
    92  	conn.mutex.Unlock()
    93  
    94  	var err error
    95  	switch {
    96  	case call == nil:
    97  		// We've got no pending call. That usually means that
    98  		// WriteHeader partially failed, and call was already
    99  		// removed; response is a server telling us about an
   100  		// error reading request body. We should still attempt
   101  		// to read error body, but there's no one to give it to.
   102  		err = conn.readBody(nil, false)
   103  	case hdr.Error != "":
   104  		// Report rpcreflect.NoSuchMethodError with CodeNotImplemented.
   105  		if strings.HasPrefix(hdr.Error, "no such request ") && hdr.ErrorCode == "" {
   106  			hdr.ErrorCode = codeNotImplemented
   107  		}
   108  		// We've got an error response. Give this to the request;
   109  		// any subsequent requests will get the ReadResponseBody
   110  		// error if there is one.
   111  		call.Error = &RequestError{
   112  			Message: hdr.Error,
   113  			Code:    hdr.ErrorCode,
   114  		}
   115  		err = conn.readBody(nil, false)
   116  		call.done()
   117  	default:
   118  		err = conn.readBody(call.Response, false)
   119  		call.done()
   120  	}
   121  	return errors.Annotate(err, "error handling response")
   122  }
   123  
   124  func (call *Call) done() {
   125  	select {
   126  	case call.Done <- call:
   127  		// ok
   128  	default:
   129  		// We don't want to block here.  It is the caller's responsibility to make
   130  		// sure the channel has enough buffer space. See comment in Go().
   131  		logger.Errorf("discarding Call reply due to insufficient Done chan capacity")
   132  	}
   133  }
   134  
   135  // Call invokes the named action on the object of the given type with the given
   136  // id. The returned values will be stored in response, which should be a pointer.
   137  // If the action fails remotely, the error will have a cause of type RequestError.
   138  // The params value may be nil if no parameters are provided; the response value
   139  // may be nil to indicate that any result should be discarded.
   140  func (conn *Conn) Call(req Request, params, response interface{}) error {
   141  	call := &Call{
   142  		Request:  req,
   143  		Params:   params,
   144  		Response: response,
   145  		Done:     make(chan *Call, 1),
   146  	}
   147  	conn.send(call)
   148  	result := <-call.Done
   149  	return errors.Trace(result.Error)
   150  }