Skip to content

Commit edcd058

Browse files
maarekRobWin
authored andcommitted
Issue ReactiveX#67: Created a new TimeLimiter decorator
1 parent eabff55 commit edcd058

File tree

10 files changed

+508
-7
lines changed

10 files changed

+508
-7
lines changed

resilience4j-all/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ dependencies {
1010
compile project(':resilience4j-retry')
1111
compile project(':resilience4j-consumer')
1212
compile project(':resilience4j-cache')
13+
compile project(':resilience4j-timelimiter')
1314
testCompile project(':resilience4j-test')
1415
}

resilience4j-all/src/main/java/io/github/resilience4j/decorators/Decorators.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package io.github.resilience4j.decorators;
22

3+
import java.util.concurrent.CompletionStage;
4+
import java.util.concurrent.ScheduledExecutorService;
5+
import java.util.function.Consumer;
6+
import java.util.function.Function;
7+
import java.util.function.Supplier;
8+
39
import io.github.resilience4j.bulkhead.Bulkhead;
410
import io.github.resilience4j.cache.Cache;
511
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
@@ -10,12 +16,6 @@
1016
import io.vavr.CheckedFunction1;
1117
import io.vavr.CheckedRunnable;
1218

13-
import java.util.concurrent.CompletionStage;
14-
import java.util.concurrent.ScheduledExecutorService;
15-
import java.util.function.Consumer;
16-
import java.util.function.Function;
17-
import java.util.function.Supplier;
18-
1919
/**
2020
* A Decorator builder which can be used to apply multiple decorators to a (Checked-)Supplier, (Checked-)Function,
2121
* (Checked-)Runnable, (Checked-)CompletionStage or (Checked-)Consumer

resilience4j-timelimiter/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dependencies {
2+
compile ( libraries.rxjava2)
3+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package io.github.resilience4j.timelimiter;
2+
3+
import io.github.resilience4j.timelimiter.internal.TimeLimiterContext;
4+
5+
import java.time.Duration;
6+
import java.util.concurrent.Callable;
7+
import java.util.concurrent.Future;
8+
import java.util.concurrent.TimeUnit;
9+
import java.util.concurrent.TimeoutException;
10+
import java.util.function.Supplier;
11+
12+
/**
13+
* A TimeLimiter decorator stops execution after a configurable duration.
14+
*/
15+
public interface TimeLimiter {
16+
17+
/**
18+
* Creates a TimeLimiter decorator with a default TimeLimiterConfig configuration.
19+
*
20+
* @return The {@link TimeLimiter}
21+
*/
22+
static TimeLimiter ofDefaults() {
23+
return new TimeLimiterContext(TimeLimiterConfig.ofDefaults());
24+
}
25+
26+
/**
27+
* Creates a TimeLimiter decorator with a TimeLimiterConfig configuration.
28+
*
29+
* @param timeLimiterConfig the TimeLimiterConfig
30+
* @return The {@link TimeLimiter}
31+
*/
32+
static TimeLimiter of(TimeLimiterConfig timeLimiterConfig) {
33+
return new TimeLimiterContext(timeLimiterConfig);
34+
}
35+
36+
/**
37+
* Creates a TimeLimiter decorator with a timeout Duration.
38+
*
39+
* @param timeoutDuration the timeout Duration
40+
* @return The {@link TimeLimiter}
41+
*/
42+
static TimeLimiter of(Duration timeoutDuration) {
43+
TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom()
44+
.timeoutDuration(timeoutDuration)
45+
.build();
46+
47+
return new TimeLimiterContext(timeLimiterConfig);
48+
}
49+
50+
/**
51+
* Creates a Callable that is restricted by a TimeLimiter.
52+
*
53+
* @param timeLimiter the TimeLimiter
54+
* @param future the original future
55+
* @param <T> the type of results supplied by the future
56+
* @param <F> the future type supplied
57+
* @return a future which is restricted by a {@link TimeLimiter}.
58+
*/
59+
static <T, F extends Future<T>> Callable<T> decorateFuture(final TimeLimiter timeLimiter, final F future) {
60+
return () -> {
61+
try {
62+
return future.get(timeLimiter.getTimeLimiterConfig().getTimeoutDuration().toMillis(), TimeUnit.MILLISECONDS);
63+
} catch (TimeoutException e) {
64+
if (timeLimiter.getTimeLimiterConfig().shouldCancelRunningFuture()) {
65+
future.cancel(true);
66+
}
67+
throw e;
68+
}
69+
};
70+
}
71+
72+
/**
73+
* Creates a Callback that is restricted by a TimeLimiter.
74+
*
75+
* @param timeLimiter the TimeLimiter
76+
* @param futureSupplier the original future supplier
77+
* @param <T> the type of results supplied by the supplier
78+
* @param <F> the future type supplied
79+
* @return a future supplier which is restricted by a {@link TimeLimiter}.
80+
*/
81+
static <T, F extends Future<T>> Callable<T> decorateFutureSupplier(TimeLimiter timeLimiter, Supplier<F> futureSupplier) {
82+
return () -> {
83+
Future<T> future = futureSupplier.get();
84+
try {
85+
return future.get(timeLimiter.getTimeLimiterConfig().getTimeoutDuration().toMillis(), TimeUnit.MILLISECONDS);
86+
} catch (TimeoutException e) {
87+
if(timeLimiter.getTimeLimiterConfig().shouldCancelRunningFuture()){
88+
future.cancel(true);
89+
}
90+
throw e;
91+
}
92+
};
93+
}
94+
95+
/**
96+
* Get the TimeLimiterConfig of this TimeLimiter decorator.
97+
*
98+
* @return the TimeLimiterConfig of this TimeLimiter decorator
99+
*/
100+
TimeLimiterConfig getTimeLimiterConfig();
101+
102+
/**
103+
* Decorates and executes the Future.
104+
*
105+
* @param future the original Future
106+
* @param <T> the result type of the future
107+
* @param <F> the type of Future
108+
* @return the result of the decorated Future.
109+
* @throws Exception if unable to compute a result
110+
*/
111+
default <T, F extends Future<T>> T executeFuture(F future) throws Exception {
112+
return decorateFuture(this, future).call();
113+
}
114+
115+
/**
116+
* Decorates and executes the Future Supplier.
117+
*
118+
* @param futureSupplier the original future supplier
119+
* @param <T> the result type of the future
120+
* @param <F> the type of Future
121+
* @return the result of the Future.
122+
* @throws Exception if unable to compute a result
123+
*/
124+
default <T, F extends Future<T>> T executeFutureSupplier(Supplier<F> futureSupplier) throws Exception {
125+
return decorateFutureSupplier(this, futureSupplier).call();
126+
}
127+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package io.github.resilience4j.timelimiter;
2+
3+
import java.time.Duration;
4+
5+
import static java.util.Objects.requireNonNull;
6+
7+
public class TimeLimiterConfig {
8+
private static final String TIMEOUT_DURATION_MUST_NOT_BE_NULL = "TimeoutDuration must not be null";
9+
10+
private Duration timeoutDuration = Duration.ofSeconds(1);
11+
private boolean cancelRunningFuture = true;
12+
13+
private TimeLimiterConfig() {
14+
}
15+
16+
/**
17+
* Returns a builder to create a custom TimeLimiterConfig.
18+
*
19+
* @return a {@link TimeLimiterConfig.Builder}
20+
*/
21+
public static Builder custom() {
22+
return new Builder();
23+
}
24+
25+
/**
26+
* Creates a default TimeLimiter configuration.
27+
*
28+
* @return a default TimeLimiter configuration.
29+
*/
30+
public static TimeLimiterConfig ofDefaults(){
31+
return new Builder().build();
32+
}
33+
34+
public Duration getTimeoutDuration() {
35+
return timeoutDuration;
36+
}
37+
38+
public boolean shouldCancelRunningFuture() {
39+
return cancelRunningFuture;
40+
}
41+
42+
@Override public String toString() {
43+
return "TimeLimiterConfig{" +
44+
"timeoutDuration=" + timeoutDuration +
45+
"cancelRunningFuture=" + cancelRunningFuture +
46+
'}';
47+
}
48+
49+
public static class Builder {
50+
51+
private TimeLimiterConfig config = new TimeLimiterConfig();
52+
53+
/**
54+
* Builds a TimeLimiterConfig
55+
*
56+
* @return the TimeLimiterConfig
57+
*/
58+
public TimeLimiterConfig build() {
59+
return config;
60+
}
61+
62+
/**
63+
* Configures the thread execution timeout
64+
* Default value is 1 second.
65+
*
66+
* @param timeoutDuration the timeout Duration
67+
* @return the TimeLimiterConfig.Builder
68+
*/
69+
public Builder timeoutDuration(final Duration timeoutDuration) {
70+
config.timeoutDuration = checkTimeoutDuration(timeoutDuration);
71+
return this;
72+
}
73+
74+
/**
75+
* Configures whether cancel is called on the running future
76+
* Defaults to TRUE
77+
*
78+
* @param cancelRunningFuture to cancel or not
79+
* @return the TimeLimiterConfig.Builder
80+
*/
81+
public Builder cancelRunningFuture(final boolean cancelRunningFuture) {
82+
config.cancelRunningFuture = cancelRunningFuture;
83+
return this;
84+
}
85+
86+
}
87+
88+
private static Duration checkTimeoutDuration(final Duration timeoutDuration) {
89+
return requireNonNull(timeoutDuration, TIMEOUT_DURATION_MUST_NOT_BE_NULL);
90+
}
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.github.resilience4j.timelimiter.internal;
2+
3+
import io.github.resilience4j.timelimiter.TimeLimiter;
4+
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
5+
6+
public class TimeLimiterContext implements TimeLimiter {
7+
private final TimeLimiterConfig timeLimiterConfig;
8+
9+
public TimeLimiterContext(TimeLimiterConfig timeLimiterConfig) {
10+
this.timeLimiterConfig = timeLimiterConfig;
11+
}
12+
13+
@Override
14+
public TimeLimiterConfig getTimeLimiterConfig() {
15+
return timeLimiterConfig;
16+
}
17+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.github.resilience4j.timelimiter;
2+
3+
import org.junit.Rule;
4+
import org.junit.Test;
5+
import org.junit.rules.ExpectedException;
6+
7+
import java.time.Duration;
8+
9+
import static org.assertj.core.api.BDDAssertions.then;
10+
11+
public class TimeLimiterConfigTest {
12+
13+
private static final Duration TIMEOUT = Duration.ofSeconds(5);
14+
private static final boolean SHOULD_CANCEL = false;
15+
private static final String TIMEOUT_DURATION_MUST_NOT_BE_NULL = "TimeoutDuration must not be null";
16+
private static final String TIMEOUT_TO_STRING = "TimeLimiterConfig{timeoutDuration=PT1ScancelRunningFuture=true}";
17+
18+
@Rule
19+
public ExpectedException exception = ExpectedException.none();
20+
21+
22+
@Test
23+
public void builderPositive() {
24+
TimeLimiterConfig config = TimeLimiterConfig.custom()
25+
.timeoutDuration(TIMEOUT)
26+
.cancelRunningFuture(SHOULD_CANCEL)
27+
.build();
28+
29+
then(config.getTimeoutDuration()).isEqualTo(TIMEOUT);
30+
then(config.shouldCancelRunningFuture()).isEqualTo(SHOULD_CANCEL);
31+
}
32+
33+
@Test
34+
public void defaultConstruction() {
35+
TimeLimiterConfig config = TimeLimiterConfig.ofDefaults();
36+
then(config.getTimeoutDuration()).isEqualTo(Duration.ofSeconds(1));
37+
then(config.shouldCancelRunningFuture()).isTrue();
38+
}
39+
40+
@Test
41+
public void builderTimeoutIsNull() {
42+
exception.expect(NullPointerException.class);
43+
exception.expectMessage(TIMEOUT_DURATION_MUST_NOT_BE_NULL);
44+
45+
TimeLimiterConfig.custom()
46+
.timeoutDuration(null);
47+
}
48+
49+
@Test
50+
public void configToString() {
51+
then(TimeLimiterConfig.ofDefaults().toString()).isEqualTo(TIMEOUT_TO_STRING);
52+
}
53+
}

0 commit comments

Comments
 (0)