
com.mongodb.internal.time.Timeout Maven / Gradle / Ivy
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mongodb.internal.time;
import com.mongodb.MongoInterruptedException;
import com.mongodb.assertions.Assertions;
import com.mongodb.internal.function.CheckedConsumer;
import com.mongodb.internal.function.CheckedFunction;
import com.mongodb.internal.function.CheckedRunnable;
import com.mongodb.internal.function.CheckedSupplier;
import com.mongodb.lang.Nullable;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.function.LongConsumer;
import java.util.function.LongFunction;
import java.util.function.Supplier;
import static com.mongodb.internal.thread.InterruptionUtil.interruptAndCreateMongoInterruptedException;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
/**
* A Timeout is a "deadline", point in time by which something must happen.
*
* @see TimePoint
*/
public interface Timeout {
/**
* @param timeouts the timeouts
* @return the instance of the timeout that expires earliest
*/
static Timeout earliest(final Timeout... timeouts) {
List list = Arrays.asList(timeouts);
list.forEach(v -> {
if (!(v instanceof TimePoint)) {
throw new AssertionError("Only TimePoints may be compared");
}
});
return Collections.min(list, (a, b) -> {
TimePoint tpa = (TimePoint) a;
TimePoint tpb = (TimePoint) b;
return tpa.compareTo(tpb);
});
}
/**
* @return an infinite (non-expiring) timeout
*/
static Timeout infinite() {
return TimePoint.infinite();
}
/**
* @param timeout the timeout
* @return the provided timeout, or an infinite timeout if provided null.
*/
static Timeout nullAsInfinite(@Nullable final Timeout timeout) {
return timeout == null ? infinite() : timeout;
}
/**
* @param duration the non-negative duration, in the specified time unit
* @param unit the time unit
* @param zeroSemantics what to interpret a 0 duration as (infinite or expired)
* @return a timeout that expires in the specified duration after now.
*/
@NotNull
static Timeout expiresIn(final long duration, final TimeUnit unit, final ZeroSemantics zeroSemantics) {
if (duration < 0) {
throw new AssertionError("Timeouts must not be in the past");
} else if (duration == 0) {
switch (zeroSemantics) {
case ZERO_DURATION_MEANS_INFINITE:
return Timeout.infinite();
case ZERO_DURATION_MEANS_EXPIRED:
return TimePoint.now();
default:
throw Assertions.fail("Unknown enum value");
}
} else {
// duration will never be negative
return TimePoint.now().timeoutAfterOrInfiniteIfNegative(duration, unit);
}
}
/**
* This timeout, shortened by the provided amount (it will expire sooner).
*
* @param amount the amount to shorten by
* @param timeUnit the time unit of the amount
* @return the shortened timeout
*/
Timeout shortenBy(long amount, TimeUnit timeUnit);
/**
* {@linkplain Condition#awaitNanos(long) Awaits} on the provided
* condition. Will {@linkplain Condition#await() await} without a waiting
* time if this timeout is infinite.
* {@linkplain #onExistsAndExpired(Timeout, Runnable) Expiry} is not
* checked by this method, and should be called outside of this method.
* @param condition the condition.
* @param action supplies the name of the action, for {@link MongoInterruptedException}
*/
default void awaitOn(final Condition condition, final Supplier action) {
try {
// ignore result, the timeout will track this remaining time
//noinspection ResultOfMethodCallIgnored
checkedRun(NANOSECONDS,
() -> condition.await(),
(ns) -> condition.awaitNanos(ns),
() -> condition.awaitNanos(0));
} catch (InterruptedException e) {
throw interruptAndCreateMongoInterruptedException("Interrupted while " + action.get(), e);
}
}
/**
* {@linkplain CountDownLatch#await(long, TimeUnit) Awaits} on the provided
* condition. Will {@linkplain CountDownLatch#await() await} without a waiting
* time if this timeout is infinite.
* {@linkplain #onExistsAndExpired(Timeout, Runnable) Expiry} is not
* checked by this method, and should be called outside of this method.
* @param latch the latch.
* @param action supplies the name of the action, for {@link MongoInterruptedException}
*/
default void awaitOn(final CountDownLatch latch, final Supplier action) {
try {
// ignore result, the timeout will track this remaining time
//noinspection ResultOfMethodCallIgnored
checkedRun(NANOSECONDS,
() -> latch.await(),
(ns) -> latch.await(ns, NANOSECONDS),
() -> latch.await(0, NANOSECONDS));
} catch (InterruptedException e) {
throw interruptAndCreateMongoInterruptedException("Interrupted while " + action.get(), e);
}
}
/**
* Call one of 3 possible branches depending on the state of the timeout,
* and return the result.
* @param timeUnit the positive (non-zero) remaining time to provide to the
* {@code onHasRemaining} branch. The underlying nano time
* is rounded down to the given time unit. If 0, the timeout
* is considered expired.
* @param onInfinite branch to take when the timeout is infinite
* @param onHasRemaining branch to take when there is positive remaining
* time in the specified time unit
* @param onExpired branch to take when the timeout is expired
* @return the result provided by the branch
* @param the type of the result
*/
default T call(final TimeUnit timeUnit,
final Supplier onInfinite, final LongFunction onHasRemaining,
final Supplier onExpired) {
return checkedCall(timeUnit, onInfinite::get, onHasRemaining::apply, onExpired::get);
}
/**
* Call, but throwing a checked exception.
* @see #call(TimeUnit, Supplier, LongFunction, Supplier)
* @param the checked exception type
* @throws E the checked exception
*/
T checkedCall(TimeUnit timeUnit,
CheckedSupplier onInfinite, CheckedFunction onHasRemaining,
CheckedSupplier onExpired) throws E;
/**
* Run one of 3 possible branches depending on the state of the timeout.
* @see #call(TimeUnit, Supplier, LongFunction, Supplier)
*/
default void run(final TimeUnit timeUnit,
final Runnable onInfinite, final LongConsumer onHasRemaining,
final Runnable onExpired) {
this.call(timeUnit, () -> {
onInfinite.run();
return null;
}, (t) -> {
onHasRemaining.accept(t);
return null;
}, () -> {
onExpired.run();
return null;
});
}
/**
* Run, but throwing a checked exception.
* @see #checkedCall(TimeUnit, CheckedSupplier, CheckedFunction, CheckedSupplier)
*/
default void checkedRun(final TimeUnit timeUnit,
final CheckedRunnable onInfinite, final CheckedConsumer onHasRemaining,
final CheckedRunnable onExpired) throws E {
this.checkedCall(timeUnit, () -> {
onInfinite.run();
return null;
}, (t) -> {
onHasRemaining.accept(t);
return null;
}, () -> {
onExpired.run();
return null;
});
}
default void onExpired(final Runnable onExpired) {
onExistsAndExpired(this, onExpired);
}
static void onExistsAndExpired(@Nullable final Timeout t, final Runnable onExpired) {
if (t == null) {
return;
}
t.run(NANOSECONDS,
() -> {},
(ns) -> {},
() -> onExpired.run());
}
enum ZeroSemantics {
ZERO_DURATION_MEANS_EXPIRED,
ZERO_DURATION_MEANS_INFINITE
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy