Release response streams used by cache metadata probes
This commit is contained in:
@@ -49,10 +49,19 @@
|
|||||||
return response ? response.json() : null;
|
return response ? response.json() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function verifiedProbe(response, hash) {
|
||||||
|
if (!response) return false;
|
||||||
|
const valid = response.headers.get('X-Asset-Hash') === hash;
|
||||||
|
// Metadata checks must release Blob/data-pipe readers. They never consume
|
||||||
|
// asset bytes, and cancelling this response does not remove its cache entry.
|
||||||
|
response.body?.cancel().catch(() => {});
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
async function status(manifest, cache, { previous, activeLayout, offline = true } = {}) {
|
async function status(manifest, cache, { previous, activeLayout, offline = true } = {}) {
|
||||||
const found = await Promise.all(blocking(manifest, previous, activeLayout).map(async path => {
|
const found = await Promise.all(blocking(manifest, previous, activeLayout).map(async path => {
|
||||||
const response = await cache.match(url(path, manifest.files[path]));
|
const response = await cache.match(url(path, manifest.files[path]));
|
||||||
return !!response && response.headers.get('X-Asset-Hash') === manifest.files[path].h;
|
return verifiedProbe(response, manifest.files[path].h);
|
||||||
}));
|
}));
|
||||||
const missing = found.filter(value => !value).length;
|
const missing = found.filter(value => !value).length;
|
||||||
return { ready: missing === 0, missing, version: manifest.buildTag, ...(offline ? await completeness(manifest, cache) : {}) };
|
return { ready: missing === 0, missing, version: manifest.buildTag, ...(offline ? await completeness(manifest, cache) : {}) };
|
||||||
@@ -85,7 +94,7 @@
|
|||||||
for (const path of selected) {
|
for (const path of selected) {
|
||||||
const key = url(path, manifest.files[path]);
|
const key = url(path, manifest.files[path]);
|
||||||
const response = await cache.match(key);
|
const response = await cache.match(key);
|
||||||
if (!response || response.headers.get('X-Asset-Hash') !== manifest.files[path].h) missing.push(key);
|
if (!verifiedProbe(response, manifest.files[path].h)) missing.push(key);
|
||||||
}
|
}
|
||||||
const count = missing.length;
|
const count = missing.length;
|
||||||
let failure;
|
let failure;
|
||||||
@@ -94,7 +103,7 @@
|
|||||||
const key = missing.shift();
|
const key = missing.shift();
|
||||||
try { await sharedDownload(key, async () => {
|
try { await sharedDownload(key, async () => {
|
||||||
const held = await cache.match(key);
|
const held = await cache.match(key);
|
||||||
if (held?.headers.get('X-Asset-Hash') === key.split('v=')[1]) return;
|
if (verifiedProbe(held, key.split('v=')[1])) return;
|
||||||
let error;
|
let error;
|
||||||
for (let attempt = 0; attempt < Math.min(3, attempts); attempt++) {
|
for (let attempt = 0; attempt < Math.min(3, attempts); attempt++) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
@@ -127,7 +136,7 @@
|
|||||||
const files = paths(manifest);
|
const files = paths(manifest);
|
||||||
const missingFiles = (await Promise.all(files.map(async path => {
|
const missingFiles = (await Promise.all(files.map(async path => {
|
||||||
const response = await cache.match(url(path, manifest.files[path]));
|
const response = await cache.match(url(path, manifest.files[path]));
|
||||||
return !response || response.headers.get('X-Asset-Hash') !== manifest.files[path].h ? path : null;
|
return verifiedProbe(response, manifest.files[path].h) ? null : path;
|
||||||
}))).filter(Boolean);
|
}))).filter(Boolean);
|
||||||
const total = files.length;
|
const total = files.length;
|
||||||
return { offlineReady: missingFiles.length === 0, cached: total - missingFiles.length, total, missingFiles };
|
return { offlineReady: missingFiles.length === 0, cached: total - missingFiles.length, total, missingFiles };
|
||||||
|
|||||||
@@ -89,3 +89,10 @@ test('blocking commit never waits on an optional file readiness probe',{timeout:
|
|||||||
await core.commit(m,c);assert.equal(extraProbes,0);assert.equal((await core.state(c)).current.buildTag,m.buildTag);
|
await core.commit(m,c);assert.equal(extraProbes,0);assert.equal((await core.state(c)).current.buildTag,m.buildTag);
|
||||||
c.match=match;assert.equal((await core.status(m,c)).offlineReady,false);
|
c.match=match;assert.equal((await core.status(m,c)).offlineReady,false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('metadata probes release response streams while stored asset bodies remain readable',async()=>{
|
||||||
|
const c=cache(),m=manifest();await c.put('/a.js?v=a',response('a'));await c.put('/index.html?v=b',response('b'));
|
||||||
|
const match=c.match,probes=[];c.match=async key=>{const r=await match(key);if(r)probes.push(r);return r;};
|
||||||
|
assert.equal((await core.status(m,c)).offlineReady,true);assert.ok(probes.length>0);assert.ok(probes.every(r=>r.bodyUsed));
|
||||||
|
assert.equal(await(await match('/a.js?v=a')).text(),'a');assert.equal(await(await match('/index.html?v=b')).text(),'b');
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user