【问题标题】:WebTestClient returning IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in threadWebTestClient 返回 IllegalStateException: block()/blockFirst()/blockLast() 是阻塞的,线程不支持
【发布时间】:2021-08-04 15:27:58
【问题描述】:

如下函数:

private Boolean canDoIt(Parameter param) {
  return myService
      .getMyObjectInReactiveWay(param)
      .map(myObject -> myService.checkMyObjectInImperativeWay(myObject))
      .block();
}

在运行时工作正常,但是在使用 WebTestClient 测试使用它的流时,我收到以下错误:

java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread parallel-1
    at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83) ~[reactor-core-3.4.1.jar:3.4.1]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Assembly trace from producer [reactor.core.publisher.MonoFlatMap] :
    reactor.core.publisher.Mono.flatMap

我知道我不应该使用block(),但我别无选择:该函数必须返回Boolean(而不是Mono<Boolean>)。也许有另一种不使用block()的编写方式。

有没有办法让WebTestClient 不抛出该错误?

使用 Reactor Core 版本3.4.6

【问题讨论】:

  • 问题是 Reactor 实际上禁止在未明确标记为与阻塞代码兼容的调度程序中调用 block。我没有找到详细的文档,但我认为。在块调用之前,您必须在通量上使用 subscribeOn(Schedulers.boundedElastic()).share() 方法。行为上的差异可能来自这样一个事实,在一种情况下,块函数在当前线程(非反应调度程序)中触发管道,但在您的测试中,您尝试从反应堆栈调用 canDoIt,因此执行线程来自一个反应式调度程序。
  • 我用subscribeOnshare 都试过了,但我仍然得到同样的错误。请注意,WebClient 不会出现错误,它只会出现在WebTestClient
  • 也许我错过/误解了一些东西。您可以尝试使用 share 和 subscribeOn 进行编辑吗?此外,使用 WebTestClient 和 flatMap 的失败代码会很好。这将有助于我进一步挖掘并调整我的答案。
  • 另外,我做了一个相当通用的答案,因为我不太确定,但如果我有失败的代码(好吧,一个最小的可重现示例),我将能够制作一个答案真正专注于您的用例。

标签: java reactive-programming spring-webflux project-reactor webtestclient


【解决方案1】:

假设您无法修改 checkMyObjectInImperativeWay 以返回 Mono:

   private Boolean canDoIt(Parameter param) {
        final AtomicBoolean result= new AtomicBoolean();
        myService.getMyObjectInReactiveWay(param)
                .map(myObject -> myService.checkMyObjectInImperativeWay(myObject))
                .subscribe((mono) -> result.set(mono));
        return result.get();
    }

【讨论】:

    【解决方案2】:

    我验证我的评论。 block() 检查调用线程是否与阻塞代码兼容(反应器外部的线程,或特定反应器调度程序的线程,如Schedulers.boundedElastic())。

    有两种方法可以在响应式堆栈中间处理阻塞调用:

    • 在您将阻止的发布者上使用 share 运算符。请注意,共享运算符会在内部缓存该值。
    • 使用scheduleOnpublishOn 在阻塞兼容调度程序上强制执行block() 调用。请注意,不应在直接调用 block() 的发布者上调用此运算符,而应在将“包装”块调用的发布者上调用此运算符(参见下面的示例)。

    一些参考资料:

    还有一个最小的可重现示例(在 v3.4.6 上测试)给出了这个输出:

    Ok context: not running from reactor Threads
    value is true
    Problematic stack: working with scheduler not compatible with blocking call
    ERROR: block()/blockFirst()/blockLast() are blocking, which is not supported in thread parallel-2
    Bad way to subscribe on a blocking compatible scheduler
    ERROR: block()/blockFirst()/blockLast() are blocking, which is not supported in thread parallel-4
    Bad way to publish on blocking compatible scheduler
    ERROR: block()/blockFirst()/blockLast() are blocking, which is not supported in thread parallel-6
    Possible workaround: share the reactive stream before blocking on it
    It worked
    Right way to subscribe on blocking compatible scheduler
    It worked
    Right way to publish on blocking compatible scheduler
    It worked
    

    代码来了:

    import reactor.core.publisher.Mono;
    import reactor.core.scheduler.Schedulers;
    
    import java.time.Duration;
    import java.util.concurrent.Callable;
    import java.util.function.Supplier;
    
    public class BlockingWorkaround {
    
        public static void main(String[] args) throws Exception {
            System.out.println("Ok context: not running from reactor Threads");
            System.out.println("value is "+blockingFunction());
    
            System.out.println("Problematic stack: working with scheduler not compatible with blocking call");
            executeAndWait(() -> blockingFunction());
    
            System.out.println("Bad way to subscribe on a blocking compatible scheduler");
            executeAndWait(() -> blockingFunctionUsingSubscribeOn());
    
            System.out.println("Bad way to publish on blocking compatible scheduler");
            executeAndWait(() -> blockingFunctionUsingPublishOn());
    
            System.out.println("Possible workaround: share the reactive stream before blocking on it");
            executeAndWait(() -> blockingFunctionShared());
    
            System.out.println("Right way to subscribe on blocking compatible scheduler");
            subscribeOnAndWait(() -> blockingFunction());
    
            System.out.println("Right way to publish on blocking compatible scheduler");
            publishOnAndWait(() -> blockingFunction());
        }
    
        static Boolean blockingFunction() {
            return delay()
                    .flatMap(delay -> Mono.just(true))
                    .block();
        }
    
        static Boolean blockingFunctionShared() {
            return delay()
                    .flatMap(delay -> Mono.just(true))
                    .share() // Mono result is cached internally
                    .block();
        }
    
        static Boolean blockingFunctionUsingSubscribeOn() {
            return delay()
                    .subscribeOn(Schedulers.boundedElastic())
                    .flatMap(delay -> Mono.just(true))
                    .block();
        }
    
        static Boolean blockingFunctionUsingPublishOn() {
            return delay()
                    .flatMap(delay -> Mono.just(true))
                    .publishOn(Schedulers.boundedElastic())
                    .block();
        }
    
        static Mono<Long> delay() {
            return Mono.delay(Duration.ofMillis(10));
        }
    
        private static void executeAndWait(Supplier<Boolean> blockingAction) throws InterruptedException {
            delay()
                    .map(it -> blockingAction.get())
                    .subscribe(
                            val -> System.out.println("It worked"),
                            err -> System.out.println("ERROR: " + err.getMessage())
                    );
    
            Thread.sleep(100);
        }
    
        private static void subscribeOnAndWait(Callable<Boolean> blockingAction) throws InterruptedException {
            final Mono<Boolean> blockingMono = Mono.fromCallable(blockingAction)
                    .subscribeOn(Schedulers.boundedElastic()); // Upstream is executed on given scheduler
    
            delay()
                    .flatMap(it -> blockingMono)
                    .subscribe(
                            val -> System.out.println("It worked"),
                            err -> System.out.println("ERROR: " + err.getMessage())
                    );
    
            Thread.sleep(100);
        }
    
        private static void publishOnAndWait(Supplier<Boolean> blockingAction) throws InterruptedException {
            delay()
                    .publishOn(Schedulers.boundedElastic()) // Cause downstream to be executed on given scheduler
                    .map(it -> blockingAction.get())
                    .subscribe(
                            val -> System.out.println("It worked"),
                            err -> System.out.println("ERROR: " + err.getMessage())
                    );
    
            Thread.sleep(100);
        }
    }
    

    【讨论】:

    • 恐怕我看不到这个解决方案如何满足我的需求:在您的subscribeOnAndWait 中您使用Mono&lt;Boolean&gt; 并返回void,我需要向外部返回Boolean来电者。
    • 我的示例认为blockingFunction 等同于您的canDoIt 函数。当从响应式上下文调用此函数时,它可能是您看到的错误。为了避免这种情况,对函数的调用必须包含在 Mono 中(在我的示例中为 blockingMono),并且 Mono 必须在有界弹性调度程序上调度。请注意,如果无法识别或修改将您的 canDoIt 函数注入反应式上下文的位置,事情就会变得更加复杂。
    猜你喜欢
    • 2019-04-15
    • 2022-07-19
    • 2020-04-21
    • 1970-01-01
    • 2018-12-29
    • 1970-01-01
    • 2016-11-27
    • 2014-12-20
    • 1970-01-01
    相关资源
    最近更新 更多