Closed Bug 1142571 Opened 9 years ago Closed 4 years ago

Display asynchronous stacks in the debugger

Categories

(DevTools :: Debugger, enhancement, P5)

x86
macOS
enhancement

Tracking

(Not tracked)

RESOLVED DUPLICATE of bug 1610417

People

(Reporter: ejpbruel, Unassigned)

References

(Blocks 1 open bug, )

Details

Now that bug 1083359 landed, stack traces can have an async parent. It would be very useful to display these asynchronous stacks in the debugger.

For instance, when debugging promise bugs, the number one thing I usually want to know is: where did this promise come from? Having the asynchronous stack would allow me to do so. We don't even need to be able to rematerialize environments on the asynchronous stack. Just having a stack at all would be great.
I think this is a duplicate of bug 981514.  Feel free to undo if I'm wrong!
Status: NEW → RESOLVED
Closed: 9 years ago
Resolution: --- → DUPLICATE
Let's make bug 981514 a meta bug and this bug for work needed in the debugger.
Status: RESOLVED → REOPENED
Resolution: DUPLICATE → ---
CC From Github

__Migrating from [bug 1142571](https://bugzilla.mozilla.org/show_bug.cgi?id=1142571)__

We want to show async stack traces in the debugger.  There is a pref required for Firefox to turn on async stacks, `javascript.options.asyncstack`, this is done because there is a performance penalty associated with this option turned on.  To mitigate this we could offer the option in the stacks column for `Async [ ]` which would toggle this pref. (also likely turning the pref off when the debugger / toolbox is closed)  However we need the server to be updated to send the required frames information.

Load the following into your browser URL bar with the debugger open:

```html
data:text/html,<script>setTimeout(function foo() {setTimeout(function bar() {console.trace();debugger;}, 100);}, 100);</script>
```

You should see a trace with:

-   `bar();`
-   `foo();`

But currently the debugger only shows:

-   `bar();`

These traces are already available in the console via [bug 1200832](https://bugzilla.mozilla.org/show_bug.cgi?id=1200832).
No longer blocks: js-devtools
Product: Firefox → DevTools
Status: REOPENED → NEW
Type: defect → enhancement
Priority: -- → P2
Whiteboard: [debugger-mvp]
Status: NEW → RESOLVED
Closed: 9 years ago5 years ago
Resolution: --- → DUPLICATE
Whiteboard: [debugger-mvp]
Status: RESOLVED → REOPENED
Resolution: DUPLICATE → ---
Status: REOPENED → NEW
Whiteboard: [debugger-mvp]

What's being requested here is actually a much stranger feature than it might seem. It's certainly not a duplicate of dbg-async-stacks. I don't think it's a feature we really want to focus on at the moment, so I'm marking it P5.

Yes, we do have stacks attached to promises when they are created and resolved. Yes, that information could be helpful to developers working in promise-based code. But those stacks cannot be spliced onto the ordinary live call stack in a reasonable way; they're almost the inverse of a call stack.

In an ordinary call stack, a caller is a function call that is temporarily suspended until its callee returns a value. That terminology may sound like I'm describing async/await, but it is actually a perfectly fine description of the synchronous case. In a call stack, only the youngest frame actually runs; all its callers are suspended waiting for their callees.

An async function call is very similar: the awaiter is temporarily suspended waiting for its awaitee to return a value. They're connected via a promise and an internal generator object, rather than just being frames stacked directly on top of each other; but in terms of who is running and who is blocked, who is producing and who is consuming, it's exactly the same as a synchronous call.

So the two cases' common structure is: suspended consumers below running producers.

Now consider the case where we're running a promise callback, attached to the promise with then or catch. A promise records the stack at which it was created, and the stack at which it was resolved. The promise creation stack is not a suspended consumer of the callback's value; it has nothing to do with that. The promise resolution stack does not show a suspended consumer of the callback's value - in fact, it's almost the opposite! It's the code that provided the value the callback is processing, making it more analogous to a callee of the callback than its caller. In the "common structure" described above, it should appear above the callback, not below it. It would be wildly confusing to display it as an extension of the callback's JS stack.

Since then takes a callback and returns a promise of its value, the closest thing to a 'caller' of that callback would be the other callbacks chained on to the promise returned by then. That is a suspended computation (represented as a closure) awaiting the value of the present callback.

Of course, a promise may have any number of callbacks attached to it - but this is true both of pure Promise-based code and of async/await code: there's no reason a single promise can't have many async functions awaiting it. The choice of which one to display as the "true" stack arises either way. I believe that promises with only one callback are the common case, however, so it may not be a problem to do some dumb strategy, like picking the first callback.

Blocks: dbg-captured
No longer blocks: async-stacks, dbg-async-stacks
Priority: P2 → P5

It might make sense if we instead captured a stack when we attach a callback to a promise. That is, a promise's set of reactions would associate a captured stack with each reaction. Attaching the callback is when the caller actually "expresses interest" in the value of the promise, roughly analogous to when a caller "expresses interest" in the return value of a callee. That's the answer to the question, "Why am I here?" which is what I think people often want out of stacks.

Whiteboard: [debugger-mvp]

This is one of the main bugs where live stack would will probably happen. Shouldn't it be attached to the live stacks meta or is there another place where implementation happens?

Flags: needinfo?(jimb)

This is not about live stacks at all, it's about captured stacks, and it's a confusing way to present them. See comment 6.

Flags: needinfo?(jimb)
Blocks: dbg-async-stacks
No longer blocks: 1586193
Blocks: dbg-71
No longer depends on: dbg-71
Blocks: dbg-72
No longer blocks: dbg-71
No longer blocks: dbg-72
No longer blocks: dbg-async-stacks

Jason, I assume this can be duped against existing captured stack bug?

Flags: needinfo?(jlaster)

yep

Flags: needinfo?(jlaster)
Status: NEW → RESOLVED
Closed: 5 years ago4 years ago
Resolution: --- → DUPLICATE
You need to log in before you can comment on or make changes to this bug.