@atlaspack/core 2.16.2-canary.13 → 2.16.2-canary.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +284 -0
- package/index.d.ts +4 -0
- package/lib/AssetGraph.js +27 -7
- package/lib/Atlaspack.js +35 -27
- package/lib/AtlaspackConfig.schema.js +7 -1
- package/lib/BundleGraph.js +8 -5
- package/lib/Dependency.js +6 -2
- package/lib/Environment.js +5 -3
- package/lib/EnvironmentManager.js +137 -0
- package/lib/InternalConfig.js +3 -2
- package/lib/PackagerRunner.js +54 -16
- package/lib/RequestTracker.js +345 -132
- package/lib/SymbolPropagation.js +14 -0
- package/lib/Transformation.js +2 -2
- package/lib/UncommittedAsset.js +20 -2
- package/lib/applyRuntimes.js +2 -1
- package/lib/assetUtils.js +2 -1
- package/lib/atlaspack-v3/AtlaspackV3.js +16 -3
- package/lib/atlaspack-v3/worker/compat/environment.js +2 -2
- package/lib/atlaspack-v3/worker/compat/mutable-asset.js +6 -6
- package/lib/atlaspack-v3/worker/compat/plugin-config.js +5 -5
- package/lib/atlaspack-v3/worker/index.js +3 -0
- package/lib/atlaspack-v3/worker/worker.js +8 -0
- package/lib/dumpGraphToGraphViz.js +1 -1
- package/lib/index.js +29 -1
- package/lib/public/Asset.js +7 -9
- package/lib/public/Bundle.js +12 -13
- package/lib/public/BundleGraph.js +3 -2
- package/lib/public/BundleGroup.js +2 -3
- package/lib/public/Config.js +95 -8
- package/lib/public/Dependency.js +4 -4
- package/lib/public/Environment.js +2 -3
- package/lib/public/MutableBundleGraph.js +5 -4
- package/lib/public/PluginOptions.js +1 -2
- package/lib/public/Target.js +4 -4
- package/lib/requests/AssetGraphRequest.js +13 -1
- package/lib/requests/AssetGraphRequestRust.js +17 -2
- package/lib/requests/AssetRequest.js +2 -1
- package/lib/requests/BundleGraphRequest.js +13 -1
- package/lib/requests/ConfigRequest.js +27 -4
- package/lib/requests/DevDepRequest.js +11 -1
- package/lib/requests/PathRequest.js +10 -0
- package/lib/requests/TargetRequest.js +18 -16
- package/lib/requests/WriteBundleRequest.js +15 -3
- package/lib/requests/WriteBundlesRequest.js +22 -1
- package/lib/resolveOptions.js +7 -4
- package/lib/worker.js +18 -1
- package/package.json +18 -25
- package/src/AssetGraph.js +30 -7
- package/src/Atlaspack.js +40 -23
- package/src/BundleGraph.js +13 -8
- package/src/Dependency.js +13 -5
- package/src/Environment.js +9 -6
- package/src/EnvironmentManager.js +145 -0
- package/src/InternalConfig.js +6 -5
- package/src/PackagerRunner.js +72 -20
- package/src/RequestTracker.js +526 -157
- package/src/SymbolPropagation.js +13 -1
- package/src/UncommittedAsset.js +23 -3
- package/src/applyRuntimes.js +6 -1
- package/src/assetUtils.js +4 -3
- package/src/atlaspack-v3/AtlaspackV3.js +24 -3
- package/src/atlaspack-v3/worker/compat/plugin-config.js +9 -5
- package/src/atlaspack-v3/worker/index.js +2 -1
- package/src/atlaspack-v3/worker/worker.js +7 -0
- package/src/index.js +5 -1
- package/src/public/Asset.js +13 -6
- package/src/public/Bundle.js +12 -11
- package/src/public/BundleGraph.js +10 -2
- package/src/public/BundleGroup.js +2 -2
- package/src/public/Config.js +132 -18
- package/src/public/Dependency.js +4 -3
- package/src/public/Environment.js +2 -2
- package/src/public/MutableBundleGraph.js +8 -5
- package/src/public/PluginOptions.js +1 -1
- package/src/public/Target.js +4 -3
- package/src/requests/AssetGraphRequest.js +13 -3
- package/src/requests/AssetGraphRequestRust.js +14 -2
- package/src/requests/AssetRequest.js +2 -1
- package/src/requests/BundleGraphRequest.js +13 -3
- package/src/requests/ConfigRequest.js +33 -9
- package/src/requests/DevDepRequest.js +22 -9
- package/src/requests/PathRequest.js +4 -0
- package/src/requests/TargetRequest.js +19 -25
- package/src/requests/WriteBundleRequest.js +14 -8
- package/src/requests/WriteBundlesRequest.js +31 -3
- package/src/resolveOptions.js +4 -2
- package/src/types.js +10 -7
- package/test/Environment.test.js +43 -34
- package/test/EnvironmentManager.test.js +192 -0
- package/test/PublicEnvironment.test.js +10 -7
- package/test/RequestTracker.test.js +124 -29
- package/test/public/Config.test.js +108 -0
- package/test/requests/ConfigRequest.test.js +199 -7
- package/test/test-utils.js +4 -9
package/lib/RequestTracker.js
CHANGED
|
@@ -6,9 +6,13 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.RequestGraph = void 0;
|
|
7
7
|
exports.cleanUpOrphans = cleanUpOrphans;
|
|
8
8
|
exports.default = void 0;
|
|
9
|
+
exports.getBiggestFSEventsInvalidations = getBiggestFSEventsInvalidations;
|
|
9
10
|
exports.getWatcherOptions = getWatcherOptions;
|
|
11
|
+
exports.invalidateRequestGraph = invalidateRequestGraph;
|
|
12
|
+
exports.invalidateRequestGraphFSEvents = invalidateRequestGraphFSEvents;
|
|
10
13
|
exports.readAndDeserializeRequestGraph = readAndDeserializeRequestGraph;
|
|
11
14
|
exports.requestTypes = exports.requestGraphEdgeTypes = void 0;
|
|
15
|
+
exports.runInvalidation = runInvalidation;
|
|
12
16
|
function _assert() {
|
|
13
17
|
const data = _interopRequireWildcard(require("assert"));
|
|
14
18
|
_assert = function () {
|
|
@@ -84,6 +88,14 @@ var _projectPath = require("./projectPath");
|
|
|
84
88
|
var _ReporterRunner = require("./ReporterRunner");
|
|
85
89
|
var _ConfigRequest = require("./requests/ConfigRequest");
|
|
86
90
|
var _utils2 = require("./utils");
|
|
91
|
+
function _perf_hooks() {
|
|
92
|
+
const data = require("perf_hooks");
|
|
93
|
+
_perf_hooks = function () {
|
|
94
|
+
return data;
|
|
95
|
+
};
|
|
96
|
+
return data;
|
|
97
|
+
}
|
|
98
|
+
var _EnvironmentManager = require("./EnvironmentManager");
|
|
87
99
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
88
100
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
89
101
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
@@ -151,7 +163,7 @@ const nodeFromOption = (option, value) => ({
|
|
|
151
163
|
hash: (0, _utils2.hashFromOption)(value)
|
|
152
164
|
});
|
|
153
165
|
const nodeFromConfigKey = (fileName, configKey, contentHash) => ({
|
|
154
|
-
id: `config_key:${(0, _projectPath.fromProjectPathRelative)(fileName)}:${configKey}`,
|
|
166
|
+
id: `config_key:${(0, _projectPath.fromProjectPathRelative)(fileName)}:${JSON.stringify(configKey)}`,
|
|
155
167
|
type: CONFIG_KEY,
|
|
156
168
|
configKey,
|
|
157
169
|
contentHash
|
|
@@ -279,6 +291,12 @@ class RequestGraph extends _graph().ContentGraph {
|
|
|
279
291
|
// If the node is invalidated, the cached request chunk on disk needs to be re-written
|
|
280
292
|
this.removeCachedRequestChunkForNode(nodeId);
|
|
281
293
|
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Nodes that are invalidated on start-up, such as JavaScript babel configuration files which are
|
|
297
|
+
* imported when the build kicks-off and might doing arbitrary work such as reading from the file
|
|
298
|
+
* system.
|
|
299
|
+
*/
|
|
282
300
|
invalidateUnpredictableNodes() {
|
|
283
301
|
for (let nodeId of this.unpredicatableNodeIds) {
|
|
284
302
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
@@ -286,6 +304,10 @@ class RequestGraph extends _graph().ContentGraph {
|
|
|
286
304
|
this.invalidateNode(nodeId, _constants.STARTUP);
|
|
287
305
|
}
|
|
288
306
|
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Effectively uncacheable nodes.
|
|
310
|
+
*/
|
|
289
311
|
invalidateOnBuildNodes() {
|
|
290
312
|
for (let nodeId of this.invalidateOnBuildNodeIds) {
|
|
291
313
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
@@ -293,29 +315,45 @@ class RequestGraph extends _graph().ContentGraph {
|
|
|
293
315
|
this.invalidateNode(nodeId, _constants.STARTUP);
|
|
294
316
|
}
|
|
295
317
|
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Nodes invalidated by environment changes, corresponds to `env: ...` inputs.
|
|
321
|
+
*/
|
|
296
322
|
invalidateEnvNodes(env) {
|
|
323
|
+
const invalidatedKeys = [];
|
|
297
324
|
for (let nodeId of this.envNodeIds) {
|
|
298
325
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
299
326
|
(0, _assert().default)(node.type === ENV);
|
|
300
|
-
|
|
327
|
+
const key = keyFromEnvContentKey(node.id);
|
|
328
|
+
if (env[key] !== node.value) {
|
|
329
|
+
invalidatedKeys.push(key);
|
|
301
330
|
let parentNodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update);
|
|
302
331
|
for (let parentNode of parentNodes) {
|
|
303
332
|
this.invalidateNode(parentNode, _constants.ENV_CHANGE);
|
|
304
333
|
}
|
|
305
334
|
}
|
|
306
335
|
}
|
|
336
|
+
return invalidatedKeys;
|
|
307
337
|
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Nodes invalidated by option changes.
|
|
341
|
+
*/
|
|
308
342
|
invalidateOptionNodes(options) {
|
|
343
|
+
const invalidatedKeys = [];
|
|
309
344
|
for (let nodeId of this.optionNodeIds) {
|
|
310
345
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
311
346
|
(0, _assert().default)(node.type === OPTION);
|
|
312
|
-
|
|
347
|
+
const key = keyFromOptionContentKey(node.id);
|
|
348
|
+
if ((0, _utils2.hashFromOption)(options[key]) !== node.hash) {
|
|
349
|
+
invalidatedKeys.push(key);
|
|
313
350
|
let parentNodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update);
|
|
314
351
|
for (let parentNode of parentNodes) {
|
|
315
352
|
this.invalidateNode(parentNode, _constants.OPTION_CHANGE);
|
|
316
353
|
}
|
|
317
354
|
}
|
|
318
355
|
}
|
|
356
|
+
return invalidatedKeys;
|
|
319
357
|
}
|
|
320
358
|
invalidateOnConfigKeyChange(requestNodeId, filePath, configKey, contentHash) {
|
|
321
359
|
let configKeyNodeId = this.addNode(nodeFromConfigKey(filePath, configKey, contentHash));
|
|
@@ -408,8 +446,8 @@ class RequestGraph extends _graph().ContentGraph {
|
|
|
408
446
|
this.invalidateOnBuildNodeIds.add(requestNodeId);
|
|
409
447
|
}
|
|
410
448
|
invalidateOnEnvChange(requestNodeId, env, value) {
|
|
411
|
-
|
|
412
|
-
|
|
449
|
+
const envNode = nodeFromEnv(env, value);
|
|
450
|
+
const envNodeId = this.addNode(envNode);
|
|
413
451
|
if (!this.hasEdge(requestNodeId, envNodeId, requestGraphEdgeTypes.invalidated_by_update)) {
|
|
414
452
|
this.addEdge(requestNodeId, envNodeId, requestGraphEdgeTypes.invalidated_by_update);
|
|
415
453
|
}
|
|
@@ -556,10 +594,12 @@ class RequestGraph extends _graph().ContentGraph {
|
|
|
556
594
|
aboveCache.set(fileNameNodeId, above);
|
|
557
595
|
return above;
|
|
558
596
|
};
|
|
597
|
+
const invalidationsByPath = new Map();
|
|
559
598
|
for (let {
|
|
560
599
|
path: _path,
|
|
561
600
|
type
|
|
562
601
|
} of events) {
|
|
602
|
+
const invalidationsBefore = this.getInvalidNodeCount();
|
|
563
603
|
if (!enableOptimization && process.env.ATLASPACK_DISABLE_CACHE_TIMEOUT !== 'true' && ++count === 256) {
|
|
564
604
|
let duration = Date.now() - startTime;
|
|
565
605
|
predictedTime = duration * (events.length >> 8);
|
|
@@ -596,7 +636,10 @@ class RequestGraph extends _graph().ContentGraph {
|
|
|
596
636
|
this.invalidNodeIds.add(id);
|
|
597
637
|
}
|
|
598
638
|
}
|
|
599
|
-
return
|
|
639
|
+
return {
|
|
640
|
+
didInvalidate: true,
|
|
641
|
+
invalidationsByPath: new Map()
|
|
642
|
+
};
|
|
600
643
|
}
|
|
601
644
|
|
|
602
645
|
// sometimes mac os reports update events as create events.
|
|
@@ -653,10 +696,17 @@ class RequestGraph extends _graph().ContentGraph {
|
|
|
653
696
|
this.removeNode(nodeId, removeOrphans);
|
|
654
697
|
}
|
|
655
698
|
let configKeyNodes = this.configKeyNodes.get(_filePath);
|
|
656
|
-
|
|
699
|
+
|
|
700
|
+
// With granular invalidations we will always run this block,
|
|
701
|
+
// so even if we get a create event (for whatever reason), we will still
|
|
702
|
+
// try to limit invalidations from config key changes through hashing.
|
|
703
|
+
//
|
|
704
|
+
// Currently create events can invalidate a large number of nodes due to
|
|
705
|
+
// "create above" invalidations.
|
|
706
|
+
if (configKeyNodes) {
|
|
657
707
|
for (let nodeId of configKeyNodes) {
|
|
658
708
|
let isInvalid = type === 'delete';
|
|
659
|
-
if (type
|
|
709
|
+
if (type !== 'delete') {
|
|
660
710
|
let node = this.getNode(nodeId);
|
|
661
711
|
(0, _assert().default)(node && node.type === CONFIG_KEY);
|
|
662
712
|
let contentHash = await (0, _ConfigRequest.getConfigKeyContentHash)(_filePath, node.configKey, options);
|
|
@@ -671,6 +721,9 @@ class RequestGraph extends _graph().ContentGraph {
|
|
|
671
721
|
}
|
|
672
722
|
}
|
|
673
723
|
}
|
|
724
|
+
const invalidationsAfter = this.getInvalidNodeCount();
|
|
725
|
+
const invalidationsForEvent = invalidationsAfter - invalidationsBefore;
|
|
726
|
+
invalidationsByPath.set(_path, (invalidationsByPath.get(_path) ?? 0) + invalidationsForEvent);
|
|
674
727
|
}
|
|
675
728
|
if ((0, _featureFlags().getFeatureFlag)('fixQuadraticCacheInvalidation')) {
|
|
676
729
|
cleanUpOrphans(this);
|
|
@@ -688,7 +741,10 @@ class RequestGraph extends _graph().ContentGraph {
|
|
|
688
741
|
numberOfInvalidatedNodes: invalidatedNodes.size
|
|
689
742
|
}
|
|
690
743
|
});
|
|
691
|
-
return
|
|
744
|
+
return {
|
|
745
|
+
didInvalidate,
|
|
746
|
+
invalidationsByPath
|
|
747
|
+
};
|
|
692
748
|
}
|
|
693
749
|
hasCachedRequestChunk(index) {
|
|
694
750
|
return this.cachedRequestChunks.has(index);
|
|
@@ -699,6 +755,13 @@ class RequestGraph extends _graph().ContentGraph {
|
|
|
699
755
|
removeCachedRequestChunkForNode(nodeId) {
|
|
700
756
|
this.cachedRequestChunks.delete(Math.floor(nodeId / this.nodesPerBlob));
|
|
701
757
|
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Returns the number of invalidated nodes in the graph.
|
|
761
|
+
*/
|
|
762
|
+
getInvalidNodeCount() {
|
|
763
|
+
return this.invalidNodeIds.size;
|
|
764
|
+
}
|
|
702
765
|
}
|
|
703
766
|
exports.RequestGraph = RequestGraph;
|
|
704
767
|
class RequestTracker {
|
|
@@ -714,11 +777,6 @@ class RequestTracker {
|
|
|
714
777
|
this.options = options;
|
|
715
778
|
this.rustAtlaspack = rustAtlaspack;
|
|
716
779
|
}
|
|
717
|
-
|
|
718
|
-
// TODO: refactor (abortcontroller should be created by RequestTracker)
|
|
719
|
-
setSignal(signal) {
|
|
720
|
-
this.signal = signal;
|
|
721
|
-
}
|
|
722
780
|
startRequest(request) {
|
|
723
781
|
let didPreviouslyExist = this.graph.hasContentKey(request.id);
|
|
724
782
|
let requestNodeId;
|
|
@@ -857,7 +915,6 @@ class RequestTracker {
|
|
|
857
915
|
options: this.options,
|
|
858
916
|
rustAtlaspack: this.rustAtlaspack
|
|
859
917
|
});
|
|
860
|
-
(0, _utils2.assertSignalNotAborted)(this.signal);
|
|
861
918
|
this.completeRequest(requestNodeId);
|
|
862
919
|
deferred.resolve(true);
|
|
863
920
|
return result;
|
|
@@ -895,40 +952,41 @@ class RequestTracker {
|
|
|
895
952
|
}
|
|
896
953
|
createAPI(requestId, previousInvalidations) {
|
|
897
954
|
let subRequestContentKeys = new Set();
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
storeResult
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
return true;
|
|
924
|
-
}
|
|
925
|
-
return false;
|
|
926
|
-
},
|
|
927
|
-
runRequest: (subRequest, opts) => {
|
|
928
|
-
subRequestContentKeys.add(subRequest.id);
|
|
929
|
-
return this.runRequest(subRequest, opts);
|
|
955
|
+
let api = {
|
|
956
|
+
invalidateOnFileCreate: input => this.graph.invalidateOnFileCreate(requestId, input),
|
|
957
|
+
invalidateOnConfigKeyChange: (filePath, configKey, contentHash) => this.graph.invalidateOnConfigKeyChange(requestId, filePath, configKey, contentHash),
|
|
958
|
+
invalidateOnFileDelete: filePath => this.graph.invalidateOnFileDelete(requestId, filePath),
|
|
959
|
+
invalidateOnFileUpdate: filePath => this.graph.invalidateOnFileUpdate(requestId, filePath),
|
|
960
|
+
invalidateOnStartup: () => this.graph.invalidateOnStartup(requestId),
|
|
961
|
+
invalidateOnBuild: () => this.graph.invalidateOnBuild(requestId),
|
|
962
|
+
invalidateOnEnvChange: env => this.graph.invalidateOnEnvChange(requestId, env, this.options.env[env]),
|
|
963
|
+
invalidateOnOptionChange: option => this.graph.invalidateOnOptionChange(requestId, option, this.options[option]),
|
|
964
|
+
getInvalidations: () => previousInvalidations,
|
|
965
|
+
storeResult: (result, cacheKey) => {
|
|
966
|
+
this.storeResult(requestId, result, cacheKey);
|
|
967
|
+
},
|
|
968
|
+
getSubRequests: () => this.graph.getSubRequests(requestId),
|
|
969
|
+
getInvalidSubRequests: () => this.graph.getInvalidSubRequests(requestId),
|
|
970
|
+
getPreviousResult: ifMatch => {
|
|
971
|
+
var _this$graph$getNode;
|
|
972
|
+
let contentKey = (0, _nullthrows().default)((_this$graph$getNode = this.graph.getNode(requestId)) === null || _this$graph$getNode === void 0 ? void 0 : _this$graph$getNode.id);
|
|
973
|
+
return this.getRequestResult(contentKey, ifMatch);
|
|
974
|
+
},
|
|
975
|
+
getRequestResult: id => this.getRequestResult(id),
|
|
976
|
+
canSkipSubrequest: contentKey => {
|
|
977
|
+
if (this.graph.hasContentKey(contentKey) && this.hasValidResult(this.graph.getNodeIdByContentKey(contentKey))) {
|
|
978
|
+
subRequestContentKeys.add(contentKey);
|
|
979
|
+
return true;
|
|
930
980
|
}
|
|
981
|
+
return false;
|
|
931
982
|
},
|
|
983
|
+
runRequest: (subRequest, opts) => {
|
|
984
|
+
subRequestContentKeys.add(subRequest.id);
|
|
985
|
+
return this.runRequest(subRequest, opts);
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
return {
|
|
989
|
+
api,
|
|
932
990
|
subRequestContentKeys
|
|
933
991
|
};
|
|
934
992
|
}
|
|
@@ -944,84 +1002,88 @@ class RequestTracker {
|
|
|
944
1002
|
return result;
|
|
945
1003
|
}
|
|
946
1004
|
}
|
|
947
|
-
await runCacheImprovements(async cache => {
|
|
948
|
-
await cache.getNativeRef().startWriteTransaction();
|
|
949
|
-
}, () => Promise.resolve());
|
|
950
1005
|
let cacheKey = getCacheKey(this.options);
|
|
951
|
-
let requestGraphKey = `requestGraph-${cacheKey}`;
|
|
1006
|
+
let requestGraphKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/RequestGraph` : `requestGraph-${cacheKey}`;
|
|
1007
|
+
let snapshotKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/snapshot` : `snapshot-${cacheKey}`;
|
|
952
1008
|
if (this.options.shouldDisableCache) {
|
|
953
1009
|
return;
|
|
954
1010
|
}
|
|
955
1011
|
let total = 0;
|
|
956
|
-
(
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
size: this.graph.nodes.length
|
|
961
|
-
});
|
|
962
|
-
let serialisedGraph = this.graph.serialize();
|
|
963
|
-
|
|
964
|
-
// Delete an existing request graph cache, to prevent invalid states
|
|
965
|
-
await this.options.cache.deleteLargeBlob(requestGraphKey);
|
|
966
|
-
const serialiseAndSet = async (key, contents) => {
|
|
967
|
-
if (signal !== null && signal !== void 0 && signal.aborted) {
|
|
968
|
-
throw new Error('Serialization was aborted');
|
|
969
|
-
}
|
|
970
|
-
await runCacheImprovements(cache => {
|
|
971
|
-
(0, _logger().instrument)(`cache.put(${key})`, () => {
|
|
972
|
-
cache.getNativeRef().putNoConfirm(key, (0, _buildCache().serialize)(contents));
|
|
973
|
-
});
|
|
974
|
-
return Promise.resolve();
|
|
975
|
-
}, async () => {
|
|
976
|
-
await this.options.cache.setLargeBlob(key, (0, _buildCache().serialize)(contents), signal ? {
|
|
977
|
-
signal: signal
|
|
978
|
-
} : undefined);
|
|
979
|
-
});
|
|
980
|
-
total += 1;
|
|
1012
|
+
await runCacheImprovements(async cache => {
|
|
1013
|
+
await cache.getNativeRef().startWriteTransaction();
|
|
1014
|
+
}, () => Promise.resolve());
|
|
1015
|
+
try {
|
|
981
1016
|
(0, _ReporterRunner.report)({
|
|
982
1017
|
type: 'cache',
|
|
983
|
-
phase: '
|
|
1018
|
+
phase: 'start',
|
|
984
1019
|
total,
|
|
985
1020
|
size: this.graph.nodes.length
|
|
986
1021
|
});
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1022
|
+
if ((0, _featureFlags().getFeatureFlag)('environmentDeduplication')) {
|
|
1023
|
+
await (0, _EnvironmentManager.writeEnvironmentsToCache)(options.cache);
|
|
1024
|
+
}
|
|
1025
|
+
let serialisedGraph = this.graph.serialize();
|
|
991
1026
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1027
|
+
// Delete an existing request graph cache, to prevent invalid states
|
|
1028
|
+
await this.options.cache.deleteLargeBlob(requestGraphKey);
|
|
1029
|
+
const serialiseAndSet = async (key, contents) => {
|
|
1030
|
+
if (signal !== null && signal !== void 0 && signal.aborted) {
|
|
1031
|
+
throw new Error('Serialization was aborted');
|
|
1032
|
+
}
|
|
1033
|
+
await runCacheImprovements(cache => {
|
|
1034
|
+
(0, _logger().instrument)(`RequestTracker::writeToCache::cache.put(${key})`, () => {
|
|
1035
|
+
cache.getNativeRef().putNoConfirm(key, (0, _buildCache().serialize)(contents));
|
|
1036
|
+
});
|
|
1037
|
+
return Promise.resolve();
|
|
1038
|
+
}, async () => {
|
|
1039
|
+
await this.options.cache.setLargeBlob(key, (0, _buildCache().serialize)(contents), signal ? {
|
|
1040
|
+
signal: signal
|
|
1041
|
+
} : undefined);
|
|
1042
|
+
});
|
|
1043
|
+
total += 1;
|
|
1044
|
+
(0, _ReporterRunner.report)({
|
|
1045
|
+
type: 'cache',
|
|
1046
|
+
phase: 'write',
|
|
1047
|
+
total,
|
|
1048
|
+
size: this.graph.nodes.length
|
|
1049
|
+
});
|
|
1050
|
+
};
|
|
1051
|
+
let queue = new (_utils().PromiseQueue)({
|
|
1052
|
+
maxConcurrent: 32
|
|
1053
|
+
});
|
|
999
1054
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1055
|
+
// Preallocating a sparse array is faster than pushing when N is high enough
|
|
1056
|
+
let cacheableNodes = new Array(serialisedGraph.nodes.length);
|
|
1057
|
+
for (let i = 0; i < serialisedGraph.nodes.length; i += 1) {
|
|
1058
|
+
let node = serialisedGraph.nodes[i];
|
|
1059
|
+
let resultCacheKey = node === null || node === void 0 ? void 0 : node.resultCacheKey;
|
|
1060
|
+
if ((node === null || node === void 0 ? void 0 : node.type) === REQUEST && resultCacheKey != null && (node === null || node === void 0 ? void 0 : node.result) != null) {
|
|
1061
|
+
queue.add(() => serialiseAndSet(resultCacheKey, node.result));
|
|
1062
|
+
|
|
1063
|
+
// eslint-disable-next-line no-unused-vars
|
|
1064
|
+
let {
|
|
1065
|
+
result: _,
|
|
1066
|
+
...newNode
|
|
1067
|
+
} = node;
|
|
1068
|
+
cacheableNodes[i] = newNode;
|
|
1069
|
+
} else {
|
|
1070
|
+
cacheableNodes[i] = node;
|
|
1071
|
+
}
|
|
1008
1072
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1073
|
+
let nodeCountsPerBlob = [];
|
|
1074
|
+
for (let i = 0; i * this.graph.nodesPerBlob < cacheableNodes.length; i += 1) {
|
|
1075
|
+
let nodesStartIndex = i * this.graph.nodesPerBlob;
|
|
1076
|
+
let nodesEndIndex = Math.min((i + 1) * this.graph.nodesPerBlob, cacheableNodes.length);
|
|
1077
|
+
nodeCountsPerBlob.push(nodesEndIndex - nodesStartIndex);
|
|
1078
|
+
if (!this.graph.hasCachedRequestChunk(i)) {
|
|
1079
|
+
// We assume the request graph nodes are immutable and won't change
|
|
1080
|
+
let nodesToCache = cacheableNodes.slice(nodesStartIndex, nodesEndIndex);
|
|
1081
|
+
queue.add(() => serialiseAndSet(getRequestGraphNodeKey(i, cacheKey), nodesToCache).then(() => {
|
|
1082
|
+
// Succeeded in writing to disk, save that we have completed this chunk
|
|
1083
|
+
this.graph.setCachedRequestChunk(i);
|
|
1084
|
+
}));
|
|
1085
|
+
}
|
|
1022
1086
|
}
|
|
1023
|
-
}
|
|
1024
|
-
try {
|
|
1025
1087
|
await queue.run();
|
|
1026
1088
|
|
|
1027
1089
|
// Set the request graph after the queue is flushed to avoid writing an invalid state
|
|
@@ -1030,7 +1092,7 @@ class RequestTracker {
|
|
|
1030
1092
|
nodeCountsPerBlob,
|
|
1031
1093
|
nodes: undefined
|
|
1032
1094
|
});
|
|
1033
|
-
await runCacheImprovements(() => serialiseAndSet(
|
|
1095
|
+
await runCacheImprovements(() => serialiseAndSet(`${cacheKey}/cache_metadata`, {
|
|
1034
1096
|
version: _constants.ATLASPACK_VERSION,
|
|
1035
1097
|
entries: this.options.entries,
|
|
1036
1098
|
mode: this.options.mode,
|
|
@@ -1038,15 +1100,16 @@ class RequestTracker {
|
|
|
1038
1100
|
watchBackend: this.options.watchBackend
|
|
1039
1101
|
}), () => Promise.resolve());
|
|
1040
1102
|
let opts = getWatcherOptions(this.options);
|
|
1041
|
-
let snapshotPath = _path2().default.join(this.options.cacheDir,
|
|
1103
|
+
let snapshotPath = _path2().default.join(this.options.cacheDir, snapshotKey + '.txt');
|
|
1042
1104
|
await this.options.outputFS.writeSnapshot(this.options.watchDir, snapshotPath, opts);
|
|
1043
1105
|
} catch (err) {
|
|
1044
1106
|
// If we have aborted, ignore the error and continue
|
|
1045
1107
|
if (!(signal !== null && signal !== void 0 && signal.aborted)) throw err;
|
|
1108
|
+
} finally {
|
|
1109
|
+
await runCacheImprovements(async cache => {
|
|
1110
|
+
await cache.getNativeRef().commitWriteTransaction();
|
|
1111
|
+
}, () => Promise.resolve());
|
|
1046
1112
|
}
|
|
1047
|
-
await runCacheImprovements(async cache => {
|
|
1048
|
-
await cache.getNativeRef().commitWriteTransaction();
|
|
1049
|
-
}, () => Promise.resolve());
|
|
1050
1113
|
(0, _ReporterRunner.report)({
|
|
1051
1114
|
type: 'cache',
|
|
1052
1115
|
phase: 'end',
|
|
@@ -1075,7 +1138,8 @@ function getWatcherOptions({
|
|
|
1075
1138
|
watchDir,
|
|
1076
1139
|
watchBackend
|
|
1077
1140
|
}) {
|
|
1078
|
-
const
|
|
1141
|
+
const vcsDirs = ['.git', '.hg'];
|
|
1142
|
+
const uniqueDirs = [...new Set([...watchIgnore, ...vcsDirs, cacheDir])];
|
|
1079
1143
|
const ignore = uniqueDirs.map(dir => _path2().default.resolve(watchDir, dir));
|
|
1080
1144
|
return {
|
|
1081
1145
|
ignore,
|
|
@@ -1083,17 +1147,30 @@ function getWatcherOptions({
|
|
|
1083
1147
|
};
|
|
1084
1148
|
}
|
|
1085
1149
|
function getCacheKey(options) {
|
|
1150
|
+
if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
|
1151
|
+
const hash = (0, _rust().hashString)(`${_constants.ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${options.watchBackend ?? ''}`);
|
|
1152
|
+
return `RequestTracker/${_constants.ATLASPACK_VERSION}/${hash}`;
|
|
1153
|
+
}
|
|
1086
1154
|
return (0, _rust().hashString)(`${_constants.ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${options.watchBackend ?? ''}`);
|
|
1087
1155
|
}
|
|
1088
1156
|
function getRequestGraphNodeKey(index, cacheKey) {
|
|
1157
|
+
if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
|
1158
|
+
return `${cacheKey}/RequestGraph/nodes/${index}`;
|
|
1159
|
+
}
|
|
1089
1160
|
return `requestGraph-nodes-${index}-${cacheKey}`;
|
|
1090
1161
|
}
|
|
1091
1162
|
async function readAndDeserializeRequestGraph(cache, requestGraphKey, cacheKey) {
|
|
1092
1163
|
let bufferLength = 0;
|
|
1093
1164
|
const getAndDeserialize = async key => {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1165
|
+
if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
|
|
1166
|
+
const buffer = await cache.getBlob(key);
|
|
1167
|
+
bufferLength += Buffer.byteLength(buffer);
|
|
1168
|
+
return (0, _buildCache().deserialize)(buffer);
|
|
1169
|
+
} else {
|
|
1170
|
+
const buffer = await cache.getLargeBlob(key);
|
|
1171
|
+
bufferLength += Buffer.byteLength(buffer);
|
|
1172
|
+
return (0, _buildCache().deserialize)(buffer);
|
|
1173
|
+
}
|
|
1097
1174
|
};
|
|
1098
1175
|
let serializedRequestGraph = await getAndDeserialize(requestGraphKey);
|
|
1099
1176
|
let nodePromises = serializedRequestGraph.nodeCountsPerBlob.map(async (nodesCount, i) => {
|
|
@@ -1115,19 +1192,33 @@ async function loadRequestGraph(options) {
|
|
|
1115
1192
|
return new RequestGraph();
|
|
1116
1193
|
}
|
|
1117
1194
|
let cacheKey = getCacheKey(options);
|
|
1118
|
-
let requestGraphKey = `requestGraph-${cacheKey}`;
|
|
1195
|
+
let requestGraphKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/RequestGraph` : `requestGraph-${cacheKey}`;
|
|
1119
1196
|
let timeout;
|
|
1120
|
-
const snapshotKey = `snapshot-${cacheKey}`;
|
|
1197
|
+
const snapshotKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/snapshot` : `snapshot-${cacheKey}`;
|
|
1121
1198
|
const snapshotPath = _path2().default.join(options.cacheDir, snapshotKey + '.txt');
|
|
1199
|
+
const commonMeta = {
|
|
1200
|
+
cacheKey,
|
|
1201
|
+
snapshotKey,
|
|
1202
|
+
cacheKeyOptions: {
|
|
1203
|
+
version: _constants.ATLASPACK_VERSION,
|
|
1204
|
+
entries: options.entries,
|
|
1205
|
+
mode: options.mode,
|
|
1206
|
+
shouldBuildLazily: options.shouldBuildLazily,
|
|
1207
|
+
watchBackend: options.watchBackend
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1122
1210
|
_logger().default.verbose({
|
|
1123
1211
|
origin: '@atlaspack/core',
|
|
1124
1212
|
message: 'Loading request graph',
|
|
1125
1213
|
meta: {
|
|
1126
|
-
|
|
1127
|
-
snapshotKey
|
|
1214
|
+
...commonMeta
|
|
1128
1215
|
}
|
|
1129
1216
|
});
|
|
1130
|
-
if (
|
|
1217
|
+
if ((0, _featureFlags().getFeatureFlag)('environmentDeduplication')) {
|
|
1218
|
+
await (0, _EnvironmentManager.loadEnvironmentsFromCache)(options.cache);
|
|
1219
|
+
}
|
|
1220
|
+
const hasRequestGraphInCache = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? await options.cache.has(requestGraphKey) : await options.cache.hasLargeBlob(requestGraphKey);
|
|
1221
|
+
if (hasRequestGraphInCache) {
|
|
1131
1222
|
try {
|
|
1132
1223
|
let {
|
|
1133
1224
|
requestGraph
|
|
@@ -1146,16 +1237,29 @@ async function loadRequestGraph(options) {
|
|
|
1146
1237
|
origin: '@atlaspack/core',
|
|
1147
1238
|
message: `File system event count: ${events.length}`,
|
|
1148
1239
|
meta: {
|
|
1240
|
+
...commonMeta,
|
|
1149
1241
|
trackableEvent: 'watcher_events_count',
|
|
1150
1242
|
watcherEventCount: events.length,
|
|
1151
1243
|
duration: Date.now() - startTime
|
|
1152
1244
|
}
|
|
1153
1245
|
});
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1246
|
+
if ((0, _featureFlags().getFeatureFlag)('verboseRequestInvalidationStats')) {
|
|
1247
|
+
const invalidationStats = await invalidateRequestGraph(requestGraph, options, events);
|
|
1248
|
+
_logger().default.verbose({
|
|
1249
|
+
origin: '@atlaspack/core',
|
|
1250
|
+
message: 'Request track loaded from cache',
|
|
1251
|
+
meta: {
|
|
1252
|
+
...commonMeta,
|
|
1253
|
+
trackableEvent: 'request_tracker_cache_key_hit',
|
|
1254
|
+
invalidationStats
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
} else {
|
|
1258
|
+
requestGraph.invalidateUnpredictableNodes();
|
|
1259
|
+
requestGraph.invalidateOnBuildNodes();
|
|
1260
|
+
requestGraph.invalidateEnvNodes(options.env);
|
|
1261
|
+
requestGraph.invalidateOptionNodes(options);
|
|
1262
|
+
}
|
|
1159
1263
|
return requestGraph;
|
|
1160
1264
|
} catch (e) {
|
|
1161
1265
|
// Prevent logging fs events took too long warning
|
|
@@ -1170,12 +1274,106 @@ async function loadRequestGraph(options) {
|
|
|
1170
1274
|
origin: '@atlaspack/core',
|
|
1171
1275
|
message: 'Cache entry for request tracker was not found, initializing a clean cache.',
|
|
1172
1276
|
meta: {
|
|
1173
|
-
|
|
1174
|
-
|
|
1277
|
+
...commonMeta,
|
|
1278
|
+
trackableEvent: 'request_tracker_cache_key_miss'
|
|
1175
1279
|
}
|
|
1176
1280
|
});
|
|
1177
1281
|
return new RequestGraph();
|
|
1178
1282
|
}
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* A wrapper around an invalidation type / method
|
|
1286
|
+
*/
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* Details about an invalidation.
|
|
1290
|
+
*
|
|
1291
|
+
* If this is a fs events invalidation, this key will contain statistics about invalidations
|
|
1292
|
+
* by path.
|
|
1293
|
+
*
|
|
1294
|
+
* If this is a env or option invalidation, this key will contain the list of changed environment
|
|
1295
|
+
* variables or options.
|
|
1296
|
+
*/
|
|
1297
|
+
|
|
1298
|
+
/**
|
|
1299
|
+
* Number of invalidations for a given file-system event.
|
|
1300
|
+
*/
|
|
1301
|
+
|
|
1302
|
+
/**
|
|
1303
|
+
* Information about a certain cache invalidation type.
|
|
1304
|
+
*/
|
|
1305
|
+
|
|
1306
|
+
/**
|
|
1307
|
+
* Respond to unpredictable, build, environment changes, option changes and file-system events
|
|
1308
|
+
* invalidating RequestGraph nodes.
|
|
1309
|
+
*
|
|
1310
|
+
* Returns the count of nodes invalidated by each invalidation type.
|
|
1311
|
+
*/
|
|
1312
|
+
async function invalidateRequestGraph(requestGraph, options, events) {
|
|
1313
|
+
const invalidationFns = [{
|
|
1314
|
+
key: 'unpredictable',
|
|
1315
|
+
fn: () => requestGraph.invalidateUnpredictableNodes()
|
|
1316
|
+
}, {
|
|
1317
|
+
key: 'onBuild',
|
|
1318
|
+
fn: () => requestGraph.invalidateOnBuildNodes()
|
|
1319
|
+
}, {
|
|
1320
|
+
key: 'env',
|
|
1321
|
+
fn: () => requestGraph.invalidateEnvNodes(options.env)
|
|
1322
|
+
}, {
|
|
1323
|
+
key: 'option',
|
|
1324
|
+
fn: () => requestGraph.invalidateOptionNodes(options)
|
|
1325
|
+
}, {
|
|
1326
|
+
key: 'fsEvents',
|
|
1327
|
+
fn: () => invalidateRequestGraphFSEvents(requestGraph, options, events)
|
|
1328
|
+
}];
|
|
1329
|
+
const invalidations = [];
|
|
1330
|
+
for (const invalidation of invalidationFns) {
|
|
1331
|
+
invalidations.push(await runInvalidation(requestGraph, invalidation));
|
|
1332
|
+
}
|
|
1333
|
+
const invalidatedCount = invalidations.reduce((acc, invalidation) => acc + invalidation.count, 0);
|
|
1334
|
+
const requestCount = requestGraph.nodes.reduce((acc, node) => acc + ((node === null || node === void 0 ? void 0 : node.type) === REQUEST ? 1 : 0), 0);
|
|
1335
|
+
const nodeCount = requestGraph.nodes.length;
|
|
1336
|
+
const nodeInvalidationRatio = invalidatedCount / nodeCount;
|
|
1337
|
+
const requestInvalidationRatio = invalidatedCount / requestCount;
|
|
1338
|
+
return {
|
|
1339
|
+
invalidations,
|
|
1340
|
+
nodeCount,
|
|
1341
|
+
requestCount,
|
|
1342
|
+
invalidatedCount,
|
|
1343
|
+
nodeInvalidationRatio,
|
|
1344
|
+
requestInvalidationRatio
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Invalidate the request graph based on file-system events.
|
|
1349
|
+
*
|
|
1350
|
+
* Returns statistics about the invalidations.
|
|
1351
|
+
*/
|
|
1352
|
+
async function invalidateRequestGraphFSEvents(requestGraph, options, events) {
|
|
1353
|
+
const {
|
|
1354
|
+
invalidationsByPath
|
|
1355
|
+
} = await requestGraph.respondToFSEvents(options.unstableFileInvalidations || events, options, 10000, true);
|
|
1356
|
+
const biggestInvalidations = getBiggestFSEventsInvalidations(invalidationsByPath);
|
|
1357
|
+
return {
|
|
1358
|
+
biggestInvalidations
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Runs an invalidation function and reports metrics.
|
|
1363
|
+
*/
|
|
1364
|
+
async function runInvalidation(requestGraph, invalidationFn) {
|
|
1365
|
+
const start = _perf_hooks().performance.now();
|
|
1366
|
+
const startInvalidationCount = requestGraph.getInvalidNodeCount();
|
|
1367
|
+
const result = await invalidationFn.fn();
|
|
1368
|
+
const count = requestGraph.getInvalidNodeCount() - startInvalidationCount;
|
|
1369
|
+
const duration = _perf_hooks().performance.now() - start;
|
|
1370
|
+
return {
|
|
1371
|
+
key: invalidationFn.key,
|
|
1372
|
+
count,
|
|
1373
|
+
detail: result ?? null,
|
|
1374
|
+
duration
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1179
1377
|
function logErrorOnBailout(options, snapshotPath, e) {
|
|
1180
1378
|
if (e.message && e.message.includes('invalid clockspec')) {
|
|
1181
1379
|
const snapshotContents = options.inputFS.readFileSync(snapshotPath, 'utf-8');
|
|
@@ -1215,4 +1413,19 @@ function cleanUpOrphans(graph) {
|
|
|
1215
1413
|
}
|
|
1216
1414
|
});
|
|
1217
1415
|
return removedNodeIds;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/**
|
|
1419
|
+
* Returns paths that invalidated the most nodes
|
|
1420
|
+
*/
|
|
1421
|
+
function getBiggestFSEventsInvalidations(invalidationsByPath, limit = 10) {
|
|
1422
|
+
const invalidations = [];
|
|
1423
|
+
for (const [path, count] of invalidationsByPath) {
|
|
1424
|
+
invalidations.push({
|
|
1425
|
+
path,
|
|
1426
|
+
count
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
invalidations.sort((a, b) => b.count - a.count);
|
|
1430
|
+
return invalidations.slice(0, limit);
|
|
1218
1431
|
}
|