2016-06-10 23:28:15 +03:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
const { memorySpec } = require("devtools/shared/specs/memory");
|
2018-12-11 19:32:41 +03:00
|
|
|
const { FrontClassWithSpec, registerFront } = require("devtools/shared/protocol");
|
2016-06-10 23:28:15 +03:00
|
|
|
|
|
|
|
loader.lazyRequireGetter(this, "FileUtils",
|
|
|
|
"resource://gre/modules/FileUtils.jsm", true);
|
|
|
|
loader.lazyRequireGetter(this, "HeapSnapshotFileUtils",
|
|
|
|
"devtools/shared/heapsnapshot/HeapSnapshotFileUtils");
|
|
|
|
|
2018-12-11 19:32:41 +03:00
|
|
|
class MemoryFront extends FrontClassWithSpec(memorySpec) {
|
2019-01-28 21:42:50 +03:00
|
|
|
constructor(client) {
|
|
|
|
super(client);
|
2016-06-10 23:28:15 +03:00
|
|
|
this._client = client;
|
2018-10-17 13:36:33 +03:00
|
|
|
this.heapSnapshotFileActorID = null;
|
2019-01-28 21:42:50 +03:00
|
|
|
|
|
|
|
// Attribute name from which to retrieve the actorID out of the target actor's form
|
|
|
|
this.formAttributeName = "memoryActor";
|
2018-12-11 19:32:41 +03:00
|
|
|
}
|
2016-06-10 23:28:15 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Save a heap snapshot, transfer it from the server to the client if the
|
|
|
|
* server and client do not share a file system, and return the local file
|
|
|
|
* path to the heap snapshot.
|
|
|
|
*
|
|
|
|
* Note that this is safe to call for actors inside sandoxed child processes,
|
|
|
|
* as we jump through the correct IPDL hoops.
|
|
|
|
*
|
|
|
|
* @params Boolean options.forceCopy
|
|
|
|
* Always force a bulk data copy of the saved heap snapshot, even when
|
|
|
|
* the server and client share a file system.
|
|
|
|
*
|
Bug 1261869 - Fix leaks in devtools; r=ejpbruel
There are two leaks addressed in this commit:
1. The thread actor's `_debuggerSourcesSeen` set was never cleared. This set
exists only as a performance optimization to speed up `_addSource` in cases
where we've already added the source. Unfortunately, this set wasn't getting
cleared when we cleared debuggees out and it ended up keeping the
`Debugger.Source`, its referent, and transitively its referent's global alive. I
figured it was simpler to make it a `WeakSet` than to add it as a special case
in `ThreadActor.prototype._clearDebuggees` and manage the lifetimes by hand. I
think this fits well with its intended use as an ephemeral performance
optimization.
2. Due to a logic error, we were not clearing debuggees in the memory actor's
`Debugger` instance on navigations. This isn't really a "proper" leak, in that
if you forced a GC, the old debuggees would go away as `Debugger` holds them
weakly, however if there was no GC between navigations, then you could still see
the old windows (and everything they "retained") as roots in the snapshot. This
issue is straightforward to fix once identified: ensure that `_clearDebuggees`
is actually called on navigation.
Finally, this commit adds a test that we don't leak Window objects when devtools
are open and we keep refreshing a tab. When it fails, it prints out the leaking
window's retaining paths.
2016-07-06 18:37:57 +03:00
|
|
|
* @params {Object|undefined} options.boundaries
|
|
|
|
* The boundaries for the heap snapshot. See
|
2017-11-08 08:25:33 +03:00
|
|
|
* ChromeUtils.webidl for more details.
|
Bug 1261869 - Fix leaks in devtools; r=ejpbruel
There are two leaks addressed in this commit:
1. The thread actor's `_debuggerSourcesSeen` set was never cleared. This set
exists only as a performance optimization to speed up `_addSource` in cases
where we've already added the source. Unfortunately, this set wasn't getting
cleared when we cleared debuggees out and it ended up keeping the
`Debugger.Source`, its referent, and transitively its referent's global alive. I
figured it was simpler to make it a `WeakSet` than to add it as a special case
in `ThreadActor.prototype._clearDebuggees` and manage the lifetimes by hand. I
think this fits well with its intended use as an ephemeral performance
optimization.
2. Due to a logic error, we were not clearing debuggees in the memory actor's
`Debugger` instance on navigations. This isn't really a "proper" leak, in that
if you forced a GC, the old debuggees would go away as `Debugger` holds them
weakly, however if there was no GC between navigations, then you could still see
the old windows (and everything they "retained") as roots in the snapshot. This
issue is straightforward to fix once identified: ensure that `_clearDebuggees`
is actually called on navigation.
Finally, this commit adds a test that we don't leak Window objects when devtools
are open and we keep refreshing a tab. When it fails, it prints out the leaking
window's retaining paths.
2016-07-06 18:37:57 +03:00
|
|
|
*
|
2016-06-10 23:28:15 +03:00
|
|
|
* @returns Promise<String>
|
|
|
|
*/
|
2018-12-11 19:32:41 +03:00
|
|
|
async saveHeapSnapshot(options = {}) {
|
|
|
|
const snapshotId = await super.saveHeapSnapshot(options.boundaries);
|
2016-06-10 23:28:15 +03:00
|
|
|
|
|
|
|
if (!options.forceCopy &&
|
2018-02-22 11:49:06 +03:00
|
|
|
(await HeapSnapshotFileUtils.haveHeapSnapshotTempFile(snapshotId))) {
|
2016-06-10 23:28:15 +03:00
|
|
|
return HeapSnapshotFileUtils.getHeapSnapshotTempFilePath(snapshotId);
|
|
|
|
}
|
|
|
|
|
2018-02-22 11:49:06 +03:00
|
|
|
return this.transferHeapSnapshot(snapshotId);
|
2018-12-11 19:32:41 +03:00
|
|
|
}
|
2016-06-10 23:28:15 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Given that we have taken a heap snapshot with the given id, transfer the
|
|
|
|
* heap snapshot file to the client. The path to the client's local file is
|
|
|
|
* returned.
|
|
|
|
*
|
|
|
|
* @param {String} snapshotId
|
|
|
|
*
|
|
|
|
* @returns Promise<String>
|
|
|
|
*/
|
2018-12-11 19:32:41 +03:00
|
|
|
async transferHeapSnapshot(snapshotId) {
|
2016-06-10 23:28:15 +03:00
|
|
|
if (!this.heapSnapshotFileActorID) {
|
2018-10-17 13:36:33 +03:00
|
|
|
const form = await this._client.mainRoot.rootForm;
|
|
|
|
this.heapSnapshotFileActorID = form.heapSnapshotFileActor;
|
2016-06-10 23:28:15 +03:00
|
|
|
}
|
|
|
|
|
2018-10-15 20:33:58 +03:00
|
|
|
try {
|
|
|
|
const request = this._client.request({
|
|
|
|
to: this.heapSnapshotFileActorID,
|
|
|
|
type: "transferHeapSnapshot",
|
2018-10-19 15:55:39 +03:00
|
|
|
snapshotId,
|
2018-10-15 20:33:58 +03:00
|
|
|
});
|
2016-06-10 23:28:15 +03:00
|
|
|
|
|
|
|
const outFilePath =
|
|
|
|
HeapSnapshotFileUtils.getNewUniqueHeapSnapshotTempFilePath();
|
|
|
|
const outFile = new FileUtils.File(outFilePath);
|
|
|
|
const outFileStream = FileUtils.openSafeFileOutputStream(outFile);
|
2018-10-15 20:33:58 +03:00
|
|
|
|
|
|
|
// This request is a bulk request. That's why the result of the request is
|
|
|
|
// an object with the `copyTo` function that can transfer the data to
|
|
|
|
// another stream.
|
|
|
|
// See devtools/shared/transport/transport.js to know more about this mode.
|
|
|
|
const { copyTo } = await request;
|
|
|
|
await copyTo(outFileStream);
|
|
|
|
|
|
|
|
FileUtils.closeSafeFileOutputStream(outFileStream);
|
|
|
|
return outFilePath;
|
|
|
|
} catch (e) {
|
|
|
|
if (e.error) {
|
|
|
|
// This isn't a real error, rather this is a message coming from the
|
|
|
|
// server. So let's throw a real error instead.
|
|
|
|
throw new Error(
|
|
|
|
`The server's actor threw an error: (${e.error}) ${e.message}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, rethrow the error
|
|
|
|
throw e;
|
|
|
|
}
|
2018-12-11 19:32:41 +03:00
|
|
|
}
|
|
|
|
}
|
2016-06-10 23:28:15 +03:00
|
|
|
|
|
|
|
exports.MemoryFront = MemoryFront;
|
2018-12-11 19:32:41 +03:00
|
|
|
registerFront(MemoryFront);
|