github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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  	"errors"
     8  	"strings"
     9  )
    10  
    11  var ErrShutdown = errors.New("connection is shut down")
    12  
    13  // Call represents an active RPC.
    14  type Call struct {
    15  	Request
    16  	Params   interface{}
    17  	Response interface{}
    18  	Error    error
    19  	Done     chan *Call
    20  }
    21  
    22  // RequestError represents an error returned from an RPC request.
    23  type RequestError struct {
    24  	Message string
    25  	Code    string
    26  }
    27  
    28  func (e *RequestError) Error() string {
    29  	m := "request error: " + e.Message
    30  	if e.Code != "" {
    31  		m += " (" + e.Code + ")"
    32  	}
    33  	return m
    34  }
    35  
    36  func (e *RequestError) ErrorCode() string {
    37  	return e.Code
    38  }
    39  
    40  func (conn *Conn) send(call *Call) {
    41  	conn.sending.Lock()
    42  	defer conn.sending.Unlock()
    43  
    44  	// Register this call.
    45  	conn.mutex.Lock()
    46  	if conn.dead == nil {
    47  		panic("rpc: call made when connection not started")
    48  	}
    49  	if conn.closing || conn.shutdown {
    50  		call.Error = ErrShutdown
    51  		conn.mutex.Unlock()
    52  		call.done()
    53  		return
    54  	}
    55  	conn.reqId++
    56  	reqId := conn.reqId
    57  	conn.clientPending[reqId] = call
    58  	conn.mutex.Unlock()
    59  
    60  	// Encode and send the request.
    61  	hdr := &Header{
    62  		RequestId: reqId,
    63  		Request:   call.Request,
    64  	}
    65  	params := call.Params
    66  	if params == nil {
    67  		params = struct{}{}
    68  	}
    69  	if conn.notifier != nil {
    70  		conn.notifier.ClientRequest(hdr, params)
    71  	}
    72  	if err := conn.codec.WriteMessage(hdr, params); err != nil {
    73  		conn.mutex.Lock()
    74  		call = conn.clientPending[reqId]
    75  		delete(conn.clientPending, reqId)
    76  		conn.mutex.Unlock()
    77  		if call != nil {
    78  			call.Error = err
    79  			call.done()
    80  		}
    81  	}
    82  }
    83  
    84  func (conn *Conn) handleResponse(hdr *Header) error {
    85  	reqId := hdr.RequestId
    86  	conn.mutex.Lock()
    87  	call := conn.clientPending[reqId]
    88  	delete(conn.clientPending, reqId)
    89  	conn.mutex.Unlock()
    90  
    91  	var err error
    92  	switch {
    93  	case call == nil:
    94  		// We've got no pending call. That usually means that
    95  		// WriteHeader partially failed, and call was already
    96  		// removed; response is a server telling us about an
    97  		// error reading request body. We should still attempt
    98  		// to read error body, but there's no one to give it to.
    99  		if conn.notifier != nil {
   100  			conn.notifier.ClientReply(Request{}, hdr, nil)
   101  		}
   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  		if conn.notifier != nil {
   117  			conn.notifier.ClientReply(call.Request, hdr, nil)
   118  		}
   119  		call.done()
   120  	default:
   121  		err = conn.readBody(call.Response, false)
   122  		if conn.notifier != nil {
   123  			conn.notifier.ClientReply(call.Request, hdr, call.Response)
   124  		}
   125  		call.done()
   126  	}
   127  	return err
   128  }
   129  
   130  func (call *Call) done() {
   131  	select {
   132  	case call.Done <- call:
   133  		// ok
   134  	default:
   135  		// We don't want to block here.  It is the caller's responsibility to make
   136  		// sure the channel has enough buffer space. See comment in Go().
   137  		logger.Errorf("discarding Call reply due to insufficient Done chan capacity")
   138  	}
   139  }
   140  
   141  // Call invokes the named action on the object of the given type with
   142  // the given id.  The returned values will be stored in response, which
   143  // should be a pointer.  If the action fails remotely, the returned
   144  // error will be of type RequestError.  The params value may be nil if
   145  // no parameters are provided; the response value may be nil to indicate
   146  // that any result should be discarded.
   147  func (conn *Conn) Call(req Request, params, response interface{}) error {
   148  	call := <-conn.Go(req, params, response, make(chan *Call, 1)).Done
   149  	return call.Error
   150  }
   151  
   152  // Go invokes the request asynchronously.  It returns the Call structure representing
   153  // the invocation.  The done channel will signal when the call is complete by returning
   154  // the same Call object.  If done is nil, Go will allocate a new channel.
   155  // If non-nil, done must be buffered or Go will deliberately panic.
   156  func (conn *Conn) Go(req Request, args, response interface{}, done chan *Call) *Call {
   157  	if done == nil {
   158  		done = make(chan *Call, 1)
   159  	} else {
   160  		// If caller passes done != nil, it must arrange that
   161  		// done has enough buffer for the number of simultaneous
   162  		// RPCs that will be using that channel.  If the channel
   163  		// is totally unbuffered, it's best not to run at all.
   164  		if cap(done) == 0 {
   165  			panic("launchpad.net/juju-core/rpc: done channel is unbuffered")
   166  		}
   167  	}
   168  	call := &Call{
   169  		Request:  req,
   170  		Params:   args,
   171  		Response: response,
   172  		Done:     done,
   173  	}
   174  	conn.send(call)
   175  	return call
   176  }