fix: run gen race condition

This commit is contained in:
2026-10-09 21:17:21 +05:00
parent 58699abdf1
commit 6dcbb7c0f8
2 changed files with 14 additions and 11 deletions
+5 -10
View File
@@ -35,17 +35,12 @@
`schema-tool-state.svelte.ts` (стейт шагов), `AlignedLayout.svelte` / `schema-tool-state.svelte.ts` (стейт шагов), `AlignedLayout.svelte` /
`WorkspaceLayout.svelte`. `WorkspaceLayout.svelte`.
### H2. Race condition превью: устаревший ответ worker'а перезаписывает новый ### H2. Race condition превью — СДЕЛАНО (2026-10)
`SchemaToolView.svelte:430-465` (`run()`) + эффект :567-575. Debounce (:199) `SchemaToolView.svelte`: добавлен generation-counter `runGen`; устаревший прогон
отменяет только запланированный запуск; уже летящий `runChain` не отменяется и `runChain` после `await` выходит без записи результатов, ошибок и сброса
нет generation-токена: при смене параметров во время долгого worker-прогона `running` (`finally` сбрасывает только актуальный); `init()` инкрементом
старый результат перезапишет новый (`assignResult` без проверки актуальности). инвалидирует прогоны прошлого хоста.
Сам executor с request-id корректен (`executor/executor.ts:45-96`) — проблема на
уровне UI. Пользователь видит превью не от своих параметров.
Минимальный фикс:
`let runGen = 0; const gen = ++runGen; ... if (gen !== runGen) return;`.
### H3. `registry-schema.ts` — 723 строки, 45 экспортов, 5 ответственностей ### H3. `registry-schema.ts` — 723 строки, 45 экспортов, 5 ответственностей
@@ -122,6 +122,9 @@
undefined, undefined,
); );
let running = $state(false); let running = $state(false);
// Generation of the latest run(): a stale in-flight chain (params changed
// while the worker was busy) must not overwrite newer results.
let runGen = 0;
let displayError = $state<DisplayError | null>(null); let displayError = $state<DisplayError | null>(null);
let sourceWarnings = $state<ChainWarning[]>([]); let sourceWarnings = $state<ChainWarning[]>([]);
let runWarnings = $state<ChainWarning[]>([]); let runWarnings = $state<ChainWarning[]>([]);
@@ -220,6 +223,8 @@
const host = chainId ? `chain:${chainId}` : page.slug; const host = chainId ? `chain:${chainId}` : page.slug;
if (host === initedFor) return; if (host === initedFor) return;
initedFor = host; initedFor = host;
// Invalidate any in-flight run from the previous host.
runGen++;
if (chainId) { if (chainId) {
const handoff = takeHandoff(chainId); const handoff = takeHandoff(chainId);
steps = getChain(chainId)?.steps ?? createChain(page); steps = getChain(chainId)?.steps ?? createChain(page);
@@ -429,6 +434,7 @@
async function run() { async function run() {
if (!schema || steps.length === 0) return; if (!schema || steps.length === 0) return;
const gen = ++runGen;
displayError = null; displayError = null;
running = true; running = true;
try { try {
@@ -439,12 +445,14 @@
? [{ name: `${baseName(page.slug)}.png`, image: source }] ? [{ name: `${baseName(page.slug)}.png`, image: source }]
: []; : [];
const r = await runChain(steps, input, textSource || undefined, execute); const r = await runChain(steps, input, textSource || undefined, execute);
if (gen !== runGen) return;
stepDims = r.stepDims; stepDims = r.stepDims;
stepResults = r.stepResults; stepResults = r.stepResults;
stepFileSets = r.stepFileSets; stepFileSets = r.stepFileSets;
runWarnings = r.warnings; runWarnings = r.warnings;
assignResult(r.out); assignResult(r.out);
} catch (e) { } catch (e) {
if (gen !== runGen) return;
if (e instanceof ChainStepError) { if (e instanceof ChainStepError) {
const base = toDisplayError(e.cause); const base = toDisplayError(e.cause);
const msg = base.kind === "i18n" ? t(base.key, base.vars) : base.text; const msg = base.kind === "i18n" ? t(base.key, base.vars) : base.text;
@@ -460,7 +468,7 @@
displayError = toDisplayError(e); displayError = toDisplayError(e);
} }
} finally { } finally {
running = false; if (gen === runGen) running = false;
} }
} }