code: public void awaitForStatePredicate(Predicate<JobExecutionState> predicate, long timeoutMs)
           throws InterruptedException, TimeoutException {
      Preconditions.checkArgument(timeoutMs >= 0);
      if (0 == timeoutMs) {
        timeoutMs = Long.MAX_VALUE;
      }
  
      long startTimeMs = System.currentTimeMillis();
      long millisRemaining = timeoutMs;
      this.changeLock.lock();
      try {
        while (!predicate.apply(this) && millisRemaining > 0) {
          if (!this.runningStateChanged.await(millisRemaining, TimeUnit.MILLISECONDS)) {
            // Not necessary but shuts up FindBugs RV_RETURN_VALUE_IGNORED_BAD_PRACTICE
            break;
          }
          millisRemaining = timeoutMs - (System.currentTimeMillis() - startTimeMs);
        }
  
        if (!predicate.apply(this)) {
          throw new TimeoutException("Timeout waiting for state predicate: " + predicate);
        }
      }
      finally {
        this.changeLock.unlock();
      }
    }
docs: Waits till a predicate on {@link #getRunningState()} becomes true or timeout is reached.
  
  @param predicate               the predicate to evaluate. Note that even though the predicate
  is applied on the entire object, it will be evaluated only when
  the running state changes.
  @param timeoutMs               the number of milliseconds to wait for the predicate to become
  true; 0 means wait forever.
  @throws InterruptedException   if the waiting was interrupted
  @throws TimeoutException       if we reached the timeout before the predicate was satisfied.