hibernate-reactive icon indicating copy to clipboard operation
hibernate-reactive copied to clipboard

Mutiny.SessionFactory#withTransaction doesn't propagate the error sometimes

Open DavideD opened this issue 2 years ago • 12 comments

See the topic on Zulip

DavideD avatar Nov 29 '23 15:11 DavideD

I've tried the following test:


	@Test
	public void testExceptionPropagation(VertxTestContext context) {
		test( context, getMutinySessionFactory()
				.withTransaction( session -> {
					int loop = 5000;
					Uni<?> uni = Uni.createFrom().voidItem();
					for ( int i = 0; i < loop; i++ ) {
						final int index = i;
						uni = uni.invoke( () -> System.out.println( index ) );
					}
					System.out.println("Finished chaining uni");
					return uni;
				} )
				.invoke( () -> System.out.println("End") )
		);
	}

and it gets stuck until until the timeout is reached:

The test execution timed out. Make sure your asynchronous code includes calls to either VertxTestContext#completeNow(), VertxTestContext#failNow() or Checkpoint#flag()

Unsatisfied checkpoints diagnostics:

java.util.concurrent.TimeoutException: The test execution timed out. Make sure your asynchronous code includes calls to either VertxTestContext#completeNow(), VertxTestContext#failNow() or Checkpoint#flag()

Unsatisfied checkpoints diagnostics:

	at io.vertx.junit5.VertxExtension.joinActiveTestContexts(VertxExtension.java:216)
	at io.vertx.junit5.VertxExtension.interceptTestMethod(VertxExtension.java:143)
	at 

The same test passes if the loop variable is smaller. It also works when we use a trampoline:

	@Test
	public void testExceptionPropagationWithLoop(VertxTestContext context) {
		test( context, getMutinySessionFactory()
				.withTransaction( session -> Uni.createFrom().completionStage( loop(
						0, 50000, i -> true, i -> {
							System.out.println( i );
							return completedFuture( i );
						}
				) ) )
				.invoke( () -> System.out.println("End") )
		);
	}

I think what's happening is that the number of chained uni is too big and Mutiny doesn't throw any exception and gets stuck instead (completion stages would throw a StackOverflow exception).

@tsegismont, any idea about how to make the error clearer to the user? Maybe there's an option in Mutiny? Thanks

I've added the tests to the latest main here: https://github.com/DavideD/hibernate-reactive/commits/1808/

DavideD avatar Jun 02 '25 14:06 DavideD

This relates to what we were discussing recently, @DavideD:

  • should we expose our trampoline to users, or
  • does Mutiny have some alternative built in?

gavinking avatar Jun 02 '25 14:06 gavinking

should we expose our trampoline to users, or

I think we should do it. We need to have a version for Mutiny and one for Stage (at least the API).

does Mutiny have some alternative built in?

I don't think it does. But what I don't understand is: why does it get stuck without printing any error?

DavideD avatar Jun 02 '25 14:06 DavideD

I took a look at the reproducer. It gets stuck because the chain of unis is too big and a StackOverflowError is thrown.

The reason why the error is not displayed is it's caught in java.util.concurrent.CompletableFuture#uniWhenComplete:

            } catch (Throwable ex) {
                if (x == null)
                    x = ex;
                else if (x != ex)
                    x.addSuppressed(ex);
            }
            completeThrowable(x, r);

It's caught after the CompletableFuture has been completed, when a callback is invoked.

You can observe the same behavior with this:

		CompletableFuture<Object> cf = CompletableFuture.completedFuture( null )
				.whenComplete( (v, ex) -> {
					throw new AssertionError();
				} );

		// Reaches this point

		System.out.println( "cf = " + cf );

And you will see:

cf = java.util.concurrent.CompletableFuture@2a8d172f[Completed exceptionally: java.util.concurrent.CompletionException: java.lang.AssertionError]

tsegismont avatar Jun 04 '25 13:06 tsegismont

@tsegismont The question is: does Mutiny provide any way to write loops without causing stack overflows? Maybe ... something involving Multi? I dunno...

If it doesn't, then we're considering exposing our trampoline that we use internally.

gavinking avatar Jun 04 '25 13:06 gavinking

I don't believe it does, but surely @jponge can help with this question

tsegismont avatar Jun 04 '25 14:06 tsegismont

I'm afraid we don't have any helper for deep pipelines, which is what's been built with:

for ( int i = 0; i < loop; i++ ) {
  final int index = i;
  uni = uni.invoke( () -> System.out.println( index ) );
}

The stack overflow happens at subscription time, because each intermediate step needs to subscribe to the next, etc.

Feel-free to open an issue on Mutiny if you want me to have another look at that known issue one of these days.

jponge avatar Jun 05 '25 12:06 jponge

Also see https://github.com/reactor/reactor-core/issues/3212, our friends at Reactor have the same kind of problems, and I pretty much agree with their conclusions.

jponge avatar Jun 05 '25 13:06 jponge

I'm afraid we don't have any helper for deep pipelines

Right but we do. So either we can expose that to users ourselves or ... you guys could copy/paste it into Mutiny...

gavinking avatar Jun 05 '25 18:06 gavinking

I'd be more than happy to have a look @gavinking, what's the pointer?

jponge avatar Jun 05 '25 19:06 jponge

https://github.com/hibernate/hibernate-reactive/blob/main/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/impl/CompletionStages.java

But note that this is not in any way an API.

This is just a collection of utility methods we use internally.

Because we have lots of need for looping.

gavinking avatar Jun 05 '25 19:06 gavinking

Thanks, also looking at https://github.com/hibernate/hibernate-reactive/blob/main/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/async/impl/AsyncTrampoline.java

jponge avatar Jun 05 '25 19:06 jponge