hibernate-reactive
hibernate-reactive copied to clipboard
Mutiny.SessionFactory#withTransaction doesn't propagate the error sometimes
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/
This relates to what we were discussing recently, @DavideD:
- should we expose our trampoline to users, or
- does Mutiny have some alternative built in?
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?
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 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.
I don't believe it does, but surely @jponge can help with this question
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.
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.
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...
I'd be more than happy to have a look @gavinking, what's the pointer?
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.
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