All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.joala.condition.timing.DeceleratingWait Maven / Gradle / Ivy

/*
 * Copyright 2012 CoreMedia AG
 *
 * This file is part of Joala.
 *
 * Joala is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Joala is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Joala.  If not, see .
 */

package net.joala.condition.timing;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import net.joala.time.Timeout;
import net.joala.time.TimeoutImpl;
import org.hamcrest.Matcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.concurrent.TimeUnit;

/**
 * 

* A waiter that uses the exponential backoff approach when waiting for a condition. * Starting with a minimum wait period, the algorithm waits successively longer on * subsequent attempts, increasing the wait time by a small percentage. This allows * short waits to be completed quickly while reducing the overhead for repeated * checks during long waits. *

* The expected number of checks grows logarithmically with * the wait duration, while the wait duration remains within a constant factor of the * actual time until the condition holds true. *

*/ // This class was in part derived from org.openqa.selenium.support.ui.FluentWait, // which is available under an Apache 2.0 license. public class DeceleratingWait implements Wait { private static final Logger LOG = LoggerFactory.getLogger(DeceleratingWait.class); @VisibleForTesting static final long DEFAULT_TIMEOUT_MILLIS = 500L; @VisibleForTesting static final long INITIAL_DELAY = 10L; @VisibleForTesting static final double DECELERATION_FACTOR = 1.1; private static final long SLEEP_NOT_MUCH_LONGER_OFFSET_MILLIS = 100L; @Nonnull private final Timeout timeout; @Nonnegative private final double timeoutFactor; @Nonnull private final WaitFailStrategy failStrategy; public DeceleratingWait() { this(new TimeoutImpl(DEFAULT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); } public DeceleratingWait(@Nonnull final Timeout timeout) { this(timeout, new WaitTimeoutFailStrategy()); } public DeceleratingWait(@Nonnull final Timeout timeout, @Nonnegative final double timeoutFactor) { this(timeout, timeoutFactor, new WaitTimeoutFailStrategy()); } public DeceleratingWait(@Nonnull final Timeout timeout, @Nonnull final WaitFailStrategy failStrategy) { this(timeout, 1d, failStrategy); } public DeceleratingWait(@Nonnull final Timeout timeout, @Nonnegative final double timeoutFactor, @Nonnull final WaitFailStrategy failStrategy) { this.timeout = timeout; this.timeoutFactor = timeoutFactor; this.failStrategy = failStrategy; } /** * Return the current time in milliseconds. Overwrite for tests. * * @return the current time */ @VisibleForTesting protected long nowMillis() { return System.currentTimeMillis(); } /** * Sleep the given number of milliseconds. * * @param millis how long to sleep * @throws InterruptedException if the current thread has been interrupted */ @VisibleForTesting protected void sleep(final long millis) throws InterruptedException { Thread.sleep(millis); } @Override public final T until(@Nonnull final F input, @Nonnull final Function stateQuery) { return until(null, input, stateQuery, null); } @Override public T until(@Nonnull final F input, @Nonnull final Function stateQuery, @Nullable final Matcher matcher) { return until(null, input, stateQuery, matcher); } @Override public T until(@Nullable final String message, @Nonnull final F input, @Nonnull final Function stateQuery, @Nullable final Matcher matcher) { // Compute the deadlineTimeMillis until which we want to wait. final long startTimeMillis = nowMillis(); final long timeoutMillis = timeout.in(TimeUnit.MILLISECONDS, timeoutFactor); final long deadlineTimeMillis = startTimeMillis + timeoutMillis; if (LOG.isDebugEnabled()) { LOG.debug("Start waiting for:"); LOG.debug(" state query: .... {}", stateQuery); LOG.debug(" matcher: ........ {}", matcher); LOG.debug(" timeout (ms): ... {}", timeoutMillis); } // At first, wait 10ms between checks. long delay = INITIAL_DELAY; // We keep track of the last exception to be able to rethrow it. IgnorableStateQueryException lastException = null; T lastState = null; while (true) { // Measure the time that the evaluation takes. final long beforeEvaluationTimeMillis = nowMillis(); try { // Evaluate and report the result unless it is null, false, or an exception. final T result = stateQuery.apply(input); if (matcher == null || matcher.matches(result)) { return result; } lastState = result; } catch (IgnorableStateQueryException e) { // Remember the exception for rethrowing. LOG.trace("Ignoring exception for now. Might rethrow later if failed with error.", e); lastException = e; } final long afterEvaluationTimeMillis = nowMillis(); // Are we past the deadlineTimeMillis? if (afterEvaluationTimeMillis > deadlineTimeMillis) { failAtDeadline(message, stateQuery, input, lastException, lastState, matcher, startTimeMillis); } delay = sleepAndRecalculateDelay(delay, deadlineTimeMillis, beforeEvaluationTimeMillis, afterEvaluationTimeMillis); } } private long sleepAndRecalculateDelay(final long previousDelay, final long deadlineTimeMillis, final long beforeEvaluationTimeMillis, final long afterEvaluationTimeMillis) { long newDelay = previousDelay; // Leave at least as much time between two checks as the check itself took. final long lastDuration = afterEvaluationTimeMillis - beforeEvaluationTimeMillis; if (lastDuration > newDelay) { newDelay = lastDuration; } // Wait, but not much longer than until the deadlineTimeMillis and at least a millisecond. try { sleep(Math.max(1, Math.min(newDelay, deadlineTimeMillis + SLEEP_NOT_MUCH_LONGER_OFFSET_MILLIS - afterEvaluationTimeMillis))); } catch (InterruptedException e) { throw new IllegalStateException("unexpected interruption", e); } // Make checks less and less frequently. // Increase the wait period using the deceleration factor, but // wait at least one millisecond longer next time. newDelay = Math.max(newDelay + 1, (long) (newDelay * DECELERATION_FACTOR)); return newDelay; } private void failAtDeadline(@Nullable final String message, @Nonnull final Function stateQuery, @Nonnull final F input, @Nullable final IgnorableStateQueryException lastException, @Nullable final T lastState, @Nonnull final Matcher matcher, @Nonnegative final long startTimeMillis) { final long consumedMillis = nowMillis() - startTimeMillis; if (lastException == null) { failStrategy.fail(message, stateQuery, input, lastState, matcher, consumedMillis); } else { failStrategy.fail(message, stateQuery, input, lastException, consumedMillis); } } @Override public String toString() { return Objects.toStringHelper(this) .add("timeout", timeout) .add("timeoutFactor", timeoutFactor) .add("failStrategy", failStrategy) .toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy