Skip to content

Commit 5baf322

Browse files
committed
fixup! lib: allow CJS source map cache to be reclaimed
1 parent af592c5 commit 5baf322

File tree

3 files changed

+47
-33
lines changed

3 files changed

+47
-33
lines changed

lib/internal/source_map/source_map_cache_map.js

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,94 +5,99 @@ const {
55
ObjectFreeze,
66
SafeFinalizationRegistry,
77
SafeMap,
8-
SafeWeakMap,
98
SafeWeakRef,
109
SymbolIterator,
1110
} = primordials;
11+
const {
12+
privateSymbols: {
13+
source_map_data_private_symbol,
14+
},
15+
} = internalBinding('util');
1216

1317
/**
14-
* Specialized WeakMap that caches source map entries by `filename` and `sourceURL`.
15-
* Cached entries can be iterated with `for..of`.
18+
* Specialized map of WeakRefs to module instances that caches source map
19+
* entries by `filename` and `sourceURL`. Cached entries can be iterated with
20+
* `for..of` syntax.
1621
*
1722
* The cache map maintains the cache entries by:
18-
* - `weakTargetMap`(Map): a strong sourceURL -> WeakRef(Module),
19-
* - `weakMap`(WeakMap): a Module instance object -> source map data.
23+
* - `weakModuleMap`(Map): a strong sourceURL -> WeakRef(Module),
24+
* - WeakRef(Module[source_map_data_private_symbol]): source map data.
2025
*
21-
* Obsolete `weakTargetMap` entries are removed by the `finalizationRegistry` callback.
22-
* This pattern decouples the strong url reference to the source map data and allow the
23-
* cache to be reclaimed eagerly, without depending on a undeterministic callback of a
24-
* finalization registry.
26+
* Obsolete `weakModuleMap` entries are removed by the `finalizationRegistry`
27+
* callback. This pattern decouples the strong url reference to the source map
28+
* data and allow the cache to be reclaimed eagerly, without depending on an
29+
* undeterministic callback of a finalization registry.
2530
*/
2631
class SourceMapCacheMap {
2732
/**
2833
* @type {Map<string, WeakRef<*>>}
2934
* The cached module instance can be removed from the global module registry
3035
* with approaches like mutating `require.cache`.
31-
* The `weakTargetMap` exposes entries by `filename` and `sourceURL`.
36+
* The `weakModuleMap` exposes entries by `filename` and `sourceURL`.
3237
* In the case of mutated module registry, obsolete entries are removed from
3338
* the cache by the `finalizationRegistry`.
3439
*/
35-
#weakTargetMap = new SafeMap();
36-
#weakMap = new SafeWeakMap();
40+
#weakModuleMap = new SafeMap();
3741

3842
#cleanup = ({ keys }) => {
3943
// Delete the entry if the weak target has been reclaimed.
4044
// If the weak target is not reclaimed, the entry was overridden by a new
4145
// weak target.
4246
ArrayPrototypeForEach(keys, (key) => {
43-
const ref = this.#weakTargetMap.get(key);
47+
const ref = this.#weakModuleMap.get(key);
4448
if (ref && ref.deref() === undefined) {
45-
this.#weakTargetMap.delete(key);
49+
this.#weakModuleMap.delete(key);
4650
}
4751
});
4852
};
4953
#finalizationRegistry = new SafeFinalizationRegistry(this.#cleanup);
5054

5155
/**
52-
* Sets the value for the given key, associated with the given weakTarget.
56+
* Sets the value for the given key, associated with the given module
57+
* instance.
5358
* @param {string[]} keys array of urls to index the value entry.
54-
* @param {*} value the value entry.
55-
* @param {object} weakTarget an object that can be weakly referenced and
56-
* invalidate the [key, value] entry once this object is reclaimed.
59+
* @param {*} sourceMapData the value entry.
60+
* @param {object} moduleInstance an object that can be weakly referenced and
61+
* invalidate the [key, value] entry after this object is reclaimed.
5762
*/
58-
set(keys, value, weakTarget) {
59-
const weakRef = new SafeWeakRef(weakTarget);
60-
ArrayPrototypeForEach(keys, (key) => this.#weakTargetMap.set(key, weakRef));
61-
this.#weakMap.set(weakTarget, value);
62-
this.#finalizationRegistry.register(weakTarget, { keys });
63+
set(keys, sourceMapData, moduleInstance) {
64+
const weakRef = new SafeWeakRef(moduleInstance);
65+
ArrayPrototypeForEach(keys, (key) => this.#weakModuleMap.set(key, weakRef));
66+
moduleInstance[source_map_data_private_symbol] = sourceMapData;
67+
this.#finalizationRegistry.register(moduleInstance, { keys });
6368
}
6469

6570
/**
6671
* Get an entry by the given key.
6772
* @param {string} key a file url or source url
6873
*/
6974
get(key) {
70-
const weakRef = this.#weakTargetMap.get(key);
71-
const target = weakRef?.deref();
72-
if (target === undefined) {
75+
const weakRef = this.#weakModuleMap.get(key);
76+
const moduleInstance = weakRef?.deref();
77+
if (moduleInstance === undefined) {
7378
return;
7479
}
75-
return this.#weakMap.get(target);
80+
return moduleInstance[source_map_data_private_symbol];
7681
}
7782

7883
/**
7984
* Estimate the size of the cache. The actual size may be smaller because
8085
* some entries may be reclaimed with the module instance.
8186
*/
8287
get size() {
83-
return this.#weakTargetMap.size;
88+
return this.#weakModuleMap.size;
8489
}
8590

8691
[SymbolIterator]() {
87-
const iterator = this.#weakTargetMap.entries();
92+
const iterator = this.#weakModuleMap.entries();
8893

8994
const next = () => {
9095
const result = iterator.next();
9196
if (result.done) return result;
9297
const { 0: key, 1: weakRef } = result.value;
93-
const target = weakRef.deref();
94-
if (target == null) return next();
95-
const value = this.#weakMap.get(target);
98+
const moduleInstance = weakRef.deref();
99+
if (moduleInstance == null) return next();
100+
const value = moduleInstance[source_map_data_private_symbol];
96101
return { done: false, value: [key, value] };
97102
};
98103

src/env_properties.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
V(untransferable_object_private_symbol, "node:untransferableObject") \
3030
V(exit_info_private_symbol, "node:exit_info_private_symbol") \
3131
V(promise_trace_id, "node:promise_trace_id") \
32-
V(require_private_symbol, "node:require_private_symbol")
32+
V(require_private_symbol, "node:require_private_symbol") \
33+
V(source_map_data_private_symbol, "node:source_map_data_private_symbol")
3334

3435
// Symbols are per-isolate primitives but Environment proxies them
3536
// for the sake of convenience.

src/module_wrap.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,14 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
248248
return;
249249
}
250250

251+
// Initialize an empty slot for source map cache before the object is frozen.
252+
if (that->SetPrivate(context,
253+
realm->isolate_data()->source_map_data_private_symbol(),
254+
Undefined(isolate))
255+
.IsNothing()) {
256+
return;
257+
}
258+
251259
// Use the extras object as an object whose GetCreationContext() will be the
252260
// original `context`, since the `Context` itself strictly speaking cannot
253261
// be stored in an internal field.

0 commit comments

Comments
 (0)