Skip to content

Commit 9c6557e

Browse files
committed
Change the way the MultiItemCache iterates over items to prevent stack exhaustion when it has a large number of items.
Added unit tests for MultiItemCache behavior, including one for covering the large item count case.
1 parent ccecc17 commit 9c6557e

File tree

2 files changed

+80
-7
lines changed

2 files changed

+80
-7
lines changed

lib/CacheFacade.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,23 @@ class MultiItemCache {
4646
* @returns {void}
4747
*/
4848
get(callback) {
49-
const next = i => {
50-
this._items[i].get((err, result) => {
49+
let i = 0;
50+
let resultFound = false;
51+
const generateGetCallback = (/** @type {Boolean} */ lastItem) => {
52+
return (
53+
/** @type {import("./WebpackError")} */ err,
54+
/** @type {T} */ res
55+
) => {
56+
if (resultFound) return;
57+
resultFound = !!(err || res || false);
5158
if (err) return callback(err);
52-
if (result !== undefined) return callback(null, result);
53-
if (++i >= this._items.length) return callback();
54-
next(i);
55-
});
59+
if (res !== undefined) return callback(null, res);
60+
if (lastItem) return callback();
61+
};
5662
};
57-
next(0);
63+
do {
64+
this._items[i].get(generateGetCallback(i === this._items.length - 1));
65+
} while (++i < this._items.length);
5866
}
5967

6068
/**

test/MultiItemCache.unittest.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"use strict";
2+
3+
const Cache = require("../lib/Cache");
4+
const { ItemCacheFacade, MultiItemCache } = require("../lib/CacheFacade");
5+
6+
describe("MultiItemCache", () => {
7+
it("Throws when getting items from an empty Cache", () => {
8+
const multiItemCache = new MultiItemCache(generateItemCaches(0));
9+
expect(() => multiItemCache.get(_ => _())).toThrowError();
10+
});
11+
12+
it("Returns the single ItemCacheFacade when passed an array of length 1", () => {
13+
const itemCaches = generateItemCaches(1);
14+
const multiItemCache = new MultiItemCache(itemCaches);
15+
expect(multiItemCache).toBe(itemCaches[0]);
16+
});
17+
18+
it("Retrieves items from the underlying Cache when get is called", () => {
19+
const itemCaches = generateItemCaches(10);
20+
const multiItemCache = new MultiItemCache(itemCaches);
21+
const callback = (err, res) => {
22+
expect(err).toBeNull();
23+
expect(res).toBeInstanceOf(Object);
24+
};
25+
for (let i = 0; i < 10; ++i) {
26+
multiItemCache.get(callback);
27+
}
28+
});
29+
30+
it("Can get() a large number of items without exhausting the stack", () => {
31+
const itemCaches = generateItemCaches(10000, () => undefined);
32+
const multiItemCache = new MultiItemCache(itemCaches);
33+
let callbacks = 0;
34+
const callback = (err, res) => {
35+
expect(err).toBeUndefined();
36+
expect(res).toBeUndefined();
37+
++callbacks;
38+
};
39+
multiItemCache.get(callback);
40+
expect(callbacks).toEqual(1);
41+
});
42+
43+
function generateItemCaches(howMany, dataGenerator) {
44+
const ret = [];
45+
for (let i = 0; i < howMany; ++i) {
46+
const name = `ItemCache${i}`;
47+
const tag = `ItemTag${i}`;
48+
const dataGen =
49+
dataGenerator ||
50+
(() => {
51+
return { name: tag };
52+
});
53+
const cache = new Cache();
54+
cache.hooks.get.tapAsync(
55+
"DataReturner",
56+
(_identifier, _etag, _gotHandlers, callback) => {
57+
callback(undefined, dataGen());
58+
}
59+
);
60+
const itemCache = new ItemCacheFacade(cache, name, tag);
61+
ret[i] = itemCache;
62+
}
63+
return ret;
64+
}
65+
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy