mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08:00
child_process: add tracing channel for spawn
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com> PR-URL: https://github.com/nodejs/node/pull/61836 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
1 parent
2ebe49600e
commit
764e9a7560
3 files changed
+141
No files matched your search
@@ -1417,6 +1417,28 @@ added: v16.18.0
|
||||
|
||||
Emitted when a new process is created.
|
||||
|
||||
`tracing:child_process.spawn:start`
|
||||
|
||||
* `process` {ChildProcess}
|
||||
* `options` {Object}
|
||||
|
||||
Emitted when [`child_process.spawn()`][] is invoked, before the process is
|
||||
actually spawned.
|
||||
|
||||
`tracing:child_process.spawn:end`
|
||||
|
||||
* `process` {ChildProcess}
|
||||
|
||||
Emitted when [`child_process.spawn()`][] has completed successfully and the
|
||||
process has been created.
|
||||
|
||||
`tracing:child_process.spawn:error`
|
||||
|
||||
* `process` {ChildProcess}
|
||||
* `error` {Error}
|
||||
|
||||
Emitted when [`child_process.spawn()`][] encounters an error.
|
||||
|
||||
##### Event: `'execve'`
|
||||
|
||||
* `execPath` {string}
|
||||
@@ -1448,6 +1470,7 @@ Emitted when a new thread is created.
|
||||
[`channel.runStores(context, ...)`]: #channelrunstorescontext-fn-thisarg-args
|
||||
[`channel.subscribe(onMessage)`]: #channelsubscribeonmessage
|
||||
[`channel.unsubscribe(onMessage)`]: #channelunsubscribeonmessage
|
||||
[`child_process.spawn()`]: child_process.md#child_processspawncommand-args-options
|
||||
[`diagnostics_channel.channel(name)`]: #diagnostics_channelchannelname
|
||||
[`diagnostics_channel.subscribe(name, onMessage)`]: #diagnostics_channelsubscribename-onmessage
|
||||
[`diagnostics_channel.tracingChannel()`]: #diagnostics_channeltracingchannelnameorchannels
|
||||
|
||||
@@ -61,6 +61,7 @@ const spawn_sync = internalBinding('spawn_sync');
|
||||
const { kStateSymbol } = require('internal/dgram');
|
||||
const dc = require('diagnostics_channel');
|
||||
const childProcessChannel = dc.channel('child_process');
|
||||
const childProcessSpawn = dc.tracingChannel('child_process.spawn');
|
||||
|
||||
const {
|
||||
UV_EACCES,
|
||||
@@ -392,6 +393,10 @@ ChildProcess.prototype.spawn = function spawn(options) {
|
||||
this.spawnargs = options.args;
|
||||
}
|
||||
|
||||
if (childProcessSpawn.hasSubscribers) {
|
||||
childProcessSpawn.start.publish({ process: this, options });
|
||||
}
|
||||
|
||||
const err = this._handle.spawn(options);
|
||||
|
||||
// Run-time errors should emit an error, not throw an exception.
|
||||
@@ -400,6 +405,13 @@ ChildProcess.prototype.spawn = function spawn(options) {
|
||||
err === UV_EMFILE ||
|
||||
err === UV_ENFILE ||
|
||||
err === UV_ENOENT) {
|
||||
if (childProcessSpawn.hasSubscribers) {
|
||||
childProcessSpawn.error.publish({
|
||||
process: this,
|
||||
error: new ErrnoException(err, 'spawn'),
|
||||
});
|
||||
}
|
||||
|
||||
process.nextTick(onErrorNT, this, err);
|
||||
|
||||
// There is no point in continuing when we've hit EMFILE or ENFILE
|
||||
@@ -417,8 +429,20 @@ ChildProcess.prototype.spawn = function spawn(options) {
|
||||
|
||||
this._handle.close();
|
||||
this._handle = null;
|
||||
|
||||
if (childProcessSpawn.hasSubscribers) {
|
||||
childProcessSpawn.error.publish({
|
||||
process: this,
|
||||
error: new ErrnoException(err, 'spawn'),
|
||||
});
|
||||
}
|
||||
|
||||
throw new ErrnoException(err, 'spawn');
|
||||
} else {
|
||||
if (childProcessSpawn.hasSubscribers) {
|
||||
childProcessSpawn.end.publish({ process: this });
|
||||
}
|
||||
|
||||
process.nextTick(onSpawnNT, this);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { spawn, ChildProcess } = require('child_process');
|
||||
const dc = require('diagnostics_channel');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
|
||||
const isChildProcess = (process) => process instanceof ChildProcess;
|
||||
|
||||
function testDiagnosticChannel(subscribers, test, after) {
|
||||
dc.tracingChannel('child_process.spawn').subscribe(subscribers);
|
||||
|
||||
test(common.mustCall(() => {
|
||||
dc.tracingChannel('child_process.spawn').unsubscribe(subscribers);
|
||||
after?.();
|
||||
}));
|
||||
}
|
||||
|
||||
const testSuccessfulSpawn = common.mustCall(() => {
|
||||
let cb;
|
||||
|
||||
testDiagnosticChannel(
|
||||
{
|
||||
start: common.mustCall(({ process: childProcess, options }) => {
|
||||
assert.strictEqual(isChildProcess(childProcess), true);
|
||||
assert.strictEqual(options.file, process.execPath);
|
||||
}),
|
||||
end: common.mustCall(({ process: childProcess }) => {
|
||||
assert.strictEqual(isChildProcess(childProcess), true);
|
||||
}),
|
||||
error: common.mustNotCall(),
|
||||
},
|
||||
common.mustCall((callback) => {
|
||||
cb = callback;
|
||||
const child = spawn(process.execPath, ['-e', 'process.exit(0)']);
|
||||
child.on('close', () => {
|
||||
cb();
|
||||
});
|
||||
}),
|
||||
testFailingSpawnENOENT
|
||||
);
|
||||
});
|
||||
|
||||
const testFailingSpawnENOENT = common.mustCall(() => {
|
||||
testDiagnosticChannel(
|
||||
{
|
||||
start: common.mustCall(({ process: childProcess, options }) => {
|
||||
assert.strictEqual(isChildProcess(childProcess), true);
|
||||
assert.strictEqual(options.file, 'does-not-exist');
|
||||
}),
|
||||
end: common.mustNotCall(),
|
||||
error: common.mustCall(({ process: childProcess, error }) => {
|
||||
assert.strictEqual(isChildProcess(childProcess), true);
|
||||
assert.strictEqual(error.code, 'ENOENT');
|
||||
}),
|
||||
},
|
||||
common.mustCall((callback) => {
|
||||
const child = spawn('does-not-exist');
|
||||
child.on('error', () => {});
|
||||
callback();
|
||||
}),
|
||||
common.isWindows ? undefined : testFailingSpawnEACCES,
|
||||
);
|
||||
});
|
||||
|
||||
const testFailingSpawnEACCES = !common.isWindows ? common.mustCall(() => {
|
||||
tmpdir.refresh();
|
||||
const noExecFile = path.join(tmpdir.path, 'no-exec');
|
||||
fs.writeFileSync(noExecFile, '');
|
||||
fs.chmodSync(noExecFile, 0o644);
|
||||
|
||||
testDiagnosticChannel(
|
||||
{
|
||||
start: common.mustCall(({ process: childProcess, options }) => {
|
||||
assert.strictEqual(isChildProcess(childProcess), true);
|
||||
assert.strictEqual(options.file, noExecFile);
|
||||
}),
|
||||
end: common.mustNotCall(),
|
||||
error: common.mustCall(({ process: childProcess, error }) => {
|
||||
assert.strictEqual(isChildProcess(childProcess), true);
|
||||
assert.strictEqual(error.code, 'EACCES');
|
||||
}),
|
||||
},
|
||||
common.mustCall((callback) => {
|
||||
const child = spawn(noExecFile);
|
||||
child.on('error', () => {});
|
||||
callback();
|
||||
}),
|
||||
);
|
||||
}) : undefined;
|
||||
|
||||
testSuccessfulSpawn();
|
||||
Reference in new issue
Block a user