net.tascalate.concurrent.Timeouts Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of net.tascalate.concurrent Show documentation
Show all versions of net.tascalate.concurrent Show documentation
Implementation of blocking (IO-Bound) cancellable java.util.concurrent.CompletionStage
and related extensions to java.util.concurrent.ExecutorService-s
/**
* Copyright 2015-2019 Valery Silaev (http://vsilaev.com)
*
* 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 net.tascalate.concurrent;
import static net.tascalate.concurrent.LinkedCompletion.FutureCompletion;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
class Timeouts {
static final Duration NEGATIVE_DURATION = Duration.ofNanos(-1);
private Timeouts() {}
/**
* Creates a promise that is resolved successfully after delay specified
* @param duration
* the duration of timeout
* @return
* the new promise
*/
static Promise delay(Duration duration) {
TimeMeasurment tm = new TimeMeasurment(duration);
FutureCompletion result = new FutureCompletion<>();
Future> timeout = scheduler.schedule(
() -> result.complete(duration), tm.amount, tm.unit
);
return result.dependsOn(timeout).toPromise();
}
/**
* Creates a promise that is resolved after delay specified
* @param delay
* the duration of timeout
* @param timeUnit
* the time unit of the delay
* @return
* the new promise
*/
static Promise delay(long delay, TimeUnit timeUnit) {
return delay( toDuration(delay, timeUnit) );
}
static Promise delayed(T value, long delay, TimeUnit timeUnit) {
return delayed(value, toDuration(delay, timeUnit));
}
static Promise delayed(T value, Duration duration) {
return delay(duration).dependent().thenApply(d -> value, true);
}
/**
* Creates a promise that is resolved erronously with {@link TimeoutException} after delay specified
* @param duration
* the duration of timeout
* @return
* the new promise
*/
static Promise failAfter(Duration duration) {
TimeMeasurment tm = new TimeMeasurment(duration);
FutureCompletion result = new FutureCompletion<>();
Future> timeout = scheduler.schedule(
() -> result.completeExceptionally(new TimeoutException("Timeout after " + duration)),
tm.amount, tm.unit
);
return result.dependsOn(timeout).toPromise();
}
/**
* Creates a promise that is resolved erronously with {@link TimeoutException} after delay specified
* @param delay
* the duration of timeout
* @param timeUnit
* the time unit of the delay
* @return
* the new promise
*/
static Promise failAfter(long delay, TimeUnit timeUnit) {
return failAfter( toDuration(delay, timeUnit) );
}
static Duration toDuration(long delay, TimeUnit timeUnit) {
return Duration.of(delay, toChronoUnit(timeUnit));
}
static BiConsumer timeoutsCleanup(Promise self, Promise> timeout, boolean cancelOnTimeout) {
return (r, e) -> {
// Result comes from timeout and cancel-on-timeout is set
// If both are done then cancel has no effect anyway
if (cancelOnTimeout && timeout.isDone() && !timeout.isCancelled()) {
self.cancel(true);
}
timeout.cancel(true);
};
}
static BiConsumer configureDelay(Promise extends T> self, CompletableFuture> delayed, Duration duration, boolean delayOnError) {
return (result, error) -> {
if (error == null || (delayOnError && !self.isCancelled())) {
Promise> timeout = delay(duration);
delayed.whenComplete( (r, e) -> timeout.cancel(true) );
timeout.whenComplete( (r, e) -> delayed.complete(Try.nothing()) );
} else {
// when error and should not delay on error
delayed.complete(Try.nothing());
}
};
}
private static ChronoUnit toChronoUnit(TimeUnit unit) {
Objects.requireNonNull(unit, "unit");
switch (unit) {
case NANOSECONDS:
return ChronoUnit.NANOS;
case MICROSECONDS:
return ChronoUnit.MICROS;
case MILLISECONDS:
return ChronoUnit.MILLIS;
case SECONDS:
return ChronoUnit.SECONDS;
case MINUTES:
return ChronoUnit.MINUTES;
case HOURS:
return ChronoUnit.HOURS;
case DAYS:
return ChronoUnit.DAYS;
default:
throw new IllegalArgumentException("Unknown TimeUnit constant");
}
}
static class TimeMeasurment {
final TimeUnit unit;
final long amount;
TimeMeasurment(Duration duration) {
// Try to get value with best precision without throwing ArythmeticException due to overflow
if (duration.compareTo(MAX_BY_NANOS) < 0) {
amount = duration.toNanos();
unit = TimeUnit.NANOSECONDS;
} else if (duration.compareTo(MAX_BY_MILLIS) < 0) {
amount = duration.toMillis();
unit = TimeUnit.MILLISECONDS;
} else {
amount = duration.getSeconds();
unit = TimeUnit.SECONDS;
}
}
}
private static final Duration MAX_BY_NANOS = Duration.ofNanos(Long.MAX_VALUE);
private static final Duration MAX_BY_MILLIS = Duration.ofMillis(Long.MAX_VALUE);
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new ThreadFactory() {
private final ThreadFactory threadFactory = Executors.defaultThreadFactory();
@Override
public Thread newThread(Runnable r) {
final Thread result = threadFactory.newThread(r);
result.setDaemon(true);
result.setName(Timeouts.class.getName());
return result;
}
});
}