public synchronized RspList<Object> getResponseList() throws Exception {
   long expectedEndTime = timeService.expectedEndTime(timeout, MILLISECONDS);
   long waitingTime;
   while (expectedResponses > 0
       && retval == null
       && (waitingTime = timeService.remainingTime(expectedEndTime, MILLISECONDS)) > 0) {
     try {
       this.wait(waitingTime);
     } catch (InterruptedException e) {
       // reset interruption flag
       Thread.currentThread().interrupt();
       expectedResponses = -1;
     }
   }
   // Now we either have the response we need or aren't expecting any more responses - or have
   // run out of time.
   if (retval != null) return retval;
   else if (exception != null) throw exception;
   else if (expectedResponses == 0)
     throw new RpcException(
         format(
             "No more valid responses.  Received invalid responses from all of %s",
             futures.values()));
   else
     throw new TimeoutException(
         format(
             "Timed out waiting for %s for valid responses from any of %s.",
             Util.prettyPrintTime(timeout), futures.values()));
 }
    @Override
    @SuppressWarnings("unchecked")
    public synchronized void futureDone(Future<Object> objectFuture) {
      SenderContainer sc = futures.get(objectFuture);
      if (sc.processed) {
        // This can happen - it is a race condition in JGroups' NotifyingFuture.setListener() where
        // a listener
        // could be notified twice.
        if (trace)
          log.tracef(
              "Not processing callback; already processed callback for sender %s", sc.address);
      } else {
        sc.processed = true;
        Address sender = sc.address;
        boolean done = false;
        try {
          if (retval == null) {
            Object response = objectFuture.get();
            if (trace) log.tracef("Received response: %s from %s", response, sender);
            filter.isAcceptable(response, sender);
            if (!filter.needMoreResponses()) {
              retval = new RspList(Collections.singleton(new Rsp(sender, response)));
              done = true;
              // TODO cancel other tasks?
            }
          } else {
            if (trace)
              log.tracef(
                  "Skipping response from %s since a valid response for this request has already been received",
                  sender);
          }
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
        } catch (ExecutionException e) {
          Throwable cause = e.getCause();
          if (cause instanceof org.jgroups.SuspectedException) {
            // Do not set the exception field, RpcException should be thrown if there is no other
            // valid response
            return;
          } else if (cause instanceof org.jgroups.TimeoutException) {
            exception = new TimeoutException("Timeout!", e);
          } else if (cause instanceof Exception) {
            exception = (Exception) cause;
          } else {
            exception = new CacheException("Caught a throwable", cause);
          }

          if (log.isDebugEnabled())
            log.debugf("Caught exception from sender %s: %s", sender, exception);
        } finally {
          expectedResponses--;
          if (expectedResponses == 0 || done) {
            this.notify(); // make sure to awake waiting thread, but avoid unnecessary wakeups!
          }
        }
      }
    }