More useful structured concurrency stack traces
Alan Bateman
Alan.Bateman at oracle.com
Wed Jul 10 14:59:53 UTC 2024
On 09/07/2024 19:50, Louis Wasserman wrote:
> My understanding of the structured concurrency APIs now in preview is
> that when a subtask is forked, exceptions thrown in that stack trace
> will have stack traces going up to the beginning of that subtask, not
> e.g. up the structured concurrency task tree. (My tests suggest this
> is the case for simple virtual threads without structured
> concurrency.) Most concurrency frameworks on the JVM that I’ve
> encountered share the property that stack traces for exceptions don’t
> trace through the entire causal chain – and, not unrelatedly, that
> developers struggle to debug concurrent applications, especially with
> stack traces from production and not full debuggers attached.
>
> In some cases, like chained CompletableFutures, this seems necessary
> to ensure that executing what amounts to a loop does not result in
> stack traces that grow linearly with the number of chained futures.
> But when structured concurrency is involved, it seems more plausible
> to me that the most useful possible stack traces would go up the tree
> of tasks – that is, whenever a task was forked, the stack trace would
> look roughly as if it were a normal/sequential/direct invocation of
> the task. This could conceivably cause stack overflows where they
> didn’t happen before, but only for code that violates the expectations
> we have around normal sequential code: you can’t recurse unboundedly;
> use iteration instead.
>
> I’m curious if there are ways we could make the upcoming structured
> concurrency APIs give those stack traces all the way up the tree, or
> provide hooks to enable you to do that yourself.
This project has given some thought to this topic, has prototyped a
number of APIs but needs further exploration before deciding whether to
expose anything or not. One of the prototypes allowed a thread to get
the stack traces of the owners of the its enclosing scopes. The owner
(essentially parent thread) is likely to be blocked in join but it may
not be. There were also some thoughts given to captured stack traces or
water marks at fork time. Right now the only thing that is exposed is
the (usually shallow) tree in the JSON thread dump (jcmd <pid>
Thread.dump_to_file -format=json <file>) where you can see the stack
trace of the thread exactly a main tasks and the stack traces of threads
executing the subtasks.
-Alan
> Last year’s JVMLS talk on Continuations Under the Covers demonstrated
> how stacks were redesigned in ways that frequently and efficiently
> snapshot the stack itself – not just the trace, but the thing that
> includes all the variables in use. There’s a linked list of
> StackChunks, and all but maybe the top of the stack has those elements
> frozen, etc, and the top of the stack gets frozen when the thread is
> yielded. Without certainty about how stack traces are managed in the
> JVM today, I would imagine you could possibly do something similar –
> you’d add a way to cheaply snapshot a reference to the current stack
> trace that can be traversed later. If you’re willing to hold on to
> all the references currently on the stack – which might be acceptable
> for the structured concurrency case in particular, where you might be
> able to assume you’ll return to the parent task and its stack at some
> point – you might be able to do this by simply wrapping the existing
> StackChunks. Then, each `fork` or `StructuredTaskScope` creation
> might snapshot the current call stack, and you’d stitch together the
> stack traces later…somewhere. That part is a little more open ended:
> would you add a new variant of `fillInStackTrace`? Would it only
> apply to exceptions that bubbled up to the task scope? Or would we be
> adding new semantics to what happens when you throw an exception or
> walk the stack in general? The most plausible vision I have at this
> point is an API that spawns a virtual thread which receives a stack
> trace of some sort – or perhaps snapshots the current stack trace –
> and prepends that trace to all stack traces within the virtual
> thread’s execution.
>
> I suppose this is doable today if you’re willing to pay the
> performance cost of explicitly getting the current stack trace every
> time you fork a task or start a scope. That is kind of antithetical
> to the point of virtual threads – making forking tasks very efficient
> – but it’s something you might be willing to turn on during testing.
>
> Right now, my inspiration for this question is attempting to improve
> the stack trace situation with Kotlin coroutines, where Google
> production apps have complained about the difficulty of debugging with
> the current stack traces. But this is something I'd expect to apply
> equally well to all JVM languages: the ability to snapshot and string
> together stack trace causal chains like this in production could
> significantly improve the experience of debugging concurrent code.
>
> --
> Louis Wasserman
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20240710/52b7ceed/attachment-0001.htm>
More information about the loom-dev
mailing list