From e254586c517018eeb3d0adb8e67659ee9364ec01 Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Tue, 13 Jan 2026 18:32:15 +0100 Subject: [PATCH 1/2] basic-merge --- JetStreamDriver.js | 275 +++++++++++++++++++-------------------------- 1 file changed, 114 insertions(+), 161 deletions(-) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index ee50fe3e..10dcf9f5 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -189,18 +189,63 @@ class ShellFileLoader { }; -class BrowserFileLoader { +const RETRY_COUNT = 3; +const RETRY_DELAY_MS = 500; +class BrowserFileLoader { constructor() { - // TODO: Cleanup / remove / merge `blobDataCache` and `loadCache` vs. - // the global `fileLoader` cache. - this.blobDataCache = { __proto__ : null }; - this.loadCache = { __proto__ : null }; + this._blobDataCache = { __proto__ : null }; + this.counter = { + __proto__: null, + loadedResources: 0, + totalResources: 0, + } + } + + getBlobURL(file) { + const blobURL = this._blobDataCache[file].blobURL; + if (!blobURL) { + throw new Error(`Missing blob data for ${file}`); + } + return blobURL; + } + _updateCounter() { + ++this.counter.loadedResources; + JetStream.updateCounterUI(); } - async doLoadBlob(resource) { - const blobData = this.blobDataCache[resource]; + async prefetchResourcePreload(name, resource) { + const blobData = await this.prefetchResourceFile(resource); + if (!globalThis.allIsGood) + return; + return { name: name, resource: resource, blobURLOrPath: blobData.blobURL }; + } + + async prefetchResourceFile(resource) { + this.counter.totalResources++; + let blobDataOrPromise = this._blobDataCache[resource]; + if (!blobDataOrPromise) { + const newBlobData = { + resource: resource, + blob: null, + blobURL: null, + refCount: 0 + }; + blobDataOrPromise = this._loadBlob(newBlobData); + // Temporarily cache the loading promise. + this._blobDataCache[resource] = blobDataOrPromise; + } + const blobData = await blobDataOrPromise; + // Replace the potential promise in the cache. + this._blobDataCache[resource] = blobData; + blobData.refCount++; + if (globalThis.allIsGood) + this._updateCounter(); + return blobData; + } + async _loadBlob(blobData) { + let resource = blobData.resource; const compressed = isCompressed(resource); if (compressed && !JetStreamParams.prefetchResources) { resource = uncompressedName(resource); @@ -214,7 +259,7 @@ class BrowserFileLoader { } let response; - let tries = 3; + let tries = RETRY_COUNT; while (tries--) { let hasError = false; try { @@ -224,8 +269,12 @@ class BrowserFileLoader { } if (!hasError && response.ok) break; - if (tries) + if (tries) { + await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS)); + console.warning(`Request failed, retrying: ${resource}`); continue; + } + globalThis.allIsGood = false; throw new Error("Fetch failed"); } @@ -236,84 +285,24 @@ class BrowserFileLoader { response = new Response(stream); } - let blob = await response.blob(); + const blob = await response.blob(); blobData.blob = blob; blobData.blobURL = URL.createObjectURL(blob); return blobData; } - async loadBlob(type, prop, resource, incrementRefCount = true) { - let blobData = this.blobDataCache[resource]; - if (!blobData) { - blobData = { - type: type, - prop: prop, - resource: resource, - blob: null, - blobURL: null, - refCount: 0 - }; - this.blobDataCache[resource] = blobData; - } - - if (incrementRefCount) - blobData.refCount++; - - let promise = this.loadCache[resource]; - if (promise) - return promise; - - promise = this.doLoadBlob(resource); - this.loadCache[resource] = promise; - return promise; - } - - async retryPrefetchResource(type, prop, file) { - console.assert(isInBrowser); - - const counter = JetStream.counter; - const blobData = this.blobDataCache[file]; - if (blobData.blob) { - // The same preload blob may be used by multiple subtests. Though the blob is already loaded, - // we still need to check if this subtest failed to load it before. If so, handle accordingly. - if (type == "preload") { - if (this.failedPreloads && this.failedPreloads[blobData.prop]) { - this.failedPreloads[blobData.prop] = false; - this._preloadBlobData.push({ name: blobData.prop, resource: blobData.resource, blobURLOrPath: blobData.blobURL }); - counter.failedPreloadResources--; - } - } - return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources; - } - - // Retry fetching the resource. - this.loadCache[file] = null; - await this.loadBlob(type, prop, file, false).then((blobData) => { - if (!globalThis.allIsGood) - return; - if (blobData.type == "preload") - this._preloadBlobData.push({ name: blobData.prop, resource: blobData.resource, blobURLOrPath: blobData.blobURL }); - this.updateCounter(); - }); - - if (!blobData.blob) { - globalThis.allIsGood = false; - throw new Error("Fetch failed"); - } - - return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources; - } - free(files) { for (const file of files) { - const blobData = this.blobDataCache[file]; + const blobData = this._blobDataCache[file]; // If we didn't prefetch this resource, then no need to free it if (!blobData.blob) { continue } blobData.refCount--; - if (!blobData.refCount) - this.blobDataCache[file] = undefined; + if (!blobData.refCount) { + this._blobDataCache[file] = undefined; + console.log("DELETING", file); + } } } } @@ -330,10 +319,6 @@ class Driver { this.benchmarks = Array.from(new Set(benchmarks)); this.benchmarks.sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? 1 : -1); console.assert(this.benchmarks.length, "No benchmarks selected"); - this.counter = { }; - this.counter.loadedResources = 0; - this.counter.totalResources = 0; - this.counter.failedPreloadResources = 0; } async start() { @@ -360,10 +345,8 @@ class Driver { performance.mark("update-ui"); benchmark.updateUIAfterRun(); + benchmark.tearDown(); - if (isInBrowser) { - browserFileLoader.free(benchmark.files); - } } performance.measure("runner update-ui", "update-ui-start"); @@ -515,40 +498,29 @@ class Driver { } async prefetchResources() { - if (!isInBrowser) { - if (JetStreamParams.prefetchResources) { - await zlib.initialize(); - } - for (const benchmark of this.benchmarks) - benchmark.prefetchResourcesForShell(); - return; + if (isInBrowser) { + await this.prefetchResourcesForBrowser(); + } else { + await this.prefetchResourcesForShell(); } + } + + async prefetchResourcesForShell() { + if (JetStreamParams.prefetchResources) { + await zlib.initialize(); + } + for (const benchmark of this.benchmarks) + benchmark.prefetchResourcesForShell(); + } + async prefetchResourcesForBrowser() { // TODO: Cleanup the browser path of the preloading below and in - // `prefetchResourcesForBrowser` / `retryPrefetchResourcesForBrowser`. - const counter = JetStream.counter; + // `prefetchResourcesForBrowser`. const promises = []; for (const benchmark of this.benchmarks) - promises.push(benchmark.prefetchResourcesForBrowser(counter)); + promises.push(benchmark.prefetchResourcesForBrowser()); await Promise.all(promises); - if (counter.failedPreloadResources || counter.loadedResources != counter.totalResources) { - for (const benchmark of this.benchmarks) { - const allFilesLoaded = await benchmark.retryPrefetchResourcesForBrowser(counter); - if (allFilesLoaded) - break; - } - - if (counter.failedPreloadResources || counter.loadedResources != counter.totalResources) { - // If we've failed to prefetch resources even after a sequential 1 by 1 retry, - // then fail out early rather than letting subtests fail with a hang. - globalThis.allIsGood = false; - throw new Error("Fetch failed"); - } - } - - JetStream.loadCache = { }; // Done preloading all the files. - const statusElement = document.getElementById("status"); statusElement.classList.remove('loading'); statusElement.innerHTML = `Start Test`; @@ -817,6 +789,9 @@ class ShellScripts extends Scripts { } add(text) { + if (!text) { + throw new Error("Missing script source"); + } this.scripts.push(text); } @@ -849,10 +824,16 @@ class BrowserScripts extends Scripts { } add(text) { + if (!text) { + throw new Error("Missing script source"); + } this.scripts.push(``); } addWithURL(url) { + if (!url) { + throw new Error("Missing script url"); + } this.scripts.push(``); } } @@ -1098,9 +1079,8 @@ class Benchmark { for (const text of this._scripts) scripts.add(text); } else { - const cache = browserFileLoader.blobDataCache; for (const file of this.files) { - scripts.addWithURL(cache[file].blobURL); + scripts.addWithURL(browserFileLoader.getBlobURL(file)); } } @@ -1156,53 +1136,18 @@ class Benchmark { JetStream.updateCounterUI(); } - prefetchResourcesForBrowser(counter) { + async prefetchResourcesForBrowser() { console.assert(isInBrowser); - - const promises = this.files.map((file) => browserFileLoader.loadBlob("file", null, file).then((blobData) => { - if (!globalThis.allIsGood) - return; - this.updateCounter(); - }).catch((error) => { - // We'll try again later in retryPrefetchResourceForBrowser(). Don't throw an error. - })); - - for (const [name, resource] of this.preloadEntries) { - promises.push(browserFileLoader.loadBlob("preload", name, resource).then((blobData) => { - if (!globalThis.allIsGood) - return; - this._preloadBlobData.push({ name: blobData.prop, resource: blobData.resource, blobURLOrPath: blobData.blobURL }); - this.updateCounter(); - }).catch((error) => { - // We'll try again later in retryPrefetchResourceForBrowser(). Don't throw an error. - if (!this.failedPreloads) - this.failedPreloads = { }; - this.failedPreloads[name] = true; - counter.failedPreloadResources++; - })); + const promises = this.files.map((file) => browserFileLoader.prefetchResourceFile(file)); + for (const [name, resource] of Object.entries(this.plan.preload ?? {})) { + promises.push(this.prefetchResourcePreload(name, resource)); } - - JetStream.counter.totalResources += promises.length; - return Promise.all(promises); + await Promise.all(promises); } - async retryPrefetchResourcesForBrowser(counter) { - // FIXME: Move to BrowserFileLoader. - console.assert(isInBrowser); - - for (const resource of this.files) { - const allDone = await browserFileLoader.retryPrefetchResource("file", null, resource); - - if (allDone) - return true; // All resources loaded, nothing more to do. - } - - for (const [name, resource] of this.preloadEntries) { - const allDone = await browserFileLoader.retryPrefetchResource("preload", name, resource); - if (allDone) - return true; // All resources loaded, nothing more to do. - } - return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources; + async prefetchResourcePreload(name, resource) { + const preloadData = await browserFileLoader.prefetchResourcePreload(name, resource); + this.preloads.push(preloadData); } prefetchResourcesForShell() { @@ -1344,6 +1289,13 @@ class Benchmark { } plotContainer.innerHTML = `${circlesSVG}`; } + + tearDown() { + if (isInBrowser) { + browserFileLoader.free(this.files); + browserFileLoader.free(this.preloadFiles); + } + } }; class GroupedBenchmark extends Benchmark { @@ -1358,14 +1310,9 @@ class GroupedBenchmark extends Benchmark { this.benchmarks = benchmarks; } - async prefetchResourcesForBrowser(counter) { - for (const benchmark of this.benchmarks) - await benchmark.prefetchResourcesForBrowser(counter); - } - - async retryPrefetchResourcesForBrowser(counter) { + async prefetchResourcesForBrowser() { for (const benchmark of this.benchmarks) - await benchmark.retryPrefetchResourcesForBrowser(counter); + await benchmark.prefetchResourcesForBrowser(); } prefetchResourcesForShell() { @@ -1432,6 +1379,12 @@ class GroupedBenchmark extends Benchmark { this._state = BenchmarkState.DONE; } + tearDown() { + for (const benchmark of this.benchmarks) { + benchmark.tearDown(); + } + } + processResults() { this.results = []; for (const benchmark of this.benchmarks) From ab70076e3c56f815a9e54623d794f0c661ff946f Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Mon, 19 Jan 2026 17:50:04 +0100 Subject: [PATCH 2/2] fix --- JetStreamDriver.js | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index 10dcf9f5..7ac6cee6 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -307,8 +307,9 @@ class BrowserFileLoader { } } -const browserFileLoader = new BrowserFileLoader(); -const shellFileLoader = new ShellFileLoader(); +const fileLoader = isInBrowser ? + new BrowserFileLoader() : + new ShellFileLoader(); class Driver { constructor(benchmarks) { @@ -532,7 +533,7 @@ class Driver { } updateCounterUI() { - const counter = JetStream.counter; + const counter = fileLoader.counter; const statusElement = document.getElementById("status-text"); statusElement.innerText = `Loading ${counter.loadedResources} of ${counter.totalResources} ...`; @@ -1080,7 +1081,7 @@ class Benchmark { scripts.add(text); } else { for (const file of this.files) { - scripts.addWithURL(browserFileLoader.getBlobURL(file)); + scripts.addWithURL(fileLoader.getBlobURL(file)); } } @@ -1129,25 +1130,18 @@ class Benchmark { Realm.dispose(magicFrame); } - - updateCounter() { - const counter = JetStream.counter; - ++counter.loadedResources; - JetStream.updateCounterUI(); - } - async prefetchResourcesForBrowser() { console.assert(isInBrowser); - const promises = this.files.map((file) => browserFileLoader.prefetchResourceFile(file)); - for (const [name, resource] of Object.entries(this.plan.preload ?? {})) { + const promises = this.files.map((file) => fileLoader.prefetchResourceFile(file)); + for (const [name, resource] of this.preloadEntries) { promises.push(this.prefetchResourcePreload(name, resource)); } await Promise.all(promises); } async prefetchResourcePreload(name, resource) { - const preloadData = await browserFileLoader.prefetchResourcePreload(name, resource); - this.preloads.push(preloadData); + const preloadData = await fileLoader.prefetchResourcePreload(name, resource); + this._preloadBlobData.push(preloadData); } prefetchResourcesForShell() { @@ -1155,7 +1149,7 @@ class Benchmark { console.assert(!isInBrowser); console.assert(this._scripts === null, "This initialization should be called only once."); - this._scripts = this.files.map(file => shellFileLoader.load(file)); + this._scripts = this.files.map(file => fileLoader.load(file)); console.assert(this._preloadBlobData.length === 0, "This initialization should be called only once."); this._shellPrefetchedResources = Object.create(null); @@ -1292,8 +1286,8 @@ class Benchmark { tearDown() { if (isInBrowser) { - browserFileLoader.free(this.files); - browserFileLoader.free(this.preloadFiles); + fileLoader.free(this.files); + fileLoader.free(this.preloadEntries.map(([_, file]) => file)); } } };