Skip to content

Commit 119b086

Browse files
child_process: add tracing channel for spawn
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
1 parent 9cc7fcc commit 119b086

File tree

3 files changed

+141
-0
lines changed

3 files changed

+141
-0
lines changed

doc/api/diagnostics_channel.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,6 +1417,28 @@ added: v16.18.0
14171417

14181418
Emitted when a new process is created.
14191419

1420+
`tracing:child_process.spawn:start`
1421+
1422+
* `process` {ChildProcess}
1423+
* `options` {Object}
1424+
1425+
Emitted when [`child_process.spawn()`][] is invoked, before the process is
1426+
actually spawned.
1427+
1428+
`tracing:child_process.spawn:end`
1429+
1430+
* `process` {ChildProcess}
1431+
1432+
Emitted when [`child_process.spawn()`][] has completed successfully and the
1433+
process has been created.
1434+
1435+
`tracing:child_process.spawn:error`
1436+
1437+
* `process` {ChildProcess}
1438+
* `error` {Error}
1439+
1440+
Emitted when [`child_process.spawn()`][] encounters an error.
1441+
14201442
##### Event: `'execve'`
14211443

14221444
* `execPath` {string}
@@ -1453,6 +1475,7 @@ Emitted when a new thread is created.
14531475
[`diagnostics_channel.tracingChannel()`]: #diagnostics_channeltracingchannelnameorchannels
14541476
[`end` event]: #endevent
14551477
[`error` event]: #errorevent
1478+
[`child_process.spawn()`]: child_process.md#child_processspawncommand-args-options
14561479
[`net.Server.listen()`]: net.md#serverlisten
14571480
[`process.execve()`]: process.md#processexecvefile-args-env
14581481
[`start` event]: #startevent

lib/internal/child_process.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const spawn_sync = internalBinding('spawn_sync');
6161
const { kStateSymbol } = require('internal/dgram');
6262
const dc = require('diagnostics_channel');
6363
const childProcessChannel = dc.channel('child_process');
64+
const childProcessSpawn = dc.tracingChannel('child_process.spawn');
6465

6566
const {
6667
UV_EACCES,
@@ -392,6 +393,10 @@ ChildProcess.prototype.spawn = function spawn(options) {
392393
this.spawnargs = options.args;
393394
}
394395

396+
if (childProcessSpawn.hasSubscribers) {
397+
childProcessSpawn.start.publish({ process: this, options });
398+
}
399+
395400
const err = this._handle.spawn(options);
396401

397402
// Run-time errors should emit an error, not throw an exception.
@@ -400,6 +405,13 @@ ChildProcess.prototype.spawn = function spawn(options) {
400405
err === UV_EMFILE ||
401406
err === UV_ENFILE ||
402407
err === UV_ENOENT) {
408+
if (childProcessSpawn.hasSubscribers) {
409+
childProcessSpawn.error.publish({
410+
process: this,
411+
error: new ErrnoException(err, 'spawn'),
412+
});
413+
}
414+
403415
process.nextTick(onErrorNT, this, err);
404416

405417
// There is no point in continuing when we've hit EMFILE or ENFILE
@@ -417,8 +429,20 @@ ChildProcess.prototype.spawn = function spawn(options) {
417429

418430
this._handle.close();
419431
this._handle = null;
432+
433+
if (childProcessSpawn.hasSubscribers) {
434+
childProcessSpawn.error.publish({
435+
process: this,
436+
error: new ErrnoException(err, 'spawn'),
437+
});
438+
}
439+
420440
throw new ErrnoException(err, 'spawn');
421441
} else {
442+
if (childProcessSpawn.hasSubscribers) {
443+
childProcessSpawn.end.publish({ process: this });
444+
}
445+
422446
process.nextTick(onSpawnNT, this);
423447
}
424448

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const { spawn, ChildProcess } = require('child_process');
5+
const dc = require('diagnostics_channel');
6+
const path = require('path');
7+
const fs = require('fs');
8+
const tmpdir = require('../common/tmpdir');
9+
10+
const isChildProcess = (process) => process instanceof ChildProcess;
11+
12+
function testDiagnosticChannel(subscribers, test, after) {
13+
dc.tracingChannel('child_process.spawn').subscribe(subscribers);
14+
15+
test(common.mustCall(() => {
16+
dc.tracingChannel('child_process.spawn').unsubscribe(subscribers);
17+
after?.();
18+
}));
19+
}
20+
21+
const testSuccessfulSpawn = common.mustCall(() => {
22+
let cb;
23+
24+
testDiagnosticChannel(
25+
{
26+
start: common.mustCall(({ process: childProcess, options }) => {
27+
assert.strictEqual(isChildProcess(childProcess), true);
28+
assert.strictEqual(options.file, process.execPath);
29+
}),
30+
end: common.mustCall(({ process: childProcess }) => {
31+
assert.strictEqual(isChildProcess(childProcess), true);
32+
}),
33+
error: common.mustNotCall(),
34+
},
35+
common.mustCall((callback) => {
36+
cb = callback;
37+
const child = spawn(process.execPath, ['-e', 'process.exit(0)']);
38+
child.on('close', () => {
39+
cb();
40+
});
41+
}),
42+
testFailingSpawnENOENT
43+
);
44+
});
45+
46+
const testFailingSpawnENOENT = common.mustCall(() => {
47+
testDiagnosticChannel(
48+
{
49+
start: common.mustCall(({ process: childProcess, options }) => {
50+
assert.strictEqual(isChildProcess(childProcess), true);
51+
assert.strictEqual(options.file, 'does-not-exist');
52+
}),
53+
end: common.mustNotCall(),
54+
error: common.mustCall(({ process: childProcess, error }) => {
55+
assert.strictEqual(isChildProcess(childProcess), true);
56+
assert.strictEqual(error.code, 'ENOENT');
57+
}),
58+
},
59+
common.mustCall((callback) => {
60+
const child = spawn('does-not-exist');
61+
child.on('error', () => {});
62+
callback();
63+
}),
64+
common.isWindows ? undefined : testFailingSpawnEACCES,
65+
);
66+
});
67+
68+
const testFailingSpawnEACCES = !common.isWindows ? common.mustCall(() => {
69+
tmpdir.refresh();
70+
const noExecFile = path.join(tmpdir.path, 'no-exec');
71+
fs.writeFileSync(noExecFile, '');
72+
fs.chmodSync(noExecFile, 0o644);
73+
74+
testDiagnosticChannel(
75+
{
76+
start: common.mustCall(({ process: childProcess, options }) => {
77+
assert.strictEqual(isChildProcess(childProcess), true);
78+
assert.strictEqual(options.file, noExecFile);
79+
}),
80+
end: common.mustNotCall(),
81+
error: common.mustCall(({ process: childProcess, error }) => {
82+
assert.strictEqual(isChildProcess(childProcess), true);
83+
assert.strictEqual(error.code, 'EACCES');
84+
}),
85+
},
86+
common.mustCall((callback) => {
87+
const child = spawn(noExecFile);
88+
child.on('error', () => {});
89+
callback();
90+
}),
91+
);
92+
}) : undefined;
93+
94+
testSuccessfulSpawn();

0 commit comments

Comments
 (0)