Repository navigation
Memory leak when awaiting in inspector Debugger.pause callback #51397
Copy link
Copy link
Closed
Labels
inspectorIssues and PRs related to the V8 inspector protocol.Issues and PRs related to the V8 inspector protocol.
Description
Version
20.10.0
Platform
All
Subsystem
inspector
What steps will reproduce the bug?
The following code leaks memory at about 10MB per second on my machine:
import { Session } from "node:inspector/promises";
const session = new Session();
session.connect();
session.on("Debugger.paused", async (event) => {
for (const frame of event.params.callFrames) {
const localScope = frame.scopeChain.find((scope) => scope.type === "local");
if (localScope?.object?.objectId) {
const props = await session.post("Runtime.getProperties", {
objectId: localScope.object.objectId,
ownProperties: true,
});
}
}
});
await session.post("Debugger.enable");
await session.post("Debugger.setPauseOnExceptions", { state: "all" });
setInterval(() => {
console.log(`${Math.floor(process.memoryUsage().rss / 1024 / 1024)} MB`);
}, 1000);
setInterval(() => {
try {
throw new Error("Hello");
} catch {}
}, 1);How often does it reproduce? Is there a required condition?
Every time
What is the expected behavior? Why is that the expected behavior?
No memory leaked
What do you see instead?
I see ~10MB leaked every second which is around 10kB per debugger pause event.
Additional information
I found that that the following code:
import { Session } from "node:inspector/promises";
const session = new Session();
session.connect();
session.on("Debugger.resumed", () => {
console.log("Debugger resumed");
});
session.on("Debugger.paused", async (event) => {
console.log("Debugger paused start");
for (const frame of event.params.callFrames) {
const localScope = frame.scopeChain.find((scope) => scope.type === "local");
if (localScope?.object?.objectId) {
console.log("Runtime.getProperties");
const props = await session.post("Runtime.getProperties", {
objectId: localScope.object.objectId,
});
console.log("Runtime.getProperties returned");
}
}
console.log("Debugger.paused end");
});
await session.post("Debugger.enable");
await session.post("Debugger.setPauseOnExceptions", { state: "all" });
setTimeout(() => {
try {
throw new Error("Hello");
} catch {}
}, 1000);Outputs:
Debugger paused start
Runtime.getProperties
Debugger resumed
Runtime.getProperties returned
Runtime.getProperties
Runtime.getProperties returned
Runtime.getProperties
Runtime.getProperties returned
Debugger.paused end
The debugger does not wait for Debugger.resume. Instead it resumes automatically as soon as we call await session.post and I guess calling Runtime.getProperties after the resume causes something to be leaked?
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
inspectorIssues and PRs related to the V8 inspector protocol.Issues and PRs related to the V8 inspector protocol.
Activity
anonrig commentedon Jan 8, 2024
cc @legendecas @joyeecheung any suggestions?
legendecas commentedon Jan 9, 2024
The retained scope information with
'Debugger.paused'must be released with a'Debugger.resume'call when the debugger is still paused.For instance:
Notably, using breakpoints with a same-thread inspector session is not a good practice because the inspected script can not be distinguished from the inspector session codes and the program is literally been resumed when the
'Debugger.paused'fires.timfish commentedon Jan 9, 2024
The issue is that the debugger resumes on it's own when I call
await session.post("Runtime.getProperties").For example, see this full code example I posted to a discussion:
https://github.com/orgs/nodejs/discussions/51393
I had to add
isPausedto the above example to keep track of the paused status because If you call'Debugger.paused'when it's not paused it throws an error.So it not possible to call anything asynchronous from within the
Debugger.pausedcallback? In that case the newer promises API can't be used?theanarkh commentedon Jan 10, 2024
I think you can use the example as follows.
because VM will call resume after
Debugger.pauseevent, so you can not useawaitinDebugger.pauseevent.legendecas commentedon Jan 10, 2024
As mentioned above, it is not recommended to use breakpoints with same thread inspector sessions. Instead, try to connect to the inspector with websocket connections or with a worker thread:
doc: add notes on inspector breakpoints
doc: add notes on inspector breakpoints
doc: add notes on inspector breakpoints
doc: add notes on inspector breakpoints