Let the flights' fix history fill before the long soak measures the heap, and say why a context-loss case drew no frame

The long soak's heap slope counted the per-aircraft dead-reckoning history (POSITION_HISTORY_LIMIT fixes for 10 000 aircraft, about 14 MiB over the first 2.5 min of polls) as switch growth: a run with the same workload and no switches grows as much and then levels off. With --assert the soak now waits until the flights feed has been read more times than the history keeps before it switches; over 500 switches the heap then grows 1.07 KiB per switch.

qa-strict-context-loss records, only when no non-blank frame came, how many frames the running and the watched engine drew in 3 s and how much of the last one differs from its first pixel.
This commit is contained in:
German Khokhlov committed 2026-11-04 17:48:46 +04:00
1 parent f89aee1eac
commit 1100cdcff5
2 files changed
+63 -1

No files matched your search

+46
View File
@@ -164,6 +164,51 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
const outcome = after.engine === to ? 'switched' : 'rolled back';
if (after.running) await rememberEngine(p);
const drawn = after.running ? await draws(after.engine) : false;
// Why no frame passed, when none did: frames drawn in 3 s by the engine
// the page runs and by the one the probe watched, and how many of 48x48
// samples of the last frame differ from the first one.
const noFrame =
after.running && !drawn
? await p.evaluate(async (kind) => {
const running = window.__godsEyeView.engine;
const watched = window.__qaEngines.get(kind);
const probe = document.createElement('canvas');
probe.width = probe.height = 48;
const context = probe.getContext('2d', {
willReadFrequently: true,
});
const count = { running: 0, watched: 0, differing: null };
const offs = [
running.frames.postRender.addEventListener(() => {
count.running++;
context.drawImage(running.host.canvas, 0, 0, 48, 48);
const d = context.getImageData(0, 0, 48, 48).data;
let differing = 0;
for (let i = 4; i < d.length; i += 4)
if (
Math.abs(d[i] - d[0]) +
Math.abs(d[i + 1] - d[1]) +
Math.abs(d[i + 2] - d[2]) >
24
)
differing++;
count.differing = differing;
}),
watched === running
? () => {}
: watched?.frames.postRender.addEventListener(
() => count.watched++,
),
];
await new Promise((resolve) => setTimeout(resolve, 3000));
offs.forEach((off) => off?.());
return {
...count,
sameEngine: watched === running,
connected: running.host.canvas.isConnected,
};
}, after.engine)
: null;
console.log(
'after',
label,
@@ -205,6 +250,7 @@ await strict.run({ args: ['--use-angle=metal', '--enable-gpu'] }, async (b) => {
sessionIntact: intact(after),
history: after.history - before.history,
drawn,
...(noFrame ? { noFrame } : {}),
back,
backEngine: backState?.engine ?? null,
backSessionIntact: backState ? intact(backState) : false,
+17 -1
View File
@@ -14,7 +14,8 @@
//
// Checked on every switch: the same session (manager and every layer
// model), the radio still playing the same station with one play() call.
// With --assert (the long run), after the warm-up (switch 20): live WebGL
// With --assert (the long run), the flights' fix history fills first (see
// below), then after the warm-up (switch 20): live WebGL
// objects per engine context grow by at most 0.1 per switch (least-squares
// slope over the checkpoints) and by at most 64 overall; no new WebGL
// context; JS heap slope at most 10 KiB per switch; no new document; the
@@ -42,6 +43,7 @@ import {
} from './qa-strict-session.mjs';
import { installRadioFixture, stations } from './fixtures/w2/radio.mjs';
import { trafficFixtureResponse } from './traffic-fixtures.mjs';
import { POSITION_HISTORY_LIMIT } from '../src/layers/flights/recordPolicy.js';
const base = localPreview(process.argv[2]);
const option = (name, fallback) => {
@@ -193,6 +195,20 @@ for (let run = 1; run <= runs; run++) {
window.__godsEyeView.dataManager.layers.get('radio').module.getUIState()
.audioState === 'playing',
);
// Each aircraft keeps its last POSITION_HISTORY_LIMIT fixes for dead
// reckoning, so the first polls of the flights feed grow the heap by
// itself (about 14 MiB here, over 2.5 min; a run without switches then
// levels off). The long run lets that history fill before it switches,
// so the heap slope it asserts is the switches' own.
if (strictMode) {
const filling = Date.now();
await p.waitForFunction(
(polls) => window.__qaReads.flights > polls,
{ timeout: 300000, polling: 1000 },
POSITION_HISTORY_LIMIT,
);
row.historyFillSeconds = Math.round((Date.now() - filling) / 1000);
}
row.workload = await p.evaluate(() => {
const layers = window.__godsEyeView.dataManager.layers;
return Object.fromEntries(