code: public PermitsAndDelay getPermitsAndDelay(long requestedPermits, long minPermits, long timeoutMillis) {
      try {
        long storedTokens = this.tokenBucket.getStoredTokens();
  
        long eagerTokens = storedTokens / 2;
        if (eagerTokens > requestedPermits && this.tokenBucket.getTokens(eagerTokens, 0, TimeUnit.MILLISECONDS)) {
          return new PermitsAndDelay(eagerTokens, 0, true);
        }
  
        long millisToSatisfyMinPermits = (long) (minPermits / this.tokenBucket.getTokensPerMilli());
        if (millisToSatisfyMinPermits > timeoutMillis) {
          return new PermitsAndDelay(0, 0, false);
        }
        long allowedTimeout = Math.min(millisToSatisfyMinPermits + this.baseTimeout, timeoutMillis);
  
        while (requestedPermits > minPermits) {
          long wait = this.tokenBucket.tryReserveTokens(requestedPermits, allowedTimeout);
          if (wait >= 0) {
            return new PermitsAndDelay(requestedPermits, wait, true);
          }
          requestedPermits /= 2;
        }
  
        long wait = this.tokenBucket.tryReserveTokens(minPermits, allowedTimeout);
        if (wait >= 0) {
          return new PermitsAndDelay(requestedPermits, wait, true);
        }
  
      } catch (InterruptedException ie) {
        // Fallback to returning 0
      }
  
      return new PermitsAndDelay(0, 0, true);
    }
docs: Request tokens.
  @param requestedPermits the ideal number of tokens to acquire.
  @param minPermits the minimum number of tokens useful for the calling process. If this many tokens cannot be acquired,
  the method will return 0 instead,
  @param timeoutMillis the maximum wait the calling process is willing to wait for tokens.
  @return a {@link PermitsAndDelay} for the allocated permits.