Skip to content

Commit 91b7f5e

Browse files
BridgeARtargos
authored andcommitted
process: improve cwd performance
This caches the current working directory and only updates the variable if `process.chdir()` is called. PR-URL: #27224 Reviewed-By: John-David Dalton <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Backport-PR-URL: #27483
1 parent 7bbf951 commit 91b7f5e

File tree

7 files changed

+109
-11
lines changed

7 files changed

+109
-11
lines changed

lib/internal/bootstrap/node.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ if (isMainThread) {
7373
const wrapped = mainThreadSetup.wrapProcessMethods(rawMethods);
7474
process.umask = wrapped.umask;
7575
process.chdir = wrapped.chdir;
76+
process.cwd = wrapped.cwd;
7677

7778
// TODO(joyeecheung): deprecate and remove these underscore methods
7879
process._debugProcess = rawMethods._debugProcess;
@@ -86,11 +87,11 @@ if (isMainThread) {
8687
process.abort = workerThreadSetup.unavailable('process.abort()');
8788
process.chdir = workerThreadSetup.unavailable('process.chdir()');
8889
process.umask = wrapped.umask;
90+
process.cwd = rawMethods.cwd;
8991
}
9092

9193
// Set up methods on the process object for all threads
9294
{
93-
process.cwd = rawMethods.cwd;
9495
process.dlopen = rawMethods.dlopen;
9596
process.uptime = rawMethods.uptime;
9697

lib/internal/main/worker_thread.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525
getEnvMessagePort
2626
} = internalBinding('worker');
2727

28+
const workerIo = require('internal/worker/io');
2829
const {
2930
messageTypes: {
3031
// Messages that may be received by workers
@@ -38,7 +39,7 @@ const {
3839
STDIO_WANTS_MORE_DATA,
3940
},
4041
kStdioWantsMoreDataCallback
41-
} = require('internal/worker/io');
42+
} = workerIo;
4243

4344
const {
4445
fatalException: originalFatalException
@@ -89,6 +90,7 @@ if (process.env.NODE_CHANNEL_FD) {
8990
port.on('message', (message) => {
9091
if (message.type === LOAD_SCRIPT) {
9192
const {
93+
cwdCounter,
9294
filename,
9395
doEval,
9496
workerData,
@@ -111,6 +113,22 @@ port.on('message', (message) => {
111113
publicWorker.parentPort = publicPort;
112114
publicWorker.workerData = workerData;
113115

116+
// The counter is only passed to the workers created by the main thread, not
117+
// to workers created by other workers.
118+
let cachedCwd = '';
119+
let lastCounter = -1;
120+
const originalCwd = process.cwd;
121+
122+
process.cwd = function() {
123+
const currentCounter = Atomics.load(cwdCounter, 0);
124+
if (currentCounter === lastCounter)
125+
return cachedCwd;
126+
lastCounter = currentCounter;
127+
cachedCwd = originalCwd();
128+
return cachedCwd;
129+
};
130+
workerIo.sharedCwdCounter = cwdCounter;
131+
114132
if (!hasStdin)
115133
process.stdin.push(null);
116134

lib/internal/process/main_thread_only.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,15 @@ const { signals } = internalBinding('constants').os;
2020

2121
// The execution of this function itself should not cause any side effects.
2222
function wrapProcessMethods(binding) {
23+
// Cache the working directory to prevent lots of lookups. If the working
24+
// directory is changed by `chdir`, it'll be updated.
25+
let cachedCwd = '';
26+
2327
function chdir(directory) {
2428
validateString(directory, 'directory');
25-
return binding.chdir(directory);
29+
binding.chdir(directory);
30+
// Mark cache that it requires an update.
31+
cachedCwd = '';
2632
}
2733

2834
function umask(mask) {
@@ -32,9 +38,16 @@ function wrapProcessMethods(binding) {
3238
return binding.umask(mask);
3339
}
3440

41+
function cwd() {
42+
if (cachedCwd === '')
43+
cachedCwd = binding.cwd();
44+
return cachedCwd;
45+
}
46+
3547
return {
3648
chdir,
37-
umask
49+
umask,
50+
cwd
3851
};
3952
}
4053

lib/internal/worker.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
/* global SharedArrayBuffer */
4+
35
const { Object } = primordials;
46

57
const EventEmitter = require('events');
@@ -16,6 +18,7 @@ const {
1618
const { validateString } = require('internal/validators');
1719
const { getOptionValue } = require('internal/options');
1820

21+
const workerIo = require('internal/worker/io');
1922
const {
2023
drainMessagePort,
2124
MessageChannel,
@@ -26,8 +29,8 @@ const {
2629
kStdioWantsMoreDataCallback,
2730
setupPortReferencing,
2831
ReadableWorkerStdio,
29-
WritableWorkerStdio,
30-
} = require('internal/worker/io');
32+
WritableWorkerStdio
33+
} = workerIo;
3134
const { deserializeError } = require('internal/error-serdes');
3235
const { pathToFileURL } = require('url');
3336

@@ -50,6 +53,17 @@ const kParentSideStdio = Symbol('kParentSideStdio');
5053
const SHARE_ENV = Symbol.for('nodejs.worker_threads.SHARE_ENV');
5154
const debug = require('internal/util/debuglog').debuglog('worker');
5255

56+
let cwdCounter;
57+
58+
if (isMainThread) {
59+
cwdCounter = new Uint32Array(new SharedArrayBuffer(4));
60+
const originalChdir = process.chdir;
61+
process.chdir = function(path) {
62+
Atomics.add(cwdCounter, 0, 1);
63+
originalChdir(path);
64+
};
65+
}
66+
5367
class Worker extends EventEmitter {
5468
constructor(filename, options = {}) {
5569
super();
@@ -131,6 +145,7 @@ class Worker extends EventEmitter {
131145
type: messageTypes.LOAD_SCRIPT,
132146
filename,
133147
doEval: !!options.eval,
148+
cwdCounter: cwdCounter || workerIo.sharedCwdCounter,
134149
workerData: options.workerData,
135150
publicPort: port2,
136151
manifestSrc: getOptionValue('--experimental-policy') ?

test/parallel/test-process-abort.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
if (!common.isMainThread)
7+
common.skip('process.abort() is not available in Workers');
8+
9+
// Check that our built-in methods do not have a prototype/constructor behaviour
10+
// if they don't need to. This could be tested for any of our C++ methods.
11+
assert.strictEqual(process.abort.prototype, undefined);
12+
assert.throws(() => new process.abort(), TypeError);

test/parallel/test-process-chdir.js

-5
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,3 @@ const err = {
4242
};
4343
common.expectsError(function() { process.chdir({}); }, err);
4444
common.expectsError(function() { process.chdir(); }, err);
45-
46-
// Check that our built-in methods do not have a prototype/constructor behaviour
47-
// if they don't need to. This could be tested for any of our C++ methods.
48-
assert.strictEqual(process.cwd.prototype, undefined);
49-
assert.throws(() => new process.cwd(), TypeError);
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
const assert = require('assert');
3+
const common = require('../common');
4+
const { Worker, isMainThread, parentPort } = require('worker_threads');
5+
6+
// Do not use isMainThread directly, otherwise the test would time out in case
7+
// it's started inside of another worker thread.
8+
if (!process.env.HAS_STARTED_WORKER) {
9+
process.env.HAS_STARTED_WORKER = '1';
10+
if (!isMainThread) {
11+
common.skip('This test can only run as main thread');
12+
}
13+
const w = new Worker(__filename);
14+
process.chdir('..');
15+
w.on('message', common.mustCall((message) => {
16+
assert.strictEqual(message, process.cwd());
17+
process.chdir('..');
18+
w.postMessage(process.cwd());
19+
}));
20+
} else if (!process.env.SECOND_WORKER) {
21+
process.env.SECOND_WORKER = '1';
22+
const firstCwd = process.cwd();
23+
const w = new Worker(__filename);
24+
w.on('message', common.mustCall((message) => {
25+
assert.strictEqual(message, process.cwd());
26+
parentPort.postMessage(firstCwd);
27+
parentPort.onmessage = common.mustCall((obj) => {
28+
const secondCwd = process.cwd();
29+
assert.strictEqual(secondCwd, obj.data);
30+
assert.notStrictEqual(secondCwd, firstCwd);
31+
w.postMessage(secondCwd);
32+
parentPort.unref();
33+
});
34+
}));
35+
} else {
36+
const firstCwd = process.cwd();
37+
parentPort.postMessage(firstCwd);
38+
parentPort.onmessage = common.mustCall((obj) => {
39+
const secondCwd = process.cwd();
40+
assert.strictEqual(secondCwd, obj.data);
41+
assert.notStrictEqual(secondCwd, firstCwd);
42+
process.exit();
43+
});
44+
}

0 commit comments

Comments
 (0)