Vscode: Support auto attach for node.js subprocesses (aka cluster support)

Created on 12 Dec 2017  路  25Comments  路  Source: microsoft/vscode

While working on the Process viewer, I noticed that we could easily support node.js "Clusters" (or any other spawned processes) with a similar approach.

Here is a sketch:

  • introduce a new launch config attribute "autoAttachChildren"
  • if a launch config has "autoAttachChildren" set to true, node-debug starts to track creation of subprocesses of the debuggee.
  • if a subprocess is recognised as being in debug mode (e.g. started with one of more of the flags --inspect, --inspect-brk, --inspect-port, --debug, --debug-brk, --debug-port), node-debug starts an "attach" debug session for the given port.

The result looks like this (and a first cut will be available in the next Insiders).

2017-12-12_17-16-00

I've verified the feature (for macOS only) with the example from the node.js documentation for 'cluster':

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    console.log(`Master ${process.pid} is running`);

    // Fork workers.
    for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    cluster.on('exit', (worker, code, signal) => {
        console.log(`worker ${worker.process.pid} died`);
    });
} else {
    // Workers can share any TCP connection
    // In this case it is an HTTP server
    http.createServer((req, res) => {
        res.writeHead(200);
        res.end('hello world\n');
    }).listen(8000);

    console.log(`Worker ${process.pid} started`);
}

Here is the launch config:

{
    "type": "node",
    "request": "launch",
    "name": "Cluster",
    "program": "${workspaceFolder}/test.js",
    "autoAttachChildren": true
},

This already works fine for node.js < v8 (legacy).
For node.js >= v8 the problem is that all workers stop on entry, which requires some manual "continuing". The problem is that node-debug2 does not automatically send a "continue" request because it guesses that a "stop on entry" is desired. Node-debug1 had the same problem but I made it "guess" better in this case...

The work around for node.js >= v8 uses an explicit "--inspect" instead of "--inspect-brk":

{
    "type": "node",
    "request": "launch",
    "name": "Cluster (--inspect)",
    "runtimeArgs": [
        "--inspect=50000"
    ],
    "port": 50000,
    "program": "${workspaceFolder}/test.js",
    "autoAttachChildren": true
},

The fix for node-debug was to only set this._stopOnEntry if it hasn't been set in the launch config:
see https://github.com/Microsoft/vscode-node-debug/blob/9a9ccb1703741fbad10e29d1769c0b8fc5cd3228/src/node/nodeDebug.ts#L1517

/cc @roblourens @auchenberg @isidorn

debug feature-request on-testplan

Most helpful comment

This feature works now in Insiders.

Try this sample:

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    console.log(`Master ${process.pid} is running`);

    // Fork workers.
    for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    cluster.on('exit', (worker, code, signal) => {
        console.log(`worker ${worker.process.pid} died`);
    });
} else {
    // Workers can share any TCP connection
    // In this case it is an HTTP server
    http.createServer((req, res) => {
        res.writeHead(200);
        res.end('hello world\n');
    }).listen(8000);

    console.log(`Worker ${process.pid} started`);
}

with this launch config:

{
    "type": "node",
    "request": "launch",
    "name": "Cluster",
    "program": "${workspaceFolder}/test.js",
    "autoAttachChildProcesses": true
},

All 25 comments

This is cool. Here's an issue from someone debugging with the cluster module: https://github.com/Microsoft/vscode/issues/39033

How does cluster.fork select a debug port? Does it automatically pick incrementing port numbers?

@roblourens yes, cluster increments port numbers like this (from the Process Viewer):

"legacy" as expected:
2017-12-12_22-03-51

"inspect" by using an interesting "inspect-port" flag:
2017-12-12_22-02-36

If a port is specified, "inspect-port" overrides the "inspect" port:
2017-12-12_22-08-18

Finally! Looks like a nice feature. Does it have any downsides or would this feature be enabled by default?

"autoAttachChildren" is an idiotic attribute name.
Any suggestions for a better name?

I don't think it's that bad. That's exactly what it does. I can only think of some variations on it like attachToChildProcs. You probably don't want to tie it to cluster because it would work for any type of spawned process.

The generated config maybe should copy outFiles and other props from the original config.

I agree with rob, for me the name of this setting tells what it is supposed to do. I prefer childProcess over children though.

Then let's go with "autoAttachChildProcesses" (yes, it's long but we have IntelliSense...)

This is great - it should be built into VS Code. Anyone who wants to debug a parent and its child processes in Node wants this.

A first cut is already in VS Code Insiders.

@roblourens I've verified (by patching Insiders) that your fix would work fine.
But I couldn't build/package node-debug2 for the marketplace (how do you build node-debug2?). It would be great if you could publish a 1.20.0 version.

I just press cmd+shift+b :) What problem did you have? I just published 1.20.0.

gulp package fails for me (I wanted to produce a VSIX for installing it in Insiders).

It works for me

Strange.
I did:

git clean -xfd .
npm install
gulp package

and got:
2017-12-20_18-21-18

Are you using an 'npm link' for vscode-chrome-debug-core?

No, but I just cleaned everything and I see the same thing. Something wrong with my package-lock.json?

If I delete and regenerate the package-lock, there are a million changes and npm list doesn't fail...?

Interesting: after regenerate the package-lock not only all the error are gone, but the strange problem with the version attribute being a url is gone too:
2017-12-21_00-20-37

The last few times I've done something that changes the package-lock.json, I've seen it randomly add and remove those

This feature works now in Insiders.

Try this sample:

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    console.log(`Master ${process.pid} is running`);

    // Fork workers.
    for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    cluster.on('exit', (worker, code, signal) => {
        console.log(`worker ${worker.process.pid} died`);
    });
} else {
    // Workers can share any TCP connection
    // In this case it is an HTTP server
    http.createServer((req, res) => {
        res.writeHead(200);
        res.end('hello world\n');
    }).listen(8000);

    console.log(`Worker ${process.pid} started`);
}

with this launch config:

{
    "type": "node",
    "request": "launch",
    "name": "Cluster",
    "program": "${workspaceFolder}/test.js",
    "autoAttachChildProcesses": true
},

Hello @weinand I can't set up http server on each cluster according with your code.
My environments:

  1. windows 10 x64
  2. vs code version 1.20.0-insider.
  3. node v6.3.1

Output data:

Debugger listening on [::]:11036
Master 16144 is running
Debugger listening on [::]:11037
Debugger listening on [::]:11038
Debugger listening on [::]:11039
Debugger listening on [::]:11040

May be you can clarify why cluster.fork() command doesn't achieve next parts of script.

@ddorkin-issart How does your launch config look like?

@weinand This is excellent when using npm, but is there anything special that needs to be done to incorporate nodemon on a cluster?

@mdhornet90 this feature has nothing to do with npm.
Here are the requirements:

  • in order to be able to track the subprocesses, we need the process id. For this we require that the main debuggee launched from the launch config is a node.js process and we use an "evaluate" to find its process id.
  • in order to be able to attach to the subprocesses they must be in debug mode. The feature determines whether a process is in debug mode by analysing the program arguments. Currently we detect the patterns --inspect, --inspect-brk, --inspect-port, --debug, --debug-brk, --debug-port (all optionally followed by a '=' and a port number).

Alright, so this feature is independent of _how_ node is started, thanks for the insight!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

omidgolparvar picture omidgolparvar  路  3Comments

v-pavanp picture v-pavanp  路  3Comments

philipgiuliani picture philipgiuliani  路  3Comments

curtw picture curtw  路  3Comments

DovydasNavickas picture DovydasNavickas  路  3Comments