process: improve cwd performance
This caches the current working directory and only updates the variable if `process.chdir()` is called. PR-URL: https://github.com/nodejs/node/pull/27224 Reviewed-By: John-David Dalton <john.david.dalton@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Michaël Zasso <targos@protonmail.com>
This commit is contained in:
parent
55147d7d99
commit
1d022e8253
@ -73,6 +73,7 @@ if (isMainThread) {
|
|||||||
const wrapped = mainThreadSetup.wrapProcessMethods(rawMethods);
|
const wrapped = mainThreadSetup.wrapProcessMethods(rawMethods);
|
||||||
process.umask = wrapped.umask;
|
process.umask = wrapped.umask;
|
||||||
process.chdir = wrapped.chdir;
|
process.chdir = wrapped.chdir;
|
||||||
|
process.cwd = wrapped.cwd;
|
||||||
|
|
||||||
// TODO(joyeecheung): deprecate and remove these underscore methods
|
// TODO(joyeecheung): deprecate and remove these underscore methods
|
||||||
process._debugProcess = rawMethods._debugProcess;
|
process._debugProcess = rawMethods._debugProcess;
|
||||||
@ -86,11 +87,11 @@ if (isMainThread) {
|
|||||||
process.abort = workerThreadSetup.unavailable('process.abort()');
|
process.abort = workerThreadSetup.unavailable('process.abort()');
|
||||||
process.chdir = workerThreadSetup.unavailable('process.chdir()');
|
process.chdir = workerThreadSetup.unavailable('process.chdir()');
|
||||||
process.umask = wrapped.umask;
|
process.umask = wrapped.umask;
|
||||||
|
process.cwd = rawMethods.cwd;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up methods on the process object for all threads
|
// Set up methods on the process object for all threads
|
||||||
{
|
{
|
||||||
process.cwd = rawMethods.cwd;
|
|
||||||
process.dlopen = rawMethods.dlopen;
|
process.dlopen = rawMethods.dlopen;
|
||||||
process.uptime = rawMethods.uptime;
|
process.uptime = rawMethods.uptime;
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ const {
|
|||||||
getEnvMessagePort
|
getEnvMessagePort
|
||||||
} = internalBinding('worker');
|
} = internalBinding('worker');
|
||||||
|
|
||||||
|
const workerIo = require('internal/worker/io');
|
||||||
const {
|
const {
|
||||||
messageTypes: {
|
messageTypes: {
|
||||||
// Messages that may be received by workers
|
// Messages that may be received by workers
|
||||||
@ -38,7 +39,7 @@ const {
|
|||||||
STDIO_WANTS_MORE_DATA,
|
STDIO_WANTS_MORE_DATA,
|
||||||
},
|
},
|
||||||
kStdioWantsMoreDataCallback
|
kStdioWantsMoreDataCallback
|
||||||
} = require('internal/worker/io');
|
} = workerIo;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fatalException: originalFatalException
|
fatalException: originalFatalException
|
||||||
@ -89,6 +90,7 @@ if (process.env.NODE_CHANNEL_FD) {
|
|||||||
port.on('message', (message) => {
|
port.on('message', (message) => {
|
||||||
if (message.type === LOAD_SCRIPT) {
|
if (message.type === LOAD_SCRIPT) {
|
||||||
const {
|
const {
|
||||||
|
cwdCounter,
|
||||||
filename,
|
filename,
|
||||||
doEval,
|
doEval,
|
||||||
workerData,
|
workerData,
|
||||||
@ -111,6 +113,22 @@ port.on('message', (message) => {
|
|||||||
publicWorker.parentPort = publicPort;
|
publicWorker.parentPort = publicPort;
|
||||||
publicWorker.workerData = workerData;
|
publicWorker.workerData = workerData;
|
||||||
|
|
||||||
|
// The counter is only passed to the workers created by the main thread, not
|
||||||
|
// to workers created by other workers.
|
||||||
|
let cachedCwd = '';
|
||||||
|
let lastCounter = -1;
|
||||||
|
const originalCwd = process.cwd;
|
||||||
|
|
||||||
|
process.cwd = function() {
|
||||||
|
const currentCounter = Atomics.load(cwdCounter, 0);
|
||||||
|
if (currentCounter === lastCounter)
|
||||||
|
return cachedCwd;
|
||||||
|
lastCounter = currentCounter;
|
||||||
|
cachedCwd = originalCwd();
|
||||||
|
return cachedCwd;
|
||||||
|
};
|
||||||
|
workerIo.sharedCwdCounter = cwdCounter;
|
||||||
|
|
||||||
if (!hasStdin)
|
if (!hasStdin)
|
||||||
process.stdin.push(null);
|
process.stdin.push(null);
|
||||||
|
|
||||||
|
@ -20,9 +20,15 @@ const { signals } = internalBinding('constants').os;
|
|||||||
|
|
||||||
// The execution of this function itself should not cause any side effects.
|
// The execution of this function itself should not cause any side effects.
|
||||||
function wrapProcessMethods(binding) {
|
function wrapProcessMethods(binding) {
|
||||||
|
// Cache the working directory to prevent lots of lookups. If the working
|
||||||
|
// directory is changed by `chdir`, it'll be updated.
|
||||||
|
let cachedCwd = '';
|
||||||
|
|
||||||
function chdir(directory) {
|
function chdir(directory) {
|
||||||
validateString(directory, 'directory');
|
validateString(directory, 'directory');
|
||||||
return binding.chdir(directory);
|
binding.chdir(directory);
|
||||||
|
// Mark cache that it requires an update.
|
||||||
|
cachedCwd = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function umask(mask) {
|
function umask(mask) {
|
||||||
@ -32,9 +38,16 @@ function wrapProcessMethods(binding) {
|
|||||||
return binding.umask(mask);
|
return binding.umask(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cwd() {
|
||||||
|
if (cachedCwd === '')
|
||||||
|
cachedCwd = binding.cwd();
|
||||||
|
return cachedCwd;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chdir,
|
chdir,
|
||||||
umask
|
umask,
|
||||||
|
cwd
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
/* global SharedArrayBuffer */
|
||||||
|
|
||||||
const { Object } = primordials;
|
const { Object } = primordials;
|
||||||
|
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
@ -16,6 +18,7 @@ const {
|
|||||||
const { validateString } = require('internal/validators');
|
const { validateString } = require('internal/validators');
|
||||||
const { getOptionValue } = require('internal/options');
|
const { getOptionValue } = require('internal/options');
|
||||||
|
|
||||||
|
const workerIo = require('internal/worker/io');
|
||||||
const {
|
const {
|
||||||
drainMessagePort,
|
drainMessagePort,
|
||||||
MessageChannel,
|
MessageChannel,
|
||||||
@ -26,8 +29,8 @@ const {
|
|||||||
kStdioWantsMoreDataCallback,
|
kStdioWantsMoreDataCallback,
|
||||||
setupPortReferencing,
|
setupPortReferencing,
|
||||||
ReadableWorkerStdio,
|
ReadableWorkerStdio,
|
||||||
WritableWorkerStdio,
|
WritableWorkerStdio
|
||||||
} = require('internal/worker/io');
|
} = workerIo;
|
||||||
const { deserializeError } = require('internal/error-serdes');
|
const { deserializeError } = require('internal/error-serdes');
|
||||||
const { pathToFileURL } = require('url');
|
const { pathToFileURL } = require('url');
|
||||||
|
|
||||||
@ -50,6 +53,17 @@ const kParentSideStdio = Symbol('kParentSideStdio');
|
|||||||
const SHARE_ENV = Symbol.for('nodejs.worker_threads.SHARE_ENV');
|
const SHARE_ENV = Symbol.for('nodejs.worker_threads.SHARE_ENV');
|
||||||
const debug = require('internal/util/debuglog').debuglog('worker');
|
const debug = require('internal/util/debuglog').debuglog('worker');
|
||||||
|
|
||||||
|
let cwdCounter;
|
||||||
|
|
||||||
|
if (isMainThread) {
|
||||||
|
cwdCounter = new Uint32Array(new SharedArrayBuffer(4));
|
||||||
|
const originalChdir = process.chdir;
|
||||||
|
process.chdir = function(path) {
|
||||||
|
Atomics.add(cwdCounter, 0, 1);
|
||||||
|
originalChdir(path);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
class Worker extends EventEmitter {
|
class Worker extends EventEmitter {
|
||||||
constructor(filename, options = {}) {
|
constructor(filename, options = {}) {
|
||||||
super();
|
super();
|
||||||
@ -131,6 +145,7 @@ class Worker extends EventEmitter {
|
|||||||
type: messageTypes.LOAD_SCRIPT,
|
type: messageTypes.LOAD_SCRIPT,
|
||||||
filename,
|
filename,
|
||||||
doEval: !!options.eval,
|
doEval: !!options.eval,
|
||||||
|
cwdCounter: cwdCounter || workerIo.sharedCwdCounter,
|
||||||
workerData: options.workerData,
|
workerData: options.workerData,
|
||||||
publicPort: port2,
|
publicPort: port2,
|
||||||
manifestSrc: getOptionValue('--experimental-policy') ?
|
manifestSrc: getOptionValue('--experimental-policy') ?
|
||||||
|
12
test/parallel/test-process-abort.js
Normal file
12
test/parallel/test-process-abort.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
if (!common.isMainThread)
|
||||||
|
common.skip('process.abort() is not available in Workers');
|
||||||
|
|
||||||
|
// Check that our built-in methods do not have a prototype/constructor behaviour
|
||||||
|
// if they don't need to. This could be tested for any of our C++ methods.
|
||||||
|
assert.strictEqual(process.abort.prototype, undefined);
|
||||||
|
assert.throws(() => new process.abort(), TypeError);
|
@ -42,8 +42,3 @@ const err = {
|
|||||||
};
|
};
|
||||||
common.expectsError(function() { process.chdir({}); }, err);
|
common.expectsError(function() { process.chdir({}); }, err);
|
||||||
common.expectsError(function() { process.chdir(); }, err);
|
common.expectsError(function() { process.chdir(); }, err);
|
||||||
|
|
||||||
// Check that our built-in methods do not have a prototype/constructor behaviour
|
|
||||||
// if they don't need to. This could be tested for any of our C++ methods.
|
|
||||||
assert.strictEqual(process.cwd.prototype, undefined);
|
|
||||||
assert.throws(() => new process.cwd(), TypeError);
|
|
||||||
|
44
test/parallel/test-worker-process-cwd.js
Normal file
44
test/parallel/test-worker-process-cwd.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
'use strict';
|
||||||
|
const assert = require('assert');
|
||||||
|
const common = require('../common');
|
||||||
|
const { Worker, isMainThread, parentPort } = require('worker_threads');
|
||||||
|
|
||||||
|
// Do not use isMainThread directly, otherwise the test would time out in case
|
||||||
|
// it's started inside of another worker thread.
|
||||||
|
if (!process.env.HAS_STARTED_WORKER) {
|
||||||
|
process.env.HAS_STARTED_WORKER = '1';
|
||||||
|
if (!isMainThread) {
|
||||||
|
common.skip('This test can only run as main thread');
|
||||||
|
}
|
||||||
|
const w = new Worker(__filename);
|
||||||
|
process.chdir('..');
|
||||||
|
w.on('message', common.mustCall((message) => {
|
||||||
|
assert.strictEqual(message, process.cwd());
|
||||||
|
process.chdir('..');
|
||||||
|
w.postMessage(process.cwd());
|
||||||
|
}));
|
||||||
|
} else if (!process.env.SECOND_WORKER) {
|
||||||
|
process.env.SECOND_WORKER = '1';
|
||||||
|
const firstCwd = process.cwd();
|
||||||
|
const w = new Worker(__filename);
|
||||||
|
w.on('message', common.mustCall((message) => {
|
||||||
|
assert.strictEqual(message, process.cwd());
|
||||||
|
parentPort.postMessage(firstCwd);
|
||||||
|
parentPort.onmessage = common.mustCall((obj) => {
|
||||||
|
const secondCwd = process.cwd();
|
||||||
|
assert.strictEqual(secondCwd, obj.data);
|
||||||
|
assert.notStrictEqual(secondCwd, firstCwd);
|
||||||
|
w.postMessage(secondCwd);
|
||||||
|
parentPort.unref();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
const firstCwd = process.cwd();
|
||||||
|
parentPort.postMessage(firstCwd);
|
||||||
|
parentPort.onmessage = common.mustCall((obj) => {
|
||||||
|
const secondCwd = process.cwd();
|
||||||
|
assert.strictEqual(secondCwd, obj.data);
|
||||||
|
assert.notStrictEqual(secondCwd, firstCwd);
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user