Skip to content

Commit

Permalink
Merge pull request #203 from santoshdahal12/disable-time-limiter-conf…
Browse files Browse the repository at this point in the history
…igurable-by-id-or-group

Fixes #202 issue.
  • Loading branch information
Ryan Baxter authored Jul 29, 2024
2 parents b6d4a62 + 4079624 commit 39609c3
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,21 @@ spring:
disable-time-limiter: true
----

This type of option is only provided on a global scope within the `spring-cloud-circuitbreaker` and applies to the
basic and to the reactive circuitbreaker implementation.
`TimeLimiter` can also be disabled for specific group or instances using `spring.cloud.circuitbreaker.resilience4j.disable-time-limiter-map` as below:

[source,yaml]
----
spring:
cloud:
circuitbreaker:
resilience4j:
disable-time-limiter-map:
group1: true
instanceA: false
instanceB: true
----
These options for disabling `TimeLimit` are provided within `spring-cloud-circuitbreaker` and applies to both basic and reactive circuitbreaker implementation.

Priority order of disabling is : `instance` > `group` > `global`. Based on above configuration, `instanceB` and `group1` have `TimeLimiter` disabled but `instanceA` has enabled. For all other instances and groups, it will fall back to globally set value of `spring.cloud.circuitbreaker.resilience4j.disable-time-limiter`
If `spring.cloud.circuitbreaker.resilience4j.disable-time-limiter` is not set, by default, `TimeLimit` is enabled for remaining circuitbreaker instances and groups.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* 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
*
* https://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 org.springframework.cloud.circuitbreaker.resilience4j;

import java.util.Map;

/**
*
* A util class related to configuration properties class.
*
* @author Santosh Dahal
*/
public final class ConfigurationPropertiesUtils {

private ConfigurationPropertiesUtils() {
}

/**
* Determines if the TimeLimiter should be disabled. First priority is operation or
* method,then service and if not found returns disable-time-limit.
* @param resilience4JConfigurationProperties configuration properties set
* @param id operation or method name
* @param groupName service group name
* @return value set for id, groupName or default value of disable-time-limit and
* {@code false} otherwise
*/
static boolean isDisableTimeLimiter(Resilience4JConfigurationProperties resilience4JConfigurationProperties,
String id, String groupName) {

if (resilience4JConfigurationProperties == null) {
return false;
}
Map<String, Boolean> disableTimeLimiterMap = resilience4JConfigurationProperties.getDisableTimeLimiterMap();
if (disableTimeLimiterMap.containsKey(id)) {
return disableTimeLimiterMap.get(id);
}
if (disableTimeLimiterMap.containsKey(groupName)) {
return disableTimeLimiterMap.get(groupName);
}
return resilience4JConfigurationProperties.isDisableTimeLimiter();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,10 @@ public ReactiveCircuitBreaker create(String id, String groupName) {
.circuitBreakerConfig(circuitBreakerConfig)
.timeLimiterConfig(timeLimiterConfig)
.build();
boolean isDisableTimeLimiter = ConfigurationPropertiesUtils
.isDisableTimeLimiter(this.resilience4JConfigurationProperties, id, groupName);
return new ReactiveResilience4JCircuitBreaker(id, groupName, config, circuitBreakerRegistry,
timeLimiterRegistry, Optional.ofNullable(circuitBreakerCustomizers.get(id)), isDisableTimeLimiter());
}

private boolean isDisableTimeLimiter() {
if (resilience4JConfigurationProperties != null) {
return resilience4JConfigurationProperties.isDisableTimeLimiter();
}
return false;
timeLimiterRegistry, Optional.ofNullable(circuitBreakerCustomizers.get(id)), isDisableTimeLimiter);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,11 @@ private Resilience4JCircuitBreaker create(String id, String groupName,
bulkheadProvider);
}
else {
boolean isDisableTimeLimiter = ConfigurationPropertiesUtils
.isDisableTimeLimiter(this.resilience4JConfigurationProperties, id, groupName);
return new Resilience4JCircuitBreaker(id, groupName, circuitBreakerConfig, timeLimiterConfig,
circuitBreakerRegistry, timeLimiterRegistry, circuitBreakerExecutorService,
Optional.ofNullable(circuitBreakerCustomizers.get(id)), bulkheadProvider,
this.resilience4JConfigurationProperties.isDisableTimeLimiter());
Optional.ofNullable(circuitBreakerCustomizers.get(id)), bulkheadProvider, isDisableTimeLimiter);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package org.springframework.cloud.circuitbreaker.resilience4j;

import java.util.HashMap;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
Expand All @@ -34,6 +37,8 @@ public class Resilience4JConfigurationProperties {

private boolean disableTimeLimiter = false;

private Map<String, Boolean> disableTimeLimiterMap = new HashMap<>();

public boolean isEnableGroupMeterFilter() {
return enableGroupMeterFilter;
}
Expand Down Expand Up @@ -74,4 +79,12 @@ public void setDisableTimeLimiter(boolean disableTimeLimiter) {
this.disableTimeLimiter = disableTimeLimiter;
}

public Map<String, Boolean> getDisableTimeLimiterMap() {
return disableTimeLimiterMap;
}

public void setDisableTimeLimiterMap(Map<String, Boolean> disableTimeLimiterMap) {
this.disableTimeLimiterMap = disableTimeLimiterMap;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ public class Resilience4JAutoConfigurationPropertyTest {
@Autowired
Resilience4JCircuitBreakerFactory factory;

@Autowired
Resilience4JConfigurationProperties configurationProperties;

@Test
public void testCircuitBreakerPropertiesPopulated() {
CircuitBreakerRegistry circuitBreakerRegistry = factory.getCircuitBreakerRegistry();
Expand Down Expand Up @@ -177,4 +180,12 @@ public void testTestGroupInstanceTimeLimiterPropertiesPopulated() {
assertThat(timeLimiter.get().getTimeLimiterConfig().getTimeoutDuration()).isEqualTo(Duration.ofMillis(600));
}

@Test
public void testDisableTimeLimiterMapPropertiesPopulated() {
assertThat(configurationProperties.getDisableTimeLimiterMap().get("group1")).isTrue();
assertThat(configurationProperties.getDisableTimeLimiterMap().get("instanceB")).isTrue();
assertThat(configurationProperties.getDisableTimeLimiterMap().get("instanceA")).isFalse();
assertThat(configurationProperties.isDisableTimeLimiter()).isFalse();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.springframework.cloud.circuitbreaker.resilience4j;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -236,7 +238,36 @@ public void runWithDisabledTimeLimiter() {
.create("foo");
assertThat(cb.run(() -> {
try {
/* sleep longer than limit limit allows us to */
/* sleep longer than limit allows us to */
TimeUnit.MILLISECONDS
.sleep(Math.max(timeLimiterRegistry.getDefaultConfig().getTimeoutDuration().toMillis(), 100L) * 2);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("thread got interrupted", e);
}
return "foobar";
})).isEqualTo("foobar");
}

/**
* Run circuit breaker with default time limiter map of two circuit breaker instances
* and exceed time limit. Due to the disabled time limiter execution, everything
* should finish without errors for foo2.
*/
@Test
public void runWithDisabledTimeLimiterForASpecificInstance() {
Map<String, Boolean> disableTimeLimiterMap = new HashMap<>();
disableTimeLimiterMap.put("foo1", false);
disableTimeLimiterMap.put("foo2", true);
properties.setDisableTimeLimiter(true);
final TimeLimiterRegistry timeLimiterRegistry = TimeLimiterRegistry.ofDefaults();
CircuitBreaker cb = new Resilience4JCircuitBreakerFactory(CircuitBreakerRegistry.ofDefaults(),
timeLimiterRegistry, null, properties)
.create("foo2");
assertThat(cb.run(() -> {
try {
/* sleep longer than limit allows us to */
TimeUnit.MILLISECONDS
.sleep(Math.max(timeLimiterRegistry.getDefaultConfig().getTimeoutDuration().toMillis(), 100L) * 2);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ resilience4j.timelimiter.instances.test_circuit.timeout-duration=18s

resilience4j.thread-pool-bulkhead.instances.test_circuit.max-thread-pool-size=100
resilience4j.bulkhead.instances.test_circuit.max-concurrent-calls=50

spring.cloud.circuitbreaker.resilience4j.disableTimeLimiterMap.group1=true
spring.cloud.circuitbreaker.resilience4j.disable-time-limiter-map.instanceA=false
spring.cloud.circuitbreaker.resilience4j.disableTimeLimiterMap.instanceB=true

0 comments on commit 39609c3

Please sign in to comment.