question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Blocking call detected in MonoRateLimiter

See original GitHub issue

Resilience4j version: 1.5.0 Java version: 13

Test project: reactive-client.zip

While evaluating resilience4j rate limiter for our reactive WebClient we observed a blocking call coming from RateLimiter.reservePermission

reactor.blockhound.BlockingOperationError: Blocking call! jdk.internal.misc.Unsafe#park
	at jdk.internal.misc.Unsafe.park(Unsafe.java) ~[?:?]
	at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:359) ~[?:?]
	at io.github.resilience4j.ratelimiter.internal.AtomicRateLimiter.compareAndSet(AtomicRateLimiter.java:185) ~[resilience4j-ratelimiter-1.5.0.jar:1.5.0]
	at io.github.resilience4j.ratelimiter.internal.AtomicRateLimiter.updateStateWithBackOff(AtomicRateLimiter.java:163) ~[resilience4j-ratelimiter-1.5.0.jar:1.5.0]
	at io.github.resilience4j.ratelimiter.internal.AtomicRateLimiter.reservePermission(AtomicRateLimiter.java:127) ~[resilience4j-ratelimiter-1.5.0.jar:1.5.0]
	at io.github.resilience4j.ratelimiter.RateLimiter.reservePermission(RateLimiter.java:651) ~[resilience4j-ratelimiter-1.5.0.jar:1.5.0]
	at io.github.resilience4j.reactor.ratelimiter.operator.MonoRateLimiter.subscribe(MonoRateLimiter.java:39) ~[resilience4j-reactor-1.5.0.jar:1.5.0]
	at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) ~[reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.resubscribe(FluxRetryWhen.java:204) ~[reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.FluxRetryWhen$RetryWhenOtherSubscriber.onNext(FluxRetryWhen.java:250) ~[reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:274) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:851) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:241) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:147) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.MonoIgnoreThen$ThenAcceptInner.onNext(MonoIgnoreThen.java:296) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.publisher.MonoDelay$MonoDelayRunnable.run(MonoDelay.java:117) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28) [reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
	at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
	at java.lang.Thread.run(Thread.java:830) [?:?]

Here are our use cases. There are 2 basic patterns we are testing. In both cases we assume that application logic is trying to execute much more than it’s allowed by rate limiter.

  • sequential task - continuously fetch data from the same external source using multiple independent polling tasks that share the same RateLimiter.
        private Flux<String> sequentialTask(TestWebClient client, int ctx) {
		return client.request(ctx)
				.expand(res -> client.request(ctx));
	}
  • parallel task - continuously fetch data from multiple external sources in parallel using. For example, there are multiple users and we want to fetch some portion of data for every user running multiple ‘tasks’ in parallel and then repeat the process.
	private Flux<String> parallelTask(TestWebClient client) {
		return Flux.defer(ReactiveApplication::getUsers)
				.flatMapDelayError(client::request, 10, 1)
				.repeat();
	}

	private Flux<Integer> getUsers() {
		return Flux.range(1, 100);
	}

Web client

	public Mono<String> request(int context) {
		return webClient.get()
				.uri(uriBuilder -> uriBuilder
						.path("/test")
						.queryParam("ctx", context)
						.build()
				)
				.exchange()
				.transformDeferred(RateLimiterOperator.of(rateLimiter))
				.flatMap(clientResponse -> {
					if (clientResponse.statusCode() == HttpStatus.OK) {
						return clientResponse.bodyToMono(String.class);
					} else {
						return Mono.error(new HttpClientErrorException(clientResponse.statusCode(), clientResponse.toString()));
					}
				})
				.retryWhen(Retry
						.fixedDelay(Long.MAX_VALUE, Duration.ofMillis(5000))
						.doBeforeRetry(retrySignal -> log.warn(retrySignal.toString(), retrySignal.failure()))
				);
	}

	private WebClient webClient(String baseUrl) {
		return WebClient.builder()
				.baseUrl(baseUrl)
				.filter(logRequest())
				.filter(logResponse())
				.build();
	}

	private RateLimiter rateLimiter() {
		RateLimiterConfig config = RateLimiterConfig.custom()
				.limitRefreshPeriod(Duration.ofSeconds(1))
				.limitForPeriod(1)
				.timeoutDuration(Duration.ofSeconds(1))
				.build();

		RateLimiter rateLimiter = RateLimiterRegistry
				.of(config)
				.rateLimiter("web-client");

		return rateLimiter;
	}

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:1
  • Comments:12 (6 by maintainers)

github_iconTop GitHub Comments

2reactions
storozhukBMcommented, Jun 30, 2020

@amrynsky @RobWin Yes, this backoff is here to resolve some atomic swap under big contention issues. Our tests show that is increases throughput under contention up to 2 times. On my machine, it goes from 8 permission acquires per microsecond to 14, on machines with more cores gains are even more significant, but this is only necessary for Java 8, starting from version 9 it is already fixed in the JVM itself. This parkNanos call will not actually “block” your thread, but razer gives OS scheduler a hint to run some other thread else instead and return to it as soon as possible. I think it is totally fine to add our AtomicRateLimiter.compareAndSet to BlockHound allow-list. If you take a look at https://github.com/reactor/BlockHound/blob/master/docs/tips.md#how-to-select-what-to-whitelist you can see that our method totally qualifies with this documentation. It does not call any user-provided code, and we know how it will behave and that it will not block worker thread for any prolonged time but would just reschedule itself, and we can safely whitelist it.

1reaction
amrynskycommented, Jun 30, 2020

Added compareAndSet to whitelist. Here is my configuration for reference

BlockHound.install(builder -> builder
		.allowBlockingCallsInside(io.github.resilience4j.ratelimiter.internal.AtomicRateLimiter.class.getName(), "compareAndSet")
);

@storozhukBM @RobWin thanks for your help

Read more comments on GitHub >

github_iconTop Results From Across the Web

BlockHound: detect blocking calls in Reactive code before it's ...
The first test method verifies that blocking code is not allowed when executed from within a Non-Blocking thread belonging to the Schedulers.parallel() ...
Read more >
Detect and block spam phone calls - Apple Support
Detect and block spam phone calls. You can use Silence Unknown Callers or a third-party app to block spam calls on your iPhone....
Read more >
Call Blocking Tools and Resources
Call blocking is a tool used by phone companies to stop illegal and unwanted calls from reaching your phone.
Read more >
How To Block Unwanted Calls | Consumer Advice
What Are Call Blocking and Call Labeling? Block Calls on a Cell Phone; Block Calls on a Home Phone That Uses the Internet...
Read more >
Avoid unwanted calls with Verizon Call Filter FAQs
Call Filter lets you screen incoming calls, block spam and report unwanted numbers. ... Call Filter spam detection and call blocking. Expand All ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found