public synchronized RspList<Object> getResponseList() throws Exception {
   while (expectedResponses > 0 && retval == null) {
     try {
       this.wait(timeout);
     } 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) {
          exception = e;
          if (e.getCause() instanceof org.jgroups.TimeoutException)
            exception = new TimeoutException("Timeout!", e);
          else if (e.getCause() instanceof Exception) exception = (Exception) e.getCause();
          else exception = new CacheException("Caught a throwable", e.getCause());

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