diff --git a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfig.java b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfig.java index 721d5b0ce..1c1188641 100644 --- a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfig.java +++ b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/CircuitBreakerConfig.java @@ -56,6 +56,7 @@ public class CircuitBreakerConfig implements Serializable { private static final Predicate DEFAULT_RECORD_RESULT_PREDICATE = (Object object) -> false; private static final Function, TransitionCheckResult> DEFAULT_TRANSITION_ON_RESULT = any -> TransitionCheckResult.noTransition(); + private static final Clock DEFAULT_CLOCK = Clock.systemUTC(); // The default exception predicate counts all exceptions as failures. private transient Predicate recordExceptionPredicate = DEFAULT_RECORD_EXCEPTION_PREDICATE; @@ -87,6 +88,7 @@ public class CircuitBreakerConfig implements Serializable { .ofSeconds(DEFAULT_SLOW_CALL_DURATION_THRESHOLD); private Duration maxWaitDurationInHalfOpenState = Duration .ofSeconds(DEFAULT_WAIT_DURATION_IN_HALF_OPEN_STATE); + private transient Clock clock = DEFAULT_CLOCK; private CircuitBreakerConfig() { } @@ -189,6 +191,10 @@ public Duration getMaxWaitDurationInHalfOpenState() { return maxWaitDurationInHalfOpenState; } + public Clock getClock() { + return clock; + } + public enum SlidingWindowType { TIME_BASED, COUNT_BASED } @@ -319,6 +325,7 @@ public static class Builder { private Duration maxWaitDurationInHalfOpenState = Duration .ofSeconds(DEFAULT_WAIT_DURATION_IN_HALF_OPEN_STATE); private byte createWaitIntervalFunctionCounter = 0; + private Clock clock = DEFAULT_CLOCK; public Builder(CircuitBreakerConfig baseConfig) { @@ -341,6 +348,7 @@ public Builder(CircuitBreakerConfig baseConfig) { this.maxWaitDurationInHalfOpenState = baseConfig.maxWaitDurationInHalfOpenState; this.writableStackTraceEnabled = baseConfig.writableStackTraceEnabled; this.recordResultPredicate = baseConfig.recordResultPredicate; + this.clock = baseConfig.clock; } public Builder() { @@ -781,6 +789,22 @@ public Builder automaticTransitionFromOpenToHalfOpenEnabled( return this; } + /** + * Configures a custom Clock instance to use for time measurements. + * Default value is Clock.systemUTC(). + * + * @param clock the Clock to use + * @return the CircuitBreakerConfig.Builder + */ + public Builder clock(Clock clock) { + if (clock == null) { + this.clock = DEFAULT_CLOCK; + } else { + this.clock = clock; + } + return this; + } + /** * Builds a CircuitBreakerConfig * @@ -808,6 +832,7 @@ public CircuitBreakerConfig build() { config.currentTimestampFunction = currentTimestampFunction; config.timestampUnit = timestampUnit; config.recordResultPredicate = recordResultPredicate; + config.clock = clock; return config; } diff --git a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerMetrics.java b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerMetrics.java index bf7414d3d..37ddcd756 100644 --- a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerMetrics.java +++ b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerMetrics.java @@ -43,14 +43,13 @@ class CircuitBreakerMetrics implements CircuitBreaker.Metrics { private CircuitBreakerMetrics(int slidingWindowSize, CircuitBreakerConfig.SlidingWindowType slidingWindowType, - CircuitBreakerConfig circuitBreakerConfig, - Clock clock) { + CircuitBreakerConfig circuitBreakerConfig) { if (slidingWindowType == CircuitBreakerConfig.SlidingWindowType.COUNT_BASED) { this.metrics = new FixedSizeSlidingWindowMetrics(slidingWindowSize); this.minimumNumberOfCalls = Math .min(circuitBreakerConfig.getMinimumNumberOfCalls(), slidingWindowSize); } else { - this.metrics = new SlidingTimeWindowMetrics(slidingWindowSize, clock); + this.metrics = new SlidingTimeWindowMetrics(slidingWindowSize, circuitBreakerConfig.getClock()); this.minimumNumberOfCalls = circuitBreakerConfig.getMinimumNumberOfCalls(); } this.failureRateThreshold = circuitBreakerConfig.getFailureRateThreshold(); @@ -61,33 +60,33 @@ private CircuitBreakerMetrics(int slidingWindowSize, } private CircuitBreakerMetrics(int slidingWindowSize, - CircuitBreakerConfig circuitBreakerConfig, Clock clock) { - this(slidingWindowSize, circuitBreakerConfig.getSlidingWindowType(), circuitBreakerConfig, clock); + CircuitBreakerConfig circuitBreakerConfig) { + this(slidingWindowSize, circuitBreakerConfig.getSlidingWindowType(), circuitBreakerConfig); } - static CircuitBreakerMetrics forClosed(CircuitBreakerConfig circuitBreakerConfig, Clock clock) { + static CircuitBreakerMetrics forClosed(CircuitBreakerConfig circuitBreakerConfig) { return new CircuitBreakerMetrics(circuitBreakerConfig.getSlidingWindowSize(), - circuitBreakerConfig, clock); + circuitBreakerConfig); } static CircuitBreakerMetrics forHalfOpen(int permittedNumberOfCallsInHalfOpenState, - CircuitBreakerConfig circuitBreakerConfig, Clock clock) { + CircuitBreakerConfig circuitBreakerConfig) { return new CircuitBreakerMetrics(permittedNumberOfCallsInHalfOpenState, - CircuitBreakerConfig.SlidingWindowType.COUNT_BASED, circuitBreakerConfig, clock); + CircuitBreakerConfig.SlidingWindowType.COUNT_BASED, circuitBreakerConfig); } - static CircuitBreakerMetrics forForcedOpen(CircuitBreakerConfig circuitBreakerConfig, Clock clock) { + static CircuitBreakerMetrics forForcedOpen(CircuitBreakerConfig circuitBreakerConfig) { return new CircuitBreakerMetrics(0, CircuitBreakerConfig.SlidingWindowType.COUNT_BASED, - circuitBreakerConfig, clock); + circuitBreakerConfig); } - static CircuitBreakerMetrics forDisabled(CircuitBreakerConfig circuitBreakerConfig, Clock clock) { + static CircuitBreakerMetrics forDisabled(CircuitBreakerConfig circuitBreakerConfig) { return new CircuitBreakerMetrics(0, CircuitBreakerConfig.SlidingWindowType.COUNT_BASED, - circuitBreakerConfig, clock); + circuitBreakerConfig); } - static CircuitBreakerMetrics forMetricsOnly(CircuitBreakerConfig circuitBreakerConfig, Clock clock) { - return forClosed(circuitBreakerConfig, clock); + static CircuitBreakerMetrics forMetricsOnly(CircuitBreakerConfig circuitBreakerConfig) { + return forClosed(circuitBreakerConfig); } /** diff --git a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachine.java b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachine.java index 3db536045..14cb4cd20 100644 --- a/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachine.java +++ b/resilience4j-circuitbreaker/src/main/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachine.java @@ -71,16 +71,15 @@ public final class CircuitBreakerStateMachine implements CircuitBreaker { * * @param name the name of the CircuitBreaker * @param circuitBreakerConfig The CircuitBreaker configuration. - * @param clock A Clock which can be mocked in tests. * @param schedulerFactory A SchedulerFactory which can be mocked in tests. */ private CircuitBreakerStateMachine(String name, CircuitBreakerConfig circuitBreakerConfig, - Clock clock, SchedulerFactory schedulerFactory, Map tags) { + SchedulerFactory schedulerFactory, Map tags) { this.name = name; this.circuitBreakerConfig = Objects .requireNonNull(circuitBreakerConfig, "Config must not be null"); this.eventProcessor = new CircuitBreakerEventProcessor(); - this.clock = clock; + this.clock = circuitBreakerConfig.getClock(); this.stateReference = new AtomicReference<>(new ClosedState()); this.schedulerFactory = schedulerFactory; this.tags = Objects.requireNonNull(tags, "Tags must not be null"); @@ -97,29 +96,7 @@ private CircuitBreakerStateMachine(String name, CircuitBreakerConfig circuitBrea */ public CircuitBreakerStateMachine(String name, CircuitBreakerConfig circuitBreakerConfig, SchedulerFactory schedulerFactory) { - this(name, circuitBreakerConfig, Clock.systemUTC(), schedulerFactory, emptyMap()); - } - - /** - * Creates a circuitBreaker. - * - * @param name the name of the CircuitBreaker - * @param circuitBreakerConfig The CircuitBreaker configuration. - */ - public CircuitBreakerStateMachine(String name, CircuitBreakerConfig circuitBreakerConfig, - Clock clock) { - this(name, circuitBreakerConfig, clock, SchedulerFactory.getInstance(), emptyMap()); - } - - /** - * Creates a circuitBreaker. - * - * @param name the name of the CircuitBreaker - * @param circuitBreakerConfig The CircuitBreaker configuration. - */ - public CircuitBreakerStateMachine(String name, CircuitBreakerConfig circuitBreakerConfig, - Clock clock, Map tags) { - this(name, circuitBreakerConfig, clock, SchedulerFactory.getInstance(), tags); + this(name, circuitBreakerConfig, schedulerFactory, emptyMap()); } /** @@ -129,7 +106,7 @@ public CircuitBreakerStateMachine(String name, CircuitBreakerConfig circuitBreak * @param circuitBreakerConfig The CircuitBreaker configuration. */ public CircuitBreakerStateMachine(String name, CircuitBreakerConfig circuitBreakerConfig) { - this(name, circuitBreakerConfig, Clock.systemUTC()); + this(name, circuitBreakerConfig, SchedulerFactory.getInstance(), emptyMap()); } /** @@ -137,11 +114,10 @@ public CircuitBreakerStateMachine(String name, CircuitBreakerConfig circuitBreak * * @param name the name of the CircuitBreaker * @param circuitBreakerConfig The CircuitBreaker configuration. - * @param tags Tags to add to the CircuitBreaker. */ public CircuitBreakerStateMachine(String name, CircuitBreakerConfig circuitBreakerConfig, Map tags) { - this(name, circuitBreakerConfig, Clock.systemUTC(), tags); + this(name, circuitBreakerConfig, SchedulerFactory.getInstance(), tags); } /** @@ -588,7 +564,7 @@ private class ClosedState implements CircuitBreakerState { private final AtomicBoolean isClosed; ClosedState() { - this.circuitBreakerMetrics = CircuitBreakerMetrics.forClosed(getCircuitBreakerConfig(), clock); + this.circuitBreakerMetrics = CircuitBreakerMetrics.forClosed(getCircuitBreakerConfig()); this.isClosed = new AtomicBoolean(true); } @@ -828,7 +804,7 @@ private class DisabledState implements CircuitBreakerState { DisabledState() { this.circuitBreakerMetrics = CircuitBreakerMetrics - .forDisabled(getCircuitBreakerConfig(), clock); + .forDisabled(getCircuitBreakerConfig()); } /** @@ -899,7 +875,7 @@ private class MetricsOnlyState implements CircuitBreakerState { MetricsOnlyState() { circuitBreakerMetrics = CircuitBreakerMetrics - .forMetricsOnly(getCircuitBreakerConfig(), clock); + .forMetricsOnly(getCircuitBreakerConfig()); isFailureRateExceeded = new AtomicBoolean(false); isSlowCallRateExceeded = new AtomicBoolean(false); } @@ -995,7 +971,7 @@ private class ForcedOpenState implements CircuitBreakerState { ForcedOpenState(int attempts) { this.attempts = attempts; - this.circuitBreakerMetrics = CircuitBreakerMetrics.forForcedOpen(circuitBreakerConfig, clock); + this.circuitBreakerMetrics = CircuitBreakerMetrics.forForcedOpen(circuitBreakerConfig); } /** @@ -1074,7 +1050,7 @@ private class HalfOpenState implements CircuitBreakerState { int permittedNumberOfCallsInHalfOpenState = circuitBreakerConfig .getPermittedNumberOfCallsInHalfOpenState(); this.circuitBreakerMetrics = CircuitBreakerMetrics - .forHalfOpen(permittedNumberOfCallsInHalfOpenState, getCircuitBreakerConfig(), clock); + .forHalfOpen(permittedNumberOfCallsInHalfOpenState, getCircuitBreakerConfig()); this.permittedNumberOfCalls = new AtomicInteger(permittedNumberOfCallsInHalfOpenState); this.isHalfOpen = new AtomicBoolean(true); this.attempts = attempts; diff --git a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerMetricsTest.java b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerMetricsTest.java index 4b9227758..a3faa553a 100644 --- a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerMetricsTest.java +++ b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerMetricsTest.java @@ -34,10 +34,11 @@ public class CircuitBreakerMetricsTest { public void testCircuitBreakerMetrics() { CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() .slidingWindow(10, 10, CircuitBreakerConfig.SlidingWindowType.COUNT_BASED) + .clock(MockClock.at(2019, 1, 1, 12, 0, 0, ZoneId.of("UTC"))) .build(); CircuitBreakerMetrics circuitBreakerMetrics = CircuitBreakerMetrics - .forClosed(circuitBreakerConfig, MockClock.at(2019, 1, 1, 12, 0, 0, ZoneId.of("UTC"))); + .forClosed(circuitBreakerConfig); circuitBreakerMetrics.onSuccess(0, TimeUnit.NANOSECONDS); circuitBreakerMetrics.onSuccess(0, TimeUnit.NANOSECONDS); diff --git a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachineTest.java b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachineTest.java index 31c8090fb..4b2272891 100644 --- a/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachineTest.java +++ b/resilience4j-circuitbreaker/src/test/java/io/github/resilience4j/circuitbreaker/internal/CircuitBreakerStateMachineTest.java @@ -66,7 +66,7 @@ public class CircuitBreakerStateMachineTest { @Before @SuppressWarnings("unchecked") - public void setUp() { + public void setUp() { mockOnSuccessEventConsumer = (EventConsumer) mock(EventConsumer.class); mockOnErrorEventConsumer = (EventConsumer) mock(EventConsumer.class); mockOnStateTransitionEventConsumer = (EventConsumer) mock(EventConsumer.class); @@ -83,7 +83,8 @@ public void setUp() { .waitDurationInOpenState(Duration.ofSeconds(5)) .ignoreExceptions(NumberFormatException.class) .currentTimestampFunction(clock -> clock.instant().toEpochMilli(), TimeUnit.MILLISECONDS) - .build(), mockClock); + .clock(mockClock) + .build()); } @Test @@ -427,7 +428,8 @@ public void shouldTransitionToHalfOpenAfterWaitInterval() { .permittedNumberOfCallsInHalfOpenState(4) .waitIntervalFunctionInOpenState(IntervalFunction.ofExponentialBackoff(5000L)) .recordException(error -> !(error instanceof NumberFormatException)) - .build(), mockClock); + .clock(mockClock) + .build()); // Initially the CircuitBreaker is open intervalCircuitBreaker.transitionToOpenState();