Skip to content

Commit

Permalink
Merge e798154 into 4e9ddc3
Browse files Browse the repository at this point in the history
  • Loading branch information
maarek committed May 30, 2017
2 parents 4e9ddc3 + e798154 commit e03b182
Show file tree
Hide file tree
Showing 10 changed files with 508 additions and 7 deletions.
1 change: 1 addition & 0 deletions resilience4j-all/build.gradle
Original file line number Diff line number Diff line change
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')
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies {
compile ( libraries.rxjava2)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
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 after a configurable duration.
*/
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 that is restricted by a TimeLimiter.
*
* @param timeLimiter the TimeLimiter
* @param future the original future
* @param <T> the type of results supplied by the future
* @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 that is restricted by a TimeLimiter.
*
* @param timeLimiter the TimeLimiter
* @param futureSupplier the original future supplier
* @param <T> the type of results supplied by the 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 Future.
*
* @param future the original Future
* @param <T> the result type of the future
* @param <F> the type of Future
* @return the result of the decorated 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 Future Supplier.
*
* @param futureSupplier the original future supplier
* @param <T> the result type of the future
* @param <F> the type of Future
* @return the result of the 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();
}
}
Original file line number Diff line number Diff line change
@@ -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 1 second.
*
* @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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit e03b182

Please sign in to comment.