Skip to content

Commit 06f8154

Browse files
authored
One concurrent read (merge #1697)
2 parents ac9bb3b + 28389e3 commit 06f8154

File tree

7 files changed

+96
-33
lines changed

7 files changed

+96
-33
lines changed

packages/compartment-mapper/src/import-hook.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ export const makeImportHookMaker = (
257257
}
258258
}
259259

260-
const { read } = unpackReadPowers(readPowers);
260+
const { maybeRead } = unpackReadPowers(readPowers);
261261

262262
for (const candidateSpecifier of candidates) {
263263
const candidateModuleDescriptor = moduleDescriptors[candidateSpecifier];
@@ -298,9 +298,7 @@ export const makeImportHookMaker = (
298298
packageLocation,
299299
);
300300
// eslint-disable-next-line no-await-in-loop
301-
const moduleBytes = await read(moduleLocation).catch(
302-
_error => undefined,
303-
);
301+
const moduleBytes = await maybeRead(moduleLocation);
304302
if (moduleBytes !== undefined) {
305303
/** @type {string | undefined} */
306304
let sourceMap;

packages/compartment-mapper/src/node-modules.js

+14-14
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33

44
/** @typedef {import('./types.js').Language} Language */
55
/** @typedef {import('./types.js').ReadFn} ReadFn */
6+
/** @typedef {import('./types.js').MaybeReadFn} MaybeReadFn */
67
/** @typedef {import('./types.js').CanonicalFn} CanonicalFn */
78
/** @typedef {import('./types.js').CompartmentMapDescriptor} CompartmentMapDescriptor */
89
/** @typedef {import('./types.js').ModuleDescriptor} ModuleDescriptor */
910
/** @typedef {import('./types.js').ScopeDescriptor} ScopeDescriptor */
1011
/** @typedef {import('./types.js').CompartmentDescriptor} CompartmentDescriptor */
1112
/** @typedef {import('./types.js').ReadPowers} ReadPowers */
13+
/** @typedef {import('./types.js').MaybeReadPowers} MaybeReadPowers */
1214

1315
/**
1416
* The graph is an intermediate object model that the functions of this module
@@ -83,15 +85,13 @@ const basename = location => {
8385
};
8486

8587
/**
86-
* @param {ReadFn} read
88+
* @param {MaybeReadFn} maybeRead
8789
* @param {string} packageLocation
8890
* @returns {Promise<object>}
8991
*/
90-
const readDescriptor = async (read, packageLocation) => {
92+
const readDescriptor = async (maybeRead, packageLocation) => {
9193
const descriptorLocation = resolveLocation('package.json', packageLocation);
92-
const descriptorBytes = await read(descriptorLocation).catch(
93-
_error => undefined,
94-
);
94+
const descriptorBytes = await maybeRead(descriptorLocation);
9595
if (descriptorBytes === undefined) {
9696
return undefined;
9797
}
@@ -102,16 +102,16 @@ const readDescriptor = async (read, packageLocation) => {
102102

103103
/**
104104
* @param {Record<string, object>} memo
105-
* @param {ReadFn} read
105+
* @param {MaybeReadFn} maybeRead
106106
* @param {string} packageLocation
107107
* @returns {Promise<object>}
108108
*/
109-
const readDescriptorWithMemo = async (memo, read, packageLocation) => {
109+
const readDescriptorWithMemo = async (memo, maybeRead, packageLocation) => {
110110
let promise = memo[packageLocation];
111111
if (promise !== undefined) {
112112
return promise;
113113
}
114-
promise = readDescriptor(read, packageLocation);
114+
promise = readDescriptor(maybeRead, packageLocation);
115115
memo[packageLocation] = promise;
116116
return promise;
117117
};
@@ -479,7 +479,7 @@ const gatherDependency = async (
479479
* the URL that was used as the key of graph).
480480
* The URLs in dependencies will all exist as other keys of graph.
481481
*
482-
* @param {ReadFn} read
482+
* @param {MaybeReadFn} maybeRead
483483
* @param {CanonicalFn} canonical
484484
* @param {string} packageLocation - location of the main package.
485485
* @param {Set<string>} tags
@@ -490,7 +490,7 @@ const gatherDependency = async (
490490
* @param {Record<string,string>} [commonDependencies] - dependencies to be added to all packages
491491
*/
492492
const graphPackages = async (
493-
read,
493+
maybeRead,
494494
canonical,
495495
packageLocation,
496496
tags,
@@ -504,7 +504,7 @@ const graphPackages = async (
504504
* @returns {Promise<object>}
505505
*/
506506
const readDescriptor = packageLocation =>
507-
readDescriptorWithMemo(memo, read, packageLocation);
507+
readDescriptorWithMemo(memo, maybeRead, packageLocation);
508508

509509
if (mainPackageDescriptor !== undefined) {
510510
memo[packageLocation] = Promise.resolve(mainPackageDescriptor);
@@ -697,7 +697,7 @@ const translateGraph = (
697697
};
698698

699699
/**
700-
* @param {ReadFn | ReadPowers} readPowers
700+
* @param {ReadFn | ReadPowers | MaybeReadPowers} readPowers
701701
* @param {string} packageLocation
702702
* @param {Set<string>} tags
703703
* @param {object} packageDescriptor
@@ -717,9 +717,9 @@ export const compartmentMapForNodeModules = async (
717717
options = {},
718718
) => {
719719
const { dev = false, commonDependencies, policy } = options;
720-
const { read, canonical } = unpackReadPowers(readPowers);
720+
const { maybeRead, canonical } = unpackReadPowers(readPowers);
721721
const graph = await graphPackages(
722-
read,
722+
maybeRead,
723723
canonical,
724724
packageLocation,
725725
tags,

packages/compartment-mapper/src/node-powers.js

+29-3
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,43 @@ const makeReadPowersSloppy = ({ fs, url = undefined, crypto = undefined }) => {
4040
const pathToFileURL =
4141
url === undefined ? fakePathToFileURL : url.pathToFileURL;
4242

43+
let readMutex = Promise.resolve(undefined);
44+
4345
/**
4446
* @param {string} location
4547
*/
4648
const read = async location => {
49+
const promise = readMutex;
50+
let release = Function.prototype;
51+
readMutex = new Promise(resolve => {
52+
release = resolve;
53+
});
54+
await promise;
55+
56+
const path = fileURLToPath(location);
4757
try {
48-
const path = fileURLToPath(location);
58+
// We await here to ensure that we release the mutex only after
59+
// completing the read.
4960
return await fs.promises.readFile(path);
50-
} catch (error) {
51-
throw Error(error.message);
61+
} finally {
62+
release(undefined);
5263
}
5364
};
5465

66+
/**
67+
* @param {string} location
68+
*/
69+
const maybeRead = location =>
70+
read(location).catch(error => {
71+
if (
72+
error.message.startsWith('ENOENT: ') ||
73+
error.message.startsWith('EISDIR: ')
74+
) {
75+
return undefined;
76+
}
77+
throw error;
78+
});
79+
5580
const requireResolve = (from, specifier, options) =>
5681
createRequire(from).resolve(specifier, options);
5782

@@ -97,6 +122,7 @@ const makeReadPowersSloppy = ({ fs, url = undefined, crypto = undefined }) => {
97122

98123
return {
99124
read,
125+
maybeRead,
100126
fileURLToPath,
101127
pathToFileURL,
102128
canonical,
+32-11
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,42 @@
11
// @ts-check
2-
/** @typedef {import('./types.js').ReadFn} ReadFn */
3-
/** @typedef {import('./types.js').CanonicalFn} CanonicalFn */
4-
/** @typedef {import('./types.js').ReadPowers} ReadPowers */
52

6-
/** @type {CanonicalFn} */
3+
/** @type {import('./types.js').CanonicalFn} */
74
const canonicalShim = async path => path;
85

96
/**
10-
* @param {ReadFn | ReadPowers} powers
11-
* @returns {ReadPowers}
7+
* @param {import('./types.js').ReadFn | import('./types.js').ReadPowers | import('./types.js').MaybeReadPowers} powers
8+
* @returns {import('./types.js').MaybeReadPowers}
129
*/
1310
export const unpackReadPowers = powers => {
11+
/** @type {import('./types.js').ReadFn | undefined} */
12+
let read;
13+
/** @type {import('./types.js').MaybeReadFn | undefined} */
14+
let maybeRead;
15+
/** @type {import('./types.js').CanonicalFn | undefined} */
16+
let canonical;
17+
1418
if (typeof powers === 'function') {
15-
return {
16-
read: powers,
17-
canonical: canonicalShim,
18-
};
19+
read = powers;
20+
} else {
21+
({ read, maybeRead, canonical } = powers);
22+
}
23+
24+
if (canonical === undefined) {
25+
canonical = canonicalShim;
1926
}
20-
return powers;
27+
28+
if (maybeRead === undefined) {
29+
/** @param {string} path */
30+
maybeRead = path =>
31+
/** @type {import('./types.js').ReadFn} */ (read)(path).catch(
32+
_error => undefined,
33+
);
34+
}
35+
36+
return {
37+
...powers,
38+
read,
39+
maybeRead,
40+
canonical,
41+
};
2142
};

packages/compartment-mapper/src/types.js

+13
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ export {};
113113
* @returns {Promise<Uint8Array>} bytes
114114
*/
115115

116+
/**
117+
* A resolution of `undefined` indicates `ENOENT` or the equivalent.
118+
*
119+
* @callback MaybeReadFn
120+
* @param {string} location
121+
* @returns {Promise<Uint8Array | undefined>} bytes
122+
*/
123+
116124
/**
117125
* Returns a canonical URL for a given URL, following redirects or symbolic
118126
* links if any exist along the path.
@@ -156,6 +164,11 @@ export {};
156164
* @property {Function} [requireResolve]
157165
*/
158166

167+
/**
168+
* @typedef {ReadPowers | object} MaybeReadPowers
169+
* @property {MaybeReadFn} maybeRead
170+
*/
171+
159172
/**
160173
* @typedef {object} HashPowers
161174
* @property {ReadFn} read

packages/compartment-mapper/test/scaffold.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import {
1515
} from '../index.js';
1616
import { makeReadPowers } from '../src/node-powers.js';
1717

18-
export const readPowers = makeReadPowers({ fs, crypto, url });
18+
export const readPowers = makeReadPowers({
19+
fs,
20+
crypto,
21+
url,
22+
});
1923

2024
export const sanitizePaths = (text = '', tolerateLineChange = false) => {
2125
if (tolerateLineChange) {

packages/nat/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"./package.json": "./package.json"
1010
},
1111
"scripts": {
12+
"build": "exit 0",
1213
"test": "ava test/**/test-*.js",
1314
"lint-fix": "eslint --fix",
1415
"lint-check": "eslint"

0 commit comments

Comments
 (0)