// Push sends a request where we do not wish to wait for a REP (but is still REQ/REP pattern) func (c *client) Push(req *Request) error { return raven.SendRequest(req, c.instanceID) }
// doReq sends a request, with timeout options and retries, waits for response and returns it func (c *client) doReq(req *Request, options ...Options) (*Response, errors.Error) { if circuitbreaker.Open(req.service, req.endpoint) { inst.Counter(1.0, fmt.Sprintf("client.error.%s.%s.circuitbroken", req.service, req.endpoint), 1) log.Warnf("Broken Circuit for %s.%s", req.service, req.endpoint) return nil, errors.CircuitBroken("com.hailocab.kernel.platform.circuitbreaker", "Circuit is open") } retries := c.defaults["retries"].(int) var timeout time.Duration timeoutSupplied := false if len(options) == 1 { if _, ok := options[0]["retries"]; ok { retries = options[0]["retries"].(int) } if _, ok := options[0]["timeout"]; ok { timeout = options[0]["timeout"].(time.Duration) timeoutSupplied = true } } // setup the response channel rc := make(chan *Response, retries) c.responses.add(req, rc) defer c.responses.removeByRequest(req) instPrefix := fmt.Sprintf("client.%s.%s", req.service, req.endpoint) tAllRetries := time.Now() for i := 1; i <= retries+1; i++ { t := time.Now() c.RLock() con := c.listening c.RUnlock() if !con { log.Debug("[Client] not yet listening, establishing now...") ch := make(chan bool) go c.listen(ch) if online := <-ch; !online { log.Error("[Client] Listener failed") inst.Timing(1.0, fmt.Sprintf("%s.error", instPrefix), time.Since(t)) inst.Counter(1.0, "client.error.com.hailocab.kernel.platform.client.listenfail", 1) return nil, errors.InternalServerError("com.hailocab.kernel.platform.client.listenfail", "Listener failed") } log.Info("[Client] Listener online") } // figure out what timeout to use if !timeoutSupplied { timeout = c.timeout.Get(req.service, req.endpoint, i) } log.Tracef("[Client] Sync request attempt %d for %s using timeout %v", i, req.MessageID(), timeout) // only bother sending the request if we are listening, otherwise allow to timeout if err := raven.SendRequest(req, c.instanceID); err != nil { log.Errorf("[Client] Failed to send request: %v", err) } select { case payload := <-rc: if payload.IsError() { inst.Timing(1.0, fmt.Sprintf("%s.error", instPrefix), time.Since(t)) errorProto := &pe.PlatformError{} if err := payload.Unmarshal(errorProto); err != nil { inst.Counter(1.0, "client.error.com.hailocab.kernel.platform.badresponse", 1) return nil, errors.BadResponse("com.hailocab.kernel.platform.badresponse", err.Error()) } err := errors.FromProtobuf(errorProto) inst.Counter(1.0, fmt.Sprintf("client.error.%s", err.Code()), 1) circuitbreaker.Result(req.service, req.endpoint, err) return nil, err } inst.Timing(1.0, fmt.Sprintf("%s.success", instPrefix), time.Since(t)) circuitbreaker.Result(req.service, req.endpoint, nil) return payload, nil case <-time.After(timeout): // timeout log.Errorf("[Client] Timeout talking to %s.%s after %v for %s", req.Service(), req.Endpoint(), timeout, req.MessageID()) inst.Timing(1.0, fmt.Sprintf("%s.error", instPrefix), time.Since(t)) c.traceAttemptTimeout(req, i, timeout) circuitbreaker.Result(req.service, req.endpoint, errors.Timeout("com.hailocab.kernel.platform.timeout", fmt.Sprintf("Request timed out talking to %s.%s from %s (most recent timeout %v)", req.Service(), req.Endpoint(), req.From(), timeout), req.Service(), req.Endpoint())) } } inst.Timing(1.0, fmt.Sprintf("%s.error.timedOut", instPrefix), time.Since(tAllRetries)) inst.Counter(1.0, "client.error.com.hailocab.kernel.platform.timeout", 1) return nil, errors.Timeout( "com.hailocab.kernel.platform.timeout", fmt.Sprintf("Request timed out talking to %s.%s from %s (most recent timeout %v)", req.Service(), req.Endpoint(), req.From(), timeout), req.Service(), req.Endpoint(), ) }