@Override
  public <T> Future<T> invoke(
      EventLoop eventLoop,
      URI uri,
      ClientOptions options,
      ClientCodec codec,
      Method method,
      Object[] args)
      throws Exception {

    final CircuitBreaker circuitBreaker;
    try {
      circuitBreaker = mapping.get(eventLoop, uri, options, codec, method, args);
    } catch (Throwable t) {
      logger.warn("Failed to get a circuit breaker from mapping", t);
      return delegate().invoke(eventLoop, uri, options, codec, method, args);
    }

    if (circuitBreaker.canRequest()) {
      final Future<T> resultFut = delegate().invoke(eventLoop, uri, options, codec, method, args);
      resultFut.addListener(
          future -> {
            if (future.isSuccess()) {
              // reports success event
              circuitBreaker.onSuccess();
            } else {
              circuitBreaker.onFailure(future.cause());
            }
          });
      return resultFut;
    } else {
      // the circuit is tripped

      // prepares a failed resultPromise
      final Promise<T> resultPromise = eventLoop.newPromise();
      resultPromise.setFailure(new FailFastException(circuitBreaker));
      codec.prepareRequest(method, args, resultPromise);

      // returns immediately without calling succeeding remote invokers
      return resultPromise;
    }
  }