Repository navigation
Memory leak when awaiting in inspector Debugger.pause callback #51397
Description
Activity
- addeddebuggerIssues and PRs related to the Node.js command-line debugger.Issues and PRs related to the Node.js command-line debugger.
on Jan 8, 2024 cc @legendecas @joyeecheung any suggestions?
Reacted by Toni Villena- addedconfirmed-bugIssues and PRs for confirmed bugs.Issues and PRs for confirmed bugs.
on Jan 8, 2024 - addedconfirmed-bugIssues and PRs for confirmed bugs.Issues and PRs for confirmed bugs.and removedconfirmed-bugIssues and PRs for confirmed bugs.Issues and PRs for confirmed bugs.
on 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:
session.on("Debugger.paused", (event) => { // Call 'Debugger.resume' synchronously to release the remote object in `event`. session.post("Debugger.resume"); });
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.- addedinspectorIssues and PRs related to the V8 inspector protocol.Issues and PRs related to the V8 inspector protocol.and removedconfirmed-bugIssues and PRs for confirmed bugs.Issues and PRs for confirmed bugs.debuggerIssues and PRs related to the Node.js command-line debugger.Issues and PRs related to the Node.js command-line debugger.
on 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/51393I 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.session.on("Debugger.paused", (event) => { // Call 'Debugger.resume' synchronously to release the remote object in `event`. session.post("Debugger.resume"); });
So it not possible to call anything asynchronous from within the
Debugger.pausedcallback? In that case the newer promises API can't be used?I think you can use the example as follows.
import { Session } from "node:inspector"; 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) { session.post("Runtime.getProperties", { objectId: localScope.object.objectId, ownProperties: true, }, (err, result) => { // console.log("Runtime.getProperties"); }); } } session.post("Debugger.resume"); }); session.post("Debugger.enable", (err) => { !err && 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 {} }, 100);
because VM will call resume after
Debugger.pauseevent, so you can not useawaitinDebugger.pauseevent.Reacted by Tim FishAs 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:
import { Session } from "node:inspector/promises"; import { Worker, isMainThread } from 'node:worker_threads'; if (!isMainThread) { inspectMainThread(); } else { mainThreadWork(); new Worker(new URL(import.meta.url)); .on('exit', (code) => console.log('worker exited', code)); } function mainThreadWork() { setInterval(() => { try { throw new Error("Hello"); } catch {} }, 1); } async function inspectMainThread() { setInterval(() => { console.log(`${Math.floor(process.memoryUsage().rss / 1024 / 1024)} MB`); }, 1000); const session = new Session(); session.connectToMainThread(); session.on("Debugger.paused", async (event) => { // `await` can be used here. // After all asynchronous work is done, resume the main thread. session.post('Debugger.resume'); }); await session.post("Debugger.enable"); await session.post("Debugger.setPauseOnExceptions", { state: "all" }); }
Reacted by Tim Fish and Juan Bomfim- added a commit that references this issue
on Jan 18, 2024 - added a commit that references this issue
on Jan 19, 2024 - added a commit that references this issue
on Jan 22, 2024 - added a commit that references this issue
on Feb 15, 2024
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:
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:
Outputs:
The debugger does not wait for
Debugger.resume. Instead it resumes automatically as soon as we callawait session.postand I guess callingRuntime.getPropertiesafter the resume causes something to be leaked?