New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 622506 link

Starred by 4 users

Issue metadata

Status: Duplicate
Merged: issue 784979
Owner:
Closed: Dec 2017
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: Mac
Pri: 2
Type: Feature



Sign in to add a comment

V8DebuggerAgentImpl: add support for user-collapsible m_currentStacks

Reported by antonin....@gmail.com, Jun 22 2016

Issue description

UserAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36

Steps to reproduce the problem:
DevTools implemented support for async call stacks[1] a while back (in 2014?).

IMO since then Javascript community increased using async programming. And with ES6 promises, and upcoming async/await I expect even deeper use of async stacktraces. ClojureScript community took this to extreme with core.async. In a nutshell: you can easily write code which looks like synchronous code, but it gets compiled into a state machine. It chunks your original code into blocks on async-operation-boundaries. And then executes it chunk by chunk and properly parks/restores state while waiting on async callback completions. This can lead to pretty long chains of async calls. Especially because you can model loops in this way very easily.

Please see screenshot with some sample code in [2].

One problem is that DevTools hardcoded max length of async chain to only 4 which is not enough for year 2016 (see [3]). Make it user-configurable.
Second problem are long chains of async-tasks. I want to propose a solution below.

[1] http://www.html5rocks.com/en/tutorials/developertools/async-call-stack
[2] https://dl.dropboxusercontent.com/u/559047/core-async-long-stack-traces.png
[3] https://github.com/binaryage/dirac/blob/devtools/front_end/sdk/DebuggerModel.js#L200

What is the expected behavior?
Imagine a javascript function existed, exposed in page's context:

function asyncTaskCheckpoint(id, description, ?state) {...}

when called, it would collapse m_currentStacks in V8DebuggerAgentImpl up to next checkpointed task with the same id (if exists) or would checkpoint current task (if does not exist).

The idea is to use this in loops of async calls. Library can generate this call at the beginning of a loop, each loop would get unique id.

Description is there to be displayed in the callstack devtools UI (or elsewhere) as a virtual call frame. Description can be updated with each call (loop's code will probably want to communicate iteration number or some other essential data communicating the loop state). We could go even a step further and allow third parameter state which would be a javascript object to present in debugger UI when a given virtual stack frame gets selected.

What went wrong?
Nothing went wrong. This is a proposal.

Did this work before? No 

Chrome version: 51.0.2704.103  Channel: canary
OS Version: OS X 10.11.6
Flash Version: Shockwave Flash 22.0 r0

We could split work:
* you implement support for this in V8DebuggerAgentImpl and expose it to DevTools in InspectorBackendCommands.js (which should be easy AFAIK).
* I will try to implement this in DevTools, expose it in DevTools UI in my fork[4] and battle test it with core.async.

Based on my experience you can then go ahead and implement it for all JS devs / library authors to benefit from this. Maybe taking my pieces along the way.

[4] https://github.com/binaryage/dirac
 
Cc: tkonch...@chromium.org
Labels: -Type-Bug M-53 Type-Feature
Status: Untriaged (was: Unconfirmed)
Confirming the issue as this is a feature request. Waiting for more inputs on this
Components: Platform>DevTools>JavaScript
Owner: kozyatinskiy@chromium.org
Status: Assigned (was: Untriaged)
Hey Antonin, thanks for the proposal. 

I believe this is the same-ish situation as https://bugs.chromium.org/p/chromium/issues/detail?id=332624

So far we haven't made headway in this area. And there was some discussion about using https://domenic.github.io/zones/ for this. Curious if zones would be a good primitive to build this on. What do you think?
Project Member

Comment 3 by sheriffbot@chromium.org, Jul 3 2016

Labels: -M-53 M-54 MovedFrom-53
Moving this nonessential bug to the next milestone.

For more details visit https://www.chromium.org/issue-tracking/autotriage - Your friendly Sheriffbot
Thanks for your response, Paul.

Just studied the document briefly, it pretty much looks like zones from Angular2[1]. Zones are about attaching state to an async task in a way that this state is auto-magically inherited/shared by child/chained async tasks potentially spawned in the process. In essence a logical chain of async tasks can share a context called "zone". The problem I'm trying to solve here is not about state book-keeping but about long stack traces caused by long chains of async tasks. I have just created an experimental change[2] to DevTools code to allow longer async tasks stack traces. Please look at the first screenshot[3] and the second screenshot[4] where you can see that just a simple loop of 20 iterations generated a huge stack trace (I couldn't even take complete screenshot of it). Why is that? core.async state machine looks at the code given (the go-loop at line #16), identifies all async operations (the timeout call at line #20), breaks the code into pieces on async call boundaries and then wraps those into anonymous functions as "jobs". Each job is wrapped in some glue code which stores and restores the machine state (essentially this is a subset of zones functionality). Jobs are then chained as normal async calls via goog.async.nextTick. Please note that goog.async.nextTick is aggressive (for a reason), so the jobs chain is partitioned to short strings of jobs which are interleaved with setTimeout calls to not starve javascript scheduler. In total my simple loop with 20 iterations generated a chain of 40+ async jobs (20 promises and 20 setTimeouts). But essential is only the stack trace before entering the loop (break-loop-async) and last loop iteration (stack traces of last 3 chained async tasks on the second screenshot). Please don't judge the number of stack frames generated by core.async. I believe it could be optimized to be more readable in debuggers, but I assume that wasn't the goal when it was implemented. It can be solved by black-boxing. Our main issue here is the large number of chained async tasks.

My proposal is about detection of loops of async calls (which lead to long chains of async tasks). 
A library or user code which is aware of "looping" constructs could optionally signal each new loop iteration boundary and DevTools could drop recorded stack trace frames in V8DebuggerAgentImpl.

Please note that my proposal for new API in V8DebuggerAgentImpl is not essential to achieve my goal here. It is just an optimization. In Dirac I can increase DevTools limit (maxAsyncStackChainDepth) and implement the stack trace frames dropping on DevTools side. But this solution is fragile because without this support any long-running loop will eventually hit the limits.

As a side note. Zones could be used to implement stack-traces completely in user-land as a library without DevTools cooperation. Angular guys have done that[5] as a major show-case for zone.js. But I don't want to go this route. I strongly believe DevTools should do this job, I don't want to force people to use some kind of framework to provide reasonable async stack traces for them. DevTools has best tools to do that consistently and is already almost there.

[1] https://github.com/angular/zone.js
[2] https://github.com/binaryage/dirac/commit/be3ef5cce91a0c4b9f3f02327633353f16948a3c
[3] https://dl.dropboxusercontent.com/u/559047/core-async-loop-1.png
[4] https://dl.dropboxusercontent.com/u/559047/core-async-loop-2.png
[5] https://github.com/angular/zone.js/blob/master/lib/zone-spec/long-stack-trace.ts

Owner: kozy@chromium.org

Comment 6 by kozy@chromium.org, Dec 5 2017

Mergedinto: 784979
Status: Duplicate (was: Assigned)
We reworked our async stacks and supported cross target stacks. It means that universal detecting loops and collapsing can be implemented only on frontend side. We can detect loops for each target as well but this work is subset of general call stack sidebar improvement.

Sign in to add a comment