Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timeout Decorator #142

Merged
merged 8 commits into from May 31, 2017
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions resilience4j-all/build.gradle
Expand Up @@ -10,5 +10,6 @@ dependencies {
compile project(':resilience4j-retry')
compile project(':resilience4j-consumer')
compile project(':resilience4j-cache')
compile project(':resilience4j-timelimiter')
testCompile project(':resilience4j-test')
}
@@ -1,5 +1,11 @@
package io.github.resilience4j.decorators;

import java.util.concurrent.CompletionStage;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import io.github.resilience4j.bulkhead.Bulkhead;
import io.github.resilience4j.cache.Cache;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
Expand All @@ -10,12 +16,6 @@
import io.vavr.CheckedFunction1;
import io.vavr.CheckedRunnable;

import java.util.concurrent.CompletionStage;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* A Decorator builder which can be used to apply multiple decorators to a (Checked-)Supplier, (Checked-)Function,
* (Checked-)Runnable, (Checked-)CompletionStage or (Checked-)Consumer
Expand Down
3 changes: 3 additions & 0 deletions resilience4j-timelimiter/build.gradle
@@ -0,0 +1,3 @@
dependencies {
compile ( libraries.rxjava2)
}
@@ -0,0 +1,129 @@
package io.github.resilience4j.timelimiter;

import io.github.resilience4j.timelimiter.internal.TimeLimiterContext;

import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;

/**
* A TimeLimiter decorator stops execution at a configurable rate.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after a configurable duration, or?

*/
public interface TimeLimiter {

/**
* Creates a TimeLimiter decorator with a default TimeLimiterConfig configuration.
*
* @return The {@link TimeLimiter}
*/
static TimeLimiter ofDefaults() {
return new TimeLimiterContext(TimeLimiterConfig.ofDefaults());
}

/**
* Creates a TimeLimiter decorator with a TimeLimiterConfig configuration.
*
* @param timeLimiterConfig the TimeLimiterConfig
* @return The {@link TimeLimiter}
*/
static TimeLimiter of(TimeLimiterConfig timeLimiterConfig) {
return new TimeLimiterContext(timeLimiterConfig);
}

/**
* Creates a TimeLimiter decorator with a timeout Duration.
*
* @param timeoutDuration the timeout Duration
* @return The {@link TimeLimiter}
*/
static TimeLimiter of(Duration timeoutDuration) {
TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom()
.timeoutDuration(timeoutDuration)
.build();

return new TimeLimiterContext(timeLimiterConfig);
}

/**
* Creates a Callable which is restricted by a TimeLimiter.
*
* @param timeLimiter the {@link TimeLimiter}
* @param future the original future
* @param <T> the type of results supplied supplier
* @param <F> the future type supplied
* @return a future which is restricted by a {@link TimeLimiter}.
*/
static <T, F extends Future<T>> Callable<T> decorateFuture(final TimeLimiter timeLimiter, final F future) {
return () -> {
try {
return future.get(timeLimiter.getTimeLimiterConfig().getTimeoutDuration().toMillis(), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
if (timeLimiter.getTimeLimiterConfig().shouldCancelRunningFuture()) {
future.cancel(true);
}
throw e;
}
};
}

/**
* Creates a Callback which is restricted by a TimeLimiter.
*
* @param timeLimiter the {@link TimeLimiter}
* @param futureSupplier the original future supplier
* @param <T> the type of results supplied supplier
* @param <F> the future type supplied
* @return a future supplier which is restricted by a {@link TimeLimiter}.
*/
static <T, F extends Future<T>> Callable<T> decorateFutureSupplier(TimeLimiter timeLimiter, Supplier<F> futureSupplier) {
return () -> {
Future<T> future = futureSupplier.get();
try {
return future.get(timeLimiter.getTimeLimiterConfig().getTimeoutDuration().toMillis(), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
if(timeLimiter.getTimeLimiterConfig().shouldCancelRunningFuture()){
future.cancel(true);
}
throw e;
}
};
}

/**
* Get the TimeLimiterConfig of this TimeLimiter decorator.
*
* @return the TimeLimiterConfig of this TimeLimiter decorator
*/
TimeLimiterConfig getTimeLimiterConfig();

/**
* Decorates and executes the decorated Future.
*
* @param future the original Future
*
* @return the result of the decorated Future.
* @param <T> the result type of the future
* @param <F> the type of Future
* @throws Exception if unable to compute a result
*/
default <T, F extends Future<T>> T executeFuture(F future) throws Exception {
return decorateFuture(this, future).call();
}

/**
* Decorates and executes the decorated Future Supplier.
*
* @param futureSupplier the original future supplier
*
* @return the result of the Future.
* @param <T> the result type of the future
* @param <F> the type of Future
* @throws Exception if unable to compute a result
*/
default <T, F extends Future<T>> T executeFutureSupplier(Supplier<F> futureSupplier) throws Exception {
return decorateFutureSupplier(this, futureSupplier).call();
}
}
@@ -0,0 +1,91 @@
package io.github.resilience4j.timelimiter;

import java.time.Duration;

import static java.util.Objects.requireNonNull;

public class TimeLimiterConfig {
private static final String TIMEOUT_DURATION_MUST_NOT_BE_NULL = "TimeoutDuration must not be null";

private Duration timeoutDuration = Duration.ofSeconds(1);
private boolean cancelRunningFuture = true;

private TimeLimiterConfig() {
}

/**
* Returns a builder to create a custom TimeLimiterConfig.
*
* @return a {@link TimeLimiterConfig.Builder}
*/
public static Builder custom() {
return new Builder();
}

/**
* Creates a default TimeLimiter configuration.
*
* @return a default TimeLimiter configuration.
*/
public static TimeLimiterConfig ofDefaults(){
return new Builder().build();
}

public Duration getTimeoutDuration() {
return timeoutDuration;
}

public boolean shouldCancelRunningFuture() {
return cancelRunningFuture;
}

@Override public String toString() {
return "TimeLimiterConfig{" +
"timeoutDuration=" + timeoutDuration +
"cancelRunningFuture=" + cancelRunningFuture +
'}';
}

public static class Builder {

private TimeLimiterConfig config = new TimeLimiterConfig();

/**
* Builds a TimeLimiterConfig
*
* @return the TimeLimiterConfig
*/
public TimeLimiterConfig build() {
return config;
}

/**
* Configures the thread execution timeout
* Default value is 5 seconds.
*
* @param timeoutDuration the timeout Duration
* @return the TimeLimiterConfig.Builder
*/
public Builder timeoutDuration(final Duration timeoutDuration) {
config.timeoutDuration = checkTimeoutDuration(timeoutDuration);
return this;
}

/**
* Configures whether cancel is called on the running future
* Defaults to TRUE
*
* @param cancelRunningFuture to cancel or not
* @return the TimeLimiterConfig.Builder
*/
public Builder cancelRunningFuture(final boolean cancelRunningFuture) {
config.cancelRunningFuture = cancelRunningFuture;
return this;
}

}

private static Duration checkTimeoutDuration(final Duration timeoutDuration) {
return requireNonNull(timeoutDuration, TIMEOUT_DURATION_MUST_NOT_BE_NULL);
}
}
@@ -0,0 +1,17 @@
package io.github.resilience4j.timelimiter.internal;

import io.github.resilience4j.timelimiter.TimeLimiter;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;

public class TimeLimiterContext implements TimeLimiter {
private final TimeLimiterConfig timeLimiterConfig;

public TimeLimiterContext(TimeLimiterConfig timeLimiterConfig) {
this.timeLimiterConfig = timeLimiterConfig;
}

@Override
public TimeLimiterConfig getTimeLimiterConfig() {
return timeLimiterConfig;
}
}
@@ -0,0 +1,53 @@
package io.github.resilience4j.timelimiter;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.time.Duration;

import static org.assertj.core.api.BDDAssertions.then;

public class TimeLimiterConfigTest {

private static final Duration TIMEOUT = Duration.ofSeconds(5);
private static final boolean SHOULD_CANCEL = false;
private static final String TIMEOUT_DURATION_MUST_NOT_BE_NULL = "TimeoutDuration must not be null";
private static final String TIMEOUT_TO_STRING = "TimeLimiterConfig{timeoutDuration=PT1ScancelRunningFuture=true}";

@Rule
public ExpectedException exception = ExpectedException.none();


@Test
public void builderPositive() {
TimeLimiterConfig config = TimeLimiterConfig.custom()
.timeoutDuration(TIMEOUT)
.cancelRunningFuture(SHOULD_CANCEL)
.build();

then(config.getTimeoutDuration()).isEqualTo(TIMEOUT);
then(config.shouldCancelRunningFuture()).isEqualTo(SHOULD_CANCEL);
}

@Test
public void defaultConstruction() {
TimeLimiterConfig config = TimeLimiterConfig.ofDefaults();
then(config.getTimeoutDuration()).isEqualTo(Duration.ofSeconds(1));
then(config.shouldCancelRunningFuture()).isTrue();
}

@Test
public void builderTimeoutIsNull() {
exception.expect(NullPointerException.class);
exception.expectMessage(TIMEOUT_DURATION_MUST_NOT_BE_NULL);

TimeLimiterConfig.custom()
.timeoutDuration(null);
}

@Test
public void configToString() {
then(TimeLimiterConfig.ofDefaults().toString()).isEqualTo(TIMEOUT_TO_STRING);
}
}