New issue
Advanced search Search tips

Issue 765666 link

Starred by 33 users

Issue metadata

Status: Fixed
Owner:
Closed: Oct 2017
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: Linux , Windows , Mac
Pri: 2
Type: Bug-Regression



Sign in to add a comment

Heap Snapshot doesn't include all reachable objects

Reported by b...@lucidchart.com, Sep 15 2017

Issue description

UserAgent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36

Steps to reproduce the problem:
Open the following HTML:

<html>
    <body>
        <script>
            class Foo {
                constructor() {
                    window.addEventListener('mousemove', (e) => {
                        this.x = e.pageX;
                    });
                }
            }

            function bootstrap() {
                const f = new Foo();
            }

            bootstrap();
        </script>
    </body>
</html>

Collect a heap snapshot. Search for instances of Foo.

What is the expected behavior?
Since the Foo instance is reachable, I expect to find it in the heap snapshot.

What went wrong?
It's not in the heap snapshot. If you put a breakpoint in the event listener, and hover over `this`, you'll see the instance of Foo is very much alive and well. But it's not in the heap snapshot.

Did this work before? Yes 60, possibly 61

Chrome version: 62  Channel: n/a
OS Version: Ubuntu 16.04
Flash Version: 

A little embarrassing since I'm giving a conference talk on using the memory tools to find memory leaks on Monday, and this is the first thing I tried to set up as an example :-(
 
Cc: kkaluri@chromium.org
Labels: -Pri-2 hasbisect-per-revision Needs-Triage-M62 M-63 OS-Mac OS-Windows Pri-1
Owner: jbroman@chromium.org
Status: Assigned (was: Unconfirmed)
Able to reproduce this issue on Windows 10, Ubuntu 14.04 & Mac 10.12.6 with chrome Beta #62.0.3202.18, 
Dev #63.0.3213.3, Canary #63.0.3218.0 Issue is broken in 62.

Bisect Info:
===========
Good build : 62.0.3174.0,  Revision Range -491203
Bad build  : 62.0.3175.0,  Revision Range -491592

After executing the per-revision bisect script , i got the following CL's between good and bad build versions
===========================================
https://chromium.googlesource.com/chromium/src/+log/10570781a35945d5f833f423ca3940bc81bd7c0d..d68025ec1b431429a939e80cacceeae20dd9ff4a

The suspecting Change Log is :
-----------
https://chromium.googlesource.com/chromium/src/+/d68025ec1b431429a939e80cacceeae20dd9ff4a


jbroman@- Could you please look into this issue, if it's related to your change?  if not could you please help us to reassign this issue to the right owner.

Cc: haraken@chromium.org mlippautz@chromium.org
+mlippautz: Is it possible that V8 objects kept alive due to wrapper tracing aren't captured in the heap snapshot?
Cc: yangguo@chromium.org u...@chromium.org
I think this is a problem with UnreachableObjectsFilter that doesn't do wrapper tracing.  Essentially, the filter can be used for snapshots to filter out unreachable objects. The filter however, doesn't do ephemeral marking or wrapper tracing. 

I think this is in general flawed as the filter needs to mirror the GC behavior exactly and not just do best effort marking on its own as unmarked objects are not reported.

This probably used to work when we just called MarkLiveObjects in the GC. This was however broken with incremental marking.
Cc: jbroman@chromium.org
Owner: mlippautz@chromium.org
Assigning to mlippautz; please feel free to reassign to the correct assignee on the V8 side.
Cc: -mlippautz@chromium.org alph@chromium.org
Labels: -Pri-1 Pri-2
On a second look, this should work. Because the listener should still be reachable via a global handle and so the UnreachableObjectsFilter should not get rid of it.

Any other thoughts from DevTools experts?

Comment 6 by alph@chromium.org, Sep 27 2017

A quick look revealed the following. I modified the example a bit to be able to find the Foo class:

<html>
    <body>
        <script>
            class Foo {
                constructor() {
                    window.addEventListener('mousemove', (e) => {
                        this.x = e.pageX;
                    });
                }
            }

            function bootstrap() {
                const f = new Foo();
                return f;
            }

            zzz = bootstrap();
        </script>
    </body>
</html>

Now the Foo retaining tree looks like (see attachment 1)
The context @60389 seems to be missing retainers.

The DevTools on DevTools shows some warnings as well:

Heap snapshot: 7 nodes are unreachable from the root. Following nodes have only weak retainers:
   @60389  weak retainers: (Global handles)@29.67
_buildPostOrderIndex @ Trie.js:1575

Will investigate further

Comment 7 by alph@chromium.org, Sep 27 2017

attachment 1
Screenshot from 2017-09-27 10:57:14.png
20.9 KB View Download

Comment 8 by alph@chromium.org, Sep 28 2017

So to summarize what's going on.

The Foo object is indeed reachable, but it is not reachable from the global object. So heap profiler treats it as V8 internal object and hides from the class list. You still can find it in the Containment view by exploring global handles.

To address it I'm probably going to turn off the filtering as it started to produce lots of false negatives recently.

Another problem however is that the retainment path for it would just be:
Foo <- context <- anonymous function
The rest is not visible as event listeners are beyond v8 heap. So the path does not give the user a hint on why the object is alive.
Cc: -alph@chromium.org mlippautz@chromium.org
Owner: alph@chromium.org
Thanks a lot for debugging this!

I'll move this over so you can decide whether followup actions are needed.

> The rest is not visible as event listeners are beyond v8 heap. So the path does not give the user a hint on why the object is alive.

That's an issue we've heard a few times recently. It looks like expanding the graph to the Blink side would be really useful.
Actually, the (Foo <- context <- anonymous function) is usually sufficient hint to the user as to why it's still alive, since the dev tools let you click through to see the source code of the anonymous function. You probably see that it's being passed into addEventListener, and from there it becomes obvious. I think this is all the info we used to get in Chrome 60, but obviously I'd welcome more context if it's possible to get.
Project Member

Comment 11 by bugdroid1@chromium.org, Oct 3 2017

Comment 12 by alph@chromium.org, Oct 5 2017

Status: Fixed (was: Assigned)

Comment 13 by alph@chromium.org, Oct 20 2017

Cc: alph@chromium.org
 Issue 767553  has been merged into this issue.

Sign in to add a comment