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 }