smallrye-mutiny icon indicating copy to clipboard operation
smallrye-mutiny copied to clipboard

New feature: attach stack trace?

Open FroMage opened this issue 5 years ago • 7 comments

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;
        });
    }

FroMage avatar Jun 23 '20 08:06 FroMage

But we should make this automatic, perhaps with a global setting/config?

FroMage avatar Jun 23 '20 08:06 FroMage

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.

cescoffier avatar Jun 23 '20 09:06 cescoffier

Why, the strategy I just showed works fine.

FroMage avatar Jun 23 '20 12:06 FroMage

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.

jponge avatar Jun 30 '20 20:06 jponge

I've done a first attempt and the result was not great. I will try to see how it can be improved.

cescoffier avatar Jul 01 '20 10:07 cescoffier

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.

jponge avatar Feb 22 '21 19:02 jponge

I removed this from 0.15.0.

jponge avatar Mar 11 '21 13:03 jponge