<div dir="ltr">Moved to loom-dev; core-libs-dev to bcc.</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Tue, Jul 9, 2024 at 12:10 PM Alan Bateman <<a href="mailto:Alan.Bateman@oracle.com" target="_blank">Alan.Bateman@oracle.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><u></u>

  
  <div>
    Probably best to bring this to loom-dev as there have been some
    exploration into but where we decided not to expose any APIs at this
    time.<br>
    <br>
    -Alan<br>
    <br>
    <div>On 09/07/2024 19:50, Louis Wasserman
      wrote:<br>
    </div>
    <blockquote type="cite">
      
      <div dir="ltr">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.<br>
        <br>
        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.<br>
        <br>
        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.  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.<br>
        <br>
        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.<br>
        <br>
        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.<br>
        <div><br>
        </div>
        <span class="gmail_signature_prefix">-- </span><br>
        <div dir="ltr" class="gmail_signature">
          <div dir="ltr">
            <div>Louis Wasserman</div>
          </div>
        </div>
      </div>
    </blockquote>
    <br>
  </div>

</blockquote></div><br clear="all"><div><br></div><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature"><div dir="ltr"><div>Louis Wasserman (he/they)</div></div></div>