module: fix stat cache

The current caching logic broke by [0] because it used destructuring
on the module arguments. Since the exported property is a primitive
counting it up or down would not have any effect anymore in the module
that required that property.

The original implementation would cache all stat calls caused during
bootstrap. Afterwards it would clear the cache and lazy require calls
during runtime would create a new cascading cache for the then
loaded modules and clear the cache again.
This behavior is now restored. This is difficult to test without
exposing a lot of information and therfore the existing tests have
been removed (as they could not detect the issue).

With the broken implementation it caused each module compilation to
reset the cache and therefore minimizing the effect drastically.

[0] https://github.com/nodejs/node/pull/19177

PR-URL: https://github.com/nodejs/node/pull/26266
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Ruben Bridgewater 2019-02-22 18:52:38 +01:00
parent 3fce5cf439
commit 410eb97bce
No known key found for this signature in database
GPG Key ID: F07496B3EB3C1762
5 changed files with 15 additions and 58 deletions

View File

@ -11,12 +11,7 @@ function makeRequireFunction(mod) {
const Module = mod.constructor; const Module = mod.constructor;
function require(path) { function require(path) {
try { return mod.require(path);
exports.requireDepth += 1;
return mod.require(path);
} finally {
exports.requireDepth -= 1;
}
} }
function resolve(request, options) { function resolve(request, options) {
@ -139,7 +134,6 @@ module.exports = exports = {
builtinLibs, builtinLibs,
makeRequireFunction, makeRequireFunction,
normalizeReferrerURL, normalizeReferrerURL,
requireDepth: 0,
stripBOM, stripBOM,
stripShebang stripShebang
}; };

View File

@ -37,7 +37,6 @@ const { safeGetenv } = internalBinding('credentials');
const { const {
makeRequireFunction, makeRequireFunction,
normalizeReferrerURL, normalizeReferrerURL,
requireDepth,
stripBOM, stripBOM,
stripShebang stripShebang
} = require('internal/modules/cjs/helpers'); } = require('internal/modules/cjs/helpers');
@ -85,18 +84,17 @@ const {
const isWindows = process.platform === 'win32'; const isWindows = process.platform === 'win32';
let requireDepth = 0;
let statCache = new Map();
function stat(filename) { function stat(filename) {
filename = path.toNamespacedPath(filename); filename = path.toNamespacedPath(filename);
const cache = stat.cache; if (statCache === null) statCache = new Map();
if (cache !== null) { let result = statCache.get(filename);
const result = cache.get(filename); if (result !== undefined) return result;
if (result !== undefined) return result; result = internalModuleStat(filename);
} statCache.set(filename, result);
const result = internalModuleStat(filename);
if (cache !== null) cache.set(filename, result);
return result; return result;
} }
stat.cache = null;
function updateChildren(parent, child, scan) { function updateChildren(parent, child, scan) {
var children = parent && parent.children; var children = parent && parent.children;
@ -710,7 +708,12 @@ Module.prototype.require = function(id) {
throw new ERR_INVALID_ARG_VALUE('id', id, throw new ERR_INVALID_ARG_VALUE('id', id,
'must be a non-empty string'); 'must be a non-empty string');
} }
return Module._load(id, this, /* isMain */ false); requireDepth++;
try {
return Module._load(id, this, /* isMain */ false);
} finally {
requireDepth--;
}
}; };
@ -793,8 +796,6 @@ Module.prototype._compile = function(content, filename) {
} }
var dirname = path.dirname(filename); var dirname = path.dirname(filename);
var require = makeRequireFunction(this); var require = makeRequireFunction(this);
var depth = requireDepth;
if (depth === 0) stat.cache = new Map();
var result; var result;
var exports = this.exports; var exports = this.exports;
var thisValue = exports; var thisValue = exports;
@ -806,7 +807,7 @@ Module.prototype._compile = function(content, filename) {
result = compiledWrapper.call(thisValue, exports, require, module, result = compiledWrapper.call(thisValue, exports, require, module,
filename, dirname); filename, dirname);
} }
if (depth === 0) stat.cache = null; if (requireDepth === 0) statCache = null;
return result; return result;
}; };

View File

@ -1,11 +0,0 @@
// Flags: --expose_internals
'use strict';
const assert = require('assert');
const {
requireDepth
} = require('internal/modules/cjs/helpers');
exports.requireDepth = requireDepth;
assert.strictEqual(requireDepth, 1);
assert.deepStrictEqual(require('./two'), { requireDepth: 2 });
assert.strictEqual(requireDepth, 1);

View File

@ -1,11 +0,0 @@
// Flags: --expose_internals
'use strict';
const assert = require('assert');
const {
requireDepth
} = require('internal/modules/cjs/helpers');
exports.requireDepth = requireDepth;
assert.strictEqual(requireDepth, 2);
assert.deepStrictEqual(require('./one'), { requireDepth: 1 });
assert.strictEqual(requireDepth, 2);

View File

@ -1,16 +0,0 @@
// Flags: --expose_internals
'use strict';
require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const {
requireDepth
} = require('internal/modules/cjs/helpers');
// Module one loads two too so the expected depth for two is, well, two.
assert.strictEqual(requireDepth, 0);
const one = require(fixtures.path('module-require-depth', 'one'));
const two = require(fixtures.path('module-require-depth', 'two'));
assert.deepStrictEqual(one, { requireDepth: 1 });
assert.deepStrictEqual(two, { requireDepth: 2 });
assert.strictEqual(requireDepth, 0);