New feature: attach stack trace?
For debug purposes, it would be nice to add a mode to Mutiny which attaches stack traces to exceptions, to record where we created the Uni, rather than when it failed, which often doesn't have the right stack trace.
In the past, I've used this extensively:
private static <V> CompletionStage<V> attachStackTrace(CompletionStage<V> cs) {
Throwable exception = new RxException("Rx operation failed");
return cs.exceptionally(x -> {
exception.initCause(x);
rethrow(exception);
return null;
});
}
But we should make this automatic, perhaps with a global setting/config?
I really want this feature. However, it would require the Stack Walker API (JAva 9). Still trying to find a good way to handle both Java 8 and Java 11.
Why, the strategy I just showed works fine.
StackWalker is good when you want to filter out elements like reflection, and when you want to be sure to get the complete stack trace because the VM can in theory drop stack elements.
Otherwise the pre-Java 9 technique that @FroMage suggests is all we need.
I've done a first attempt and the result was not great. I will try to see how it can be improved.
I've done some experiments and it's a can of worms 😏
Here are some notes and brain dump.
It's not too hard to rewrite stack traces, but you can't simply presume that the top of the stack minus 1 is where you where actually called in the source code. Synthetic methods can mix in, and the group / operator nature of the Mutiny API does complicate things, not to mention that some operators are sugar on top of other operators. Still, with the name of the operator you are in you can safely descend from the top of the stack and you should be on the good stack frame.
Another problem is how to keep track of the captured stack frames, and make sure that you don't excessively capture stack frames (again, sugar operators make it more complex). This requires some invasive changes in the internals.
Next thing is how to propagate exceptions. Should you propagate the real exception and attach the stacktrace capture? Or should you propagate a wrapper exception? And in the later case what happens if you are in a sugar operator? (hint: you could propagate a rewriting stack trace which is not that from the user code but that from the sugar operator implementation.
Last but not least depending on what happens when an exception is being propagated we may need to double the tests suite to cover the cases where we have no rewritings and the cases where we do rewrite, possibly forwarding a wrapping exception.
Perhaps it's just me who's had inconclusive ideas for the moment, so I'll let it aside for a while and come back to it later to see if I can come up with another angle at that problem.
Nevertheless I'd like to make sure we don't render the code base unnecessarily complicated. It'd be nice to have that feature, but not at any cost.
And let's not forget that given this:
Uni.createFrom().item(123)
.call(() -> {
throw new RuntimeException("woops");
})
.subscribe().with(
value -> System.out.println(value),
err -> err.printStackTrace());
...well the stack trace points to the actual place where the failure happened:
java.lang.RuntimeException: woops
at io.smallrye.mutiny.helpers.stacktrace.UniStackFrameRewritingTest.lambda$basic$0(UniStackFrameRewritingTest.java:14)
at io.smallrye.mutiny.groups.UniOnItem.lambda$call$3(UniOnItem.java:120)
at io.smallrye.mutiny.groups.UniOnItem.lambda$call$2(UniOnItem.java:99)
at io.smallrye.mutiny.operators.uni.UniOnItemTransformToUni$UniOnItemTransformToUniProcessor.performInnerSubscription(UniOnItemTransformToUni.java:68)
at io.smallrye.mutiny.operators.uni.UniOnItemTransformToUni$UniOnItemTransformToUniProcessor.onItem(UniOnItemTransformToUni.java:57)
at io.smallrye.mutiny.operators.uni.builders.UniCreateFromKnownItem$KnownItemSubscription.forward(UniCreateFromKnownItem.java:38)
at io.smallrye.mutiny.operators.uni.builders.UniCreateFromKnownItem$KnownItemSubscription.access$100(UniCreateFromKnownItem.java:26)
at io.smallrye.mutiny.operators.uni.builders.UniCreateFromKnownItem.subscribe(UniCreateFromKnownItem.java:23)
(...)
It's just 1 example and you can easily come up with a problematic stack trace example, but not everything is bad with the current implementation.
I removed this from 0.15.0.