mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
Merge pull request #1046 from rudycelekli/contrib/gods-025-asset-stream-cancellation-20261011-reviewed
fix(director): reject cancellation during the final asset stream read
This commit is contained in:
4 files changed
+88
No files matched your search
@@ -36,6 +36,8 @@
|
|||||||
|
|
||||||
- Director timeline seeks cross the dateline along the short longitude arc.
|
- Director timeline seeks cross the dateline along the short longitude arc.
|
||||||
|
|
||||||
|
- Director assets cancelled during the final stream read are discarded with reader cleanup.
|
||||||
|
|
||||||
- Analyst queries preserve useful rows when an upstream timestamp is outside the supported date range.
|
- Analyst queries preserve useful rows when an upstream timestamp is outside the supported date range.
|
||||||
|
|
||||||
- Fire detections with missing or impossible positions are skipped while genuine zero coordinates remain valid.
|
- Fire detections with missing or impossible positions are skipped while genuine zero coordinates remain valid.
|
||||||
|
|||||||
@@ -398,6 +398,10 @@ and geographic placement remain distinct; Stop/replacement releases resources.
|
|||||||
Import acquires nothing. See [data packs](DIRECTOR-DATA-PACKS.md). Existing scene
|
Import acquires nothing. See [data packs](DIRECTOR-DATA-PACKS.md). Existing scene
|
||||||
content, assets and credit remain unchanged.
|
content, assets and credit remain unchanged.
|
||||||
|
|
||||||
|
The asset directory source rejects with the caller's abort reason if
|
||||||
|
cancellation arrives during any read, including the final one, or while the
|
||||||
|
reader is being released.
|
||||||
|
|
||||||
Director version-4 documents add named geographic anchors and optional explicit
|
Director version-4 documents add named geographic anchors and optional explicit
|
||||||
camera moves with easing, duration and holds. Playback and seek share one
|
camera moves with easing, duration and holds. Playback and seek share one
|
||||||
coordinate sampler; navigation/manual input revokes authored motion and pending
|
coordinate sampler; navigation/manual input revokes authored motion and pending
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { createAssetDirectorySource } from './source.js';
|
||||||
|
test('cancellation during the terminating read cannot return a scene asset', async () => {
|
||||||
|
for (const cancel of [true, false]) {
|
||||||
|
const controller = new AbortController();
|
||||||
|
let reads = 0,
|
||||||
|
cancelled = 0,
|
||||||
|
released = 0;
|
||||||
|
const source = createAssetDirectorySource({
|
||||||
|
baseUrl: 'https://fixture.example/assets/',
|
||||||
|
fetchImpl: async () => ({
|
||||||
|
ok: true,
|
||||||
|
headers: new Headers({ 'content-type': 'application/octet-stream' }),
|
||||||
|
body: {
|
||||||
|
getReader() {
|
||||||
|
return {
|
||||||
|
async read() {
|
||||||
|
reads++;
|
||||||
|
if (reads === 1)
|
||||||
|
return { done: false, value: new Uint8Array([1, 2]) };
|
||||||
|
if (cancel) controller.abort();
|
||||||
|
return { done: true };
|
||||||
|
},
|
||||||
|
async cancel() {
|
||||||
|
cancelled++;
|
||||||
|
},
|
||||||
|
releaseLock() {
|
||||||
|
released++;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const pending = source({ path: 'scene.bin', signal: controller.signal });
|
||||||
|
if (cancel) await assert.rejects(pending, { name: 'AbortError' });
|
||||||
|
else assert.deepEqual([...(await pending).bytes], [1, 2]);
|
||||||
|
assert.equal(cancelled, 1);
|
||||||
|
assert.equal(released, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cancellation during reader cleanup cannot return a scene asset', async () => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const reason = new DOMException('scene changed', 'AbortError');
|
||||||
|
let released = 0;
|
||||||
|
const source = createAssetDirectorySource({
|
||||||
|
baseUrl: 'https://fixture.example/assets/',
|
||||||
|
fetchImpl: async () => ({
|
||||||
|
ok: true,
|
||||||
|
headers: new Headers(),
|
||||||
|
body: {
|
||||||
|
getReader() {
|
||||||
|
let reads = 0;
|
||||||
|
return {
|
||||||
|
async read() {
|
||||||
|
reads++;
|
||||||
|
return reads === 1
|
||||||
|
? { done: false, value: new Uint8Array([1]) }
|
||||||
|
: { done: true };
|
||||||
|
},
|
||||||
|
async cancel() {
|
||||||
|
controller.abort(reason);
|
||||||
|
},
|
||||||
|
releaseLock() {
|
||||||
|
released++;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
await assert.rejects(
|
||||||
|
source({ path: 'scene.bin', signal: controller.signal }),
|
||||||
|
(error) => error === reason,
|
||||||
|
);
|
||||||
|
assert.equal(released, 1);
|
||||||
|
});
|
||||||
@@ -41,6 +41,7 @@ export function createAssetDirectorySource({
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
|
signal?.throwIfAborted();
|
||||||
if (done) break;
|
if (done) break;
|
||||||
length += value.byteLength;
|
length += value.byteLength;
|
||||||
if (length > maxBytes) throw new Error('Asset exceeds byte limit');
|
if (length > maxBytes) throw new Error('Asset exceeds byte limit');
|
||||||
@@ -50,6 +51,8 @@ export function createAssetDirectorySource({
|
|||||||
await reader.cancel().catch(() => {});
|
await reader.cancel().catch(() => {});
|
||||||
reader.releaseLock();
|
reader.releaseLock();
|
||||||
}
|
}
|
||||||
|
// Cleanup awaits too; a cancel during it must still discard the asset.
|
||||||
|
signal?.throwIfAborted();
|
||||||
const bytes = new Uint8Array(length);
|
const bytes = new Uint8Array(length);
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
for (const chunk of chunks) {
|
for (const chunk of chunks) {
|
||||||
|
|||||||
Reference in new issue
Block a user