Skip to content

Commit 53cefcd

Browse files
authored
Add BrowserFileLoader (#242)
Similar to ShellFileLoader introduce a BrowserFileLoader as mid-term solution to clean up the file loading code. - Move common helpers and caches from the driver to the BrowserFileLoader. - Pass along a "counter" param where easily possible
1 parent a3f5c45 commit 53cefcd

File tree

1 file changed

+150
-135
lines changed

1 file changed

+150
-135
lines changed

JetStreamDriver.js

Lines changed: 150 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,137 @@ class ShellFileLoader {
207207
}
208208
};
209209

210+
211+
class BrowserFileLoader {
212+
213+
constructor() {
214+
// TODO: Cleanup / remove / merge `blobDataCache` and `loadCache` vs.
215+
// the global `fileLoader` cache.
216+
this.blobDataCache = { __proto__ : null };
217+
this.loadCache = { __proto__ : null };
218+
}
219+
220+
async doLoadBlob(resource) {
221+
const blobData = this.blobDataCache[resource];
222+
223+
const compressed = isCompressed(resource);
224+
if (compressed && !JetStreamParams.prefetchResources) {
225+
resource = uncompressedName(resource);
226+
}
227+
228+
// If we aren't supposed to prefetch this then set the blobURL to just
229+
// be the resource URL.
230+
if (!JetStreamParams.prefetchResources) {
231+
blobData.blobURL = resource;
232+
return blobData;
233+
}
234+
235+
let response;
236+
let tries = 3;
237+
while (tries--) {
238+
let hasError = false;
239+
try {
240+
response = await fetch(resource, { cache: "no-store" });
241+
} catch (e) {
242+
hasError = true;
243+
}
244+
if (!hasError && response.ok)
245+
break;
246+
if (tries)
247+
continue;
248+
throw new Error("Fetch failed");
249+
}
250+
251+
// If we need to decompress this, then run it through a decompression
252+
// stream.
253+
if (compressed) {
254+
const stream = response.body.pipeThrough(new DecompressionStream("deflate"))
255+
response = new Response(stream);
256+
}
257+
258+
let blob = await response.blob();
259+
blobData.blob = blob;
260+
blobData.blobURL = URL.createObjectURL(blob);
261+
return blobData;
262+
}
263+
264+
async loadBlob(type, prop, resource, incrementRefCount = true) {
265+
let blobData = this.blobDataCache[resource];
266+
if (!blobData) {
267+
blobData = {
268+
type: type,
269+
prop: prop,
270+
resource: resource,
271+
blob: null,
272+
blobURL: null,
273+
refCount: 0
274+
};
275+
this.blobDataCache[resource] = blobData;
276+
}
277+
278+
if (incrementRefCount)
279+
blobData.refCount++;
280+
281+
let promise = this.loadCache[resource];
282+
if (promise)
283+
return promise;
284+
285+
promise = this.doLoadBlob(resource);
286+
this.loadCache[resource] = promise;
287+
return promise;
288+
}
289+
290+
async retryPrefetchResource(type, prop, file) {
291+
console.assert(isInBrowser);
292+
293+
const counter = JetStream.counter;
294+
const blobData = this.blobDataCache[file];
295+
if (blobData.blob) {
296+
// The same preload blob may be used by multiple subtests. Though the blob is already loaded,
297+
// we still need to check if this subtest failed to load it before. If so, handle accordingly.
298+
if (type == "preload") {
299+
if (this.failedPreloads && this.failedPreloads[blobData.prop]) {
300+
this.failedPreloads[blobData.prop] = false;
301+
this.preloads.push({ name: blobData.prop, resource: blobData.resource, blobURLOrPath: blobData.blobURL });
302+
counter.failedPreloadResources--;
303+
}
304+
}
305+
return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources;
306+
}
307+
308+
// Retry fetching the resource.
309+
this.loadCache[file] = null;
310+
await this.loadBlob(type, prop, file, false).then((blobData) => {
311+
if (!globalThis.allIsGood)
312+
return;
313+
if (blobData.type == "preload")
314+
this.preloads.push({ name: blobData.prop, resource: blobData.resource, blobURLOrPath: blobData.blobURL });
315+
this.updateCounter();
316+
});
317+
318+
if (!blobData.blob) {
319+
globalThis.allIsGood = false;
320+
throw new Error("Fetch failed");
321+
}
322+
323+
return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources;
324+
}
325+
326+
free(files) {
327+
for (const file of files) {
328+
const blobData = this.blobDataCache[file];
329+
// If we didn't prefetch this resource, then no need to free it
330+
if (!blobData.blob) {
331+
continue
332+
}
333+
blobData.refCount--;
334+
if (!blobData.refCount)
335+
this.blobDataCache[file] = undefined;
336+
}
337+
}
338+
}
339+
340+
const browserFileLoader = new BrowserFileLoader();
210341
const shellFileLoader = new ShellFileLoader();
211342

212343
class Driver {
@@ -218,10 +349,6 @@ class Driver {
218349
this.benchmarks = Array.from(new Set(benchmarks));
219350
this.benchmarks.sort((a, b) => a.plan.name.toLowerCase() < b.plan.name.toLowerCase() ? 1 : -1);
220351
console.assert(this.benchmarks.length, "No benchmarks selected");
221-
// TODO: Cleanup / remove / merge `blobDataCache` and `loadCache` vs.
222-
// the global `fileLoader` cache.
223-
this.blobDataCache = { };
224-
this.loadCache = { };
225352
this.counter = { };
226353
this.counter.loadedResources = 0;
227354
this.counter.totalResources = 0;
@@ -254,17 +381,7 @@ class Driver {
254381
benchmark.updateUIAfterRun();
255382

256383
if (isInBrowser) {
257-
const cache = JetStream.blobDataCache;
258-
for (const file of benchmark.files) {
259-
const blobData = cache[file];
260-
// If we didn't prefetch this resource, then no need to free it
261-
if (!blobData.blob) {
262-
continue
263-
}
264-
blobData.refCount--;
265-
if (!blobData.refCount)
266-
cache[file] = undefined;
267-
}
384+
browserFileLoader.free(benchmark.files);
268385
}
269386
}
270387
performance.measure("runner update-ui", "update-ui-start");
@@ -428,12 +545,12 @@ class Driver {
428545

429546
// TODO: Cleanup the browser path of the preloading below and in
430547
// `prefetchResourcesForBrowser` / `retryPrefetchResourcesForBrowser`.
548+
const counter = JetStream.counter;
431549
const promises = [];
432550
for (const benchmark of this.benchmarks)
433-
promises.push(benchmark.prefetchResourcesForBrowser());
551+
promises.push(benchmark.prefetchResourcesForBrowser(counter));
434552
await Promise.all(promises);
435553

436-
const counter = JetStream.counter;
437554
if (counter.failedPreloadResources || counter.loadedResources != counter.totalResources) {
438555
for (const benchmark of this.benchmarks) {
439556
const allFilesLoaded = await benchmark.retryPrefetchResourcesForBrowser(counter);
@@ -749,6 +866,7 @@ class BrowserScripts extends Scripts {
749866
}
750867
}
751868

869+
752870
class Benchmark {
753871
constructor(plan)
754872
{
@@ -946,7 +1064,7 @@ class Benchmark {
9461064
for (const text of this.scripts)
9471065
scripts.add(text);
9481066
} else {
949-
const cache = JetStream.blobDataCache;
1067+
const cache = browserFileLoader.blobDataCache;
9501068
for (const file of this.plan.files) {
9511069
scripts.addWithURL(cache[file].blobURL);
9521070
}
@@ -997,75 +1115,6 @@ class Benchmark {
9971115
Realm.dispose(magicFrame);
9981116
}
9991117

1000-
async doLoadBlob(resource) {
1001-
const blobData = JetStream.blobDataCache[resource];
1002-
1003-
const compressed = isCompressed(resource);
1004-
if (compressed && !JetStreamParams.prefetchResources) {
1005-
resource = uncompressedName(resource);
1006-
}
1007-
1008-
// If we aren't supposed to prefetch this then set the blobURL to just
1009-
// be the resource URL.
1010-
if (!JetStreamParams.prefetchResources) {
1011-
blobData.blobURL = resource;
1012-
return blobData;
1013-
}
1014-
1015-
let response;
1016-
let tries = 3;
1017-
while (tries--) {
1018-
let hasError = false;
1019-
try {
1020-
response = await fetch(resource, { cache: "no-store" });
1021-
} catch (e) {
1022-
hasError = true;
1023-
}
1024-
if (!hasError && response.ok)
1025-
break;
1026-
if (tries)
1027-
continue;
1028-
throw new Error("Fetch failed");
1029-
}
1030-
1031-
// If we need to decompress this, then run it through a decompression
1032-
// stream.
1033-
if (compressed) {
1034-
const stream = response.body.pipeThrough(new DecompressionStream("deflate"))
1035-
response = new Response(stream);
1036-
}
1037-
1038-
let blob = await response.blob();
1039-
blobData.blob = blob;
1040-
blobData.blobURL = URL.createObjectURL(blob);
1041-
return blobData;
1042-
}
1043-
1044-
async loadBlob(type, prop, resource, incrementRefCount = true) {
1045-
let blobData = JetStream.blobDataCache[resource];
1046-
if (!blobData) {
1047-
blobData = {
1048-
type: type,
1049-
prop: prop,
1050-
resource: resource,
1051-
blob: null,
1052-
blobURL: null,
1053-
refCount: 0
1054-
};
1055-
JetStream.blobDataCache[resource] = blobData;
1056-
}
1057-
1058-
if (incrementRefCount)
1059-
blobData.refCount++;
1060-
1061-
let promise = JetStream.loadCache[resource];
1062-
if (promise)
1063-
return promise;
1064-
1065-
promise = this.doLoadBlob(resource);
1066-
JetStream.loadCache[resource] = promise;
1067-
return promise;
1068-
}
10691118

10701119
updateCounter() {
10711120
const counter = JetStream.counter;
@@ -1074,10 +1123,10 @@ class Benchmark {
10741123
statusElement.innerHTML = `Loading ${counter.loadedResources} of ${counter.totalResources} ...`;
10751124
}
10761125

1077-
prefetchResourcesForBrowser() {
1126+
prefetchResourcesForBrowser(counter) {
10781127
console.assert(isInBrowser);
10791128

1080-
const promises = this.plan.files.map((file) => this.loadBlob("file", null, file).then((blobData) => {
1129+
const promises = this.plan.files.map((file) => browserFileLoader.loadBlob("file", null, file).then((blobData) => {
10811130
if (!globalThis.allIsGood)
10821131
return;
10831132
this.updateCounter();
@@ -1087,7 +1136,7 @@ class Benchmark {
10871136

10881137
if (this.plan.preload) {
10891138
for (const [name, resource] of Object.entries(this.plan.preload)) {
1090-
promises.push(this.loadBlob("preload", name, resource).then((blobData) => {
1139+
promises.push(browserFileLoader.loadBlob("preload", name, resource).then((blobData) => {
10911140
if (!globalThis.allIsGood)
10921141
return;
10931142
this.preloads.push({ name: blobData.prop, resource: blobData.resource, blobURLOrPath: blobData.blobURL });
@@ -1097,7 +1146,7 @@ class Benchmark {
10971146
if (!this.failedPreloads)
10981147
this.failedPreloads = { };
10991148
this.failedPreloads[name] = true;
1100-
JetStream.counter.failedPreloadResources++;
1149+
counter.failedPreloadResources++;
11011150
}));
11021151
}
11031152
}
@@ -1106,55 +1155,20 @@ class Benchmark {
11061155
return Promise.all(promises);
11071156
}
11081157

1109-
async retryPrefetchResource(type, prop, file) {
1110-
console.assert(isInBrowser);
1111-
1112-
const counter = JetStream.counter;
1113-
const blobData = JetStream.blobDataCache[file];
1114-
if (blobData.blob) {
1115-
// The same preload blob may be used by multiple subtests. Though the blob is already loaded,
1116-
// we still need to check if this subtest failed to load it before. If so, handle accordingly.
1117-
if (type == "preload") {
1118-
if (this.failedPreloads && this.failedPreloads[blobData.prop]) {
1119-
this.failedPreloads[blobData.prop] = false;
1120-
this.preloads.push({ name: blobData.prop, resource: blobData.resource, blobURLOrPath: blobData.blobURL });
1121-
counter.failedPreloadResources--;
1122-
}
1123-
}
1124-
return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources;
1125-
}
1126-
1127-
// Retry fetching the resource.
1128-
JetStream.loadCache[file] = null;
1129-
await this.loadBlob(type, prop, file, false).then((blobData) => {
1130-
if (!globalThis.allIsGood)
1131-
return;
1132-
if (blobData.type == "preload")
1133-
this.preloads.push({ name: blobData.prop, resource: blobData.resource, blobURLOrPath: blobData.blobURL });
1134-
this.updateCounter();
1135-
});
1136-
1137-
if (!blobData.blob) {
1138-
globalThis.allIsGood = false;
1139-
throw new Error("Fetch failed");
1140-
}
1141-
1142-
return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources;
1143-
}
1144-
1145-
async retryPrefetchResourcesForBrowser() {
1158+
async retryPrefetchResourcesForBrowser(counter) {
1159+
// FIXME: Move to BrowserFileLoader.
11461160
console.assert(isInBrowser);
11471161

1148-
const counter = JetStream.counter;
11491162
for (const resource of this.plan.files) {
1150-
const allDone = await this.retryPrefetchResource("file", null, resource);
1163+
const allDone = await browserFileLoader.retryPrefetchResource("file", null, resource);
1164+
11511165
if (allDone)
11521166
return true; // All resources loaded, nothing more to do.
11531167
}
11541168

11551169
if (this.plan.preload) {
11561170
for (const [name, resource] of Object.entries(this.plan.preload)) {
1157-
const allDone = await this.retryPrefetchResource("preload", name, resource);
1171+
const allDone = await browserFileLoader.retryPrefetchResource("preload", name, resource);
11581172
if (allDone)
11591173
return true; // All resources loaded, nothing more to do.
11601174
}
@@ -1163,6 +1177,7 @@ class Benchmark {
11631177
}
11641178

11651179
prefetchResourcesForShell() {
1180+
// FIXME: move to ShellFileLoader.
11661181
console.assert(!isInBrowser);
11671182

11681183
console.assert(this.scripts === null, "This initialization should be called only once.");
@@ -1316,14 +1331,14 @@ class GroupedBenchmark extends Benchmark {
13161331
this.benchmarks = benchmarks;
13171332
}
13181333

1319-
async prefetchResourcesForBrowser() {
1334+
async prefetchResourcesForBrowser(counter) {
13201335
for (const benchmark of this.benchmarks)
1321-
await benchmark.prefetchResourcesForBrowser();
1336+
await benchmark.prefetchResourcesForBrowser(counter);
13221337
}
13231338

1324-
async retryPrefetchResourcesForBrowser() {
1339+
async retryPrefetchResourcesForBrowser(counter) {
13251340
for (const benchmark of this.benchmarks)
1326-
await benchmark.retryPrefetchResourcesForBrowser();
1341+
await benchmark.retryPrefetchResourcesForBrowser(counter);
13271342
}
13281343

13291344
prefetchResourcesForShell() {

0 commit comments

Comments
 (0)