Skip to content

Semaphore Bulkhead Still Executes Request Event If Call Is Rejected #168

@wuyongc

Description

@wuyongc

1、SpringBoot2.7.8+SpringCloud2021.0.4+SpringCloudAlibaba2021.0.4.0 , Use OpenFeign + Resilience4J + Bulkhead to achieve a fuse and downgrade

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
        </dependency>
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-bulkhead</artifactId>
        </dependency>

2、I was trying to setup a sample with resilience4j semaphore bulkhead support with maxConcurrentCalls set as 10.

resilience4j.bulkhead:
    instances:
        backendA:
            maxConcurrentCalls: 10
resilience4j:
  circuitbreaker:
    configs:
      default:
        slidingWindowSize: 10
        slidingWindowType: TIME_BASED
        failureRateThreshold: 50
        minimumNumberOfCalls: 5
        waitDurationInOpenState: 10s
        writablestacktraceEnabled: false

3、Resilience4j Bulkhead setting Semaphorebulkhead mode

spring:
  cloud:
    circuitbreaker:
      resilience4j:
        enableSemaphoreDefaultBulkhead: true

4、Openfeign server simulation error

    @GetMapping(path = "health")
    public String health() {
        try {
            int i = 1/0;
        } catch (Exception e) {
            System.out.println("111");
            throw new RuntimeException("111");
        }
        return "ok";
    }

5、openfeign client

@EcsFeignClient(name = "console", url = "http://127.0.0.1:8095")
public interface TestFeign {

    @GetMapping("health")
    String health();

}

6、After the service melting, remote calls can still be generated, accessing remote services

executing in thread : ForkJoinPool.commonPool-worker-1
executing in thread : ForkJoinPool.commonPool-worker-2
exception io.github.resilience4j.bulkhead.BulkheadFullException: Bulkhead 'TestFeignhealthz' is full and does not permit further calls

7、On debugging Resilience4jBulkheadProvider, found that CompletableFuture asyncCall = CompletableFuture.supplyAsync(supplier); started a forkjoinpool thread and backend code execution was initiated even before the semaphore check was done.

I was under the impression that the backend code will be called only if semaphore check was succeeded.

org.springframework.cloud.circuitbreaker.resilience4j.Resilience4jBulkheadProvider#decorateBulkhead

	private <T> Supplier<CompletionStage<T>> decorateBulkhead(final String id,
			final io.vavr.collection.Map<String, String> tags, final Supplier<T> supplier) {
		Resilience4jBulkheadConfigurationBuilder.BulkheadConfiguration configuration = configurations
				.computeIfAbsent(id, defaultConfiguration);

		if (semaphoreDefaultBulkhead
				|| (bulkheadRegistry.find(id).isPresent() && !threadPoolBulkheadRegistry.find(id).isPresent())) {
			Bulkhead bulkhead = bulkheadRegistry.bulkhead(id, configuration.getBulkheadConfig(), tags);
			CompletableFuture<T> asyncCall = CompletableFuture.supplyAsync(supplier);
			return Bulkhead.decorateCompletionStage(bulkhead, () -> asyncCall);
		}
		else {
			ThreadPoolBulkhead threadPoolBulkhead = threadPoolBulkheadRegistry.bulkhead(id,
					configuration.getThreadPoolBulkheadConfig(), tags);
			return threadPoolBulkhead.decorateSupplier(supplier);
		}
	}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions