fix(engine): run merges outside the admission drain — every merge froze planning admission
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
7
.changeset/merge-outside-admission-drain.md
Normal file
7
.changeset/merge-outside-admission-drain.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Planning admission no longer freezes for the duration of every merge.
|
||||||
|
category: fix
|
||||||
|
dev: Root cause of the 5-10 min "Queued to plan" stalls — the merge lane ran the entire merge inside projectAdmissionCoordinator's single-flight drain, so triage's poll parked awaiting it (and its re-entrance guard then dropped every tick silently). The lane start now claims and returns; the merge body runs outside the drain. No automated regression test yet — the drain-blocking shape needs a project-engine harness; the triage poll watchdog (e51ebff381) is the backstop meanwhile.
|
||||||
@@ -3881,7 +3881,24 @@ export class ProjectEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let selected = false;
|
let selected = false;
|
||||||
let value: T | undefined;
|
/*
|
||||||
|
FNXC:ConcurrencyAdmission 2026-08-01-01:50 (ROOT CAUSE — triage admission died during every merge):
|
||||||
|
This lane previously ran `value = await start()` INSIDE its admission `start()` callback —
|
||||||
|
i.e. the ENTIRE merge (git rebase, verification, landing: minutes, or forever when the
|
||||||
|
merge wedges) executed inside `admitOldest`'s single-flight drain. The coordinator is a
|
||||||
|
project-wide singleton and every caller awaits the previous drain, so triage's poll parked
|
||||||
|
at `await existing` for the whole merge window, its `polling` re-entrance guard stayed
|
||||||
|
closed, and every 15s tick + task:created wake dropped silently. Observed twice on the
|
||||||
|
live board as "queued to plan with open capacity" (5m50s behind FN-8627's merge; ~10min
|
||||||
|
behind FN-8635's adoption-paused landing), each ending in a batch admission the second
|
||||||
|
the merge finished. With merge pinned at 1, every merge was a planning outage.
|
||||||
|
|
||||||
|
The lane start now only CLAIMS the admission and returns; the merge body runs after
|
||||||
|
`admitOldest` settles, outside the drain. Capacity stays honest: the merge row's own
|
||||||
|
merging/landing status is what `claimed()` counts, and at-most-once merging is enforced
|
||||||
|
by the merge lease, not by this drain. The transient admit→status-write gap is the same
|
||||||
|
one every other lane (triage `void specifyTask`, scheduler `void schedule`) already has.
|
||||||
|
*/
|
||||||
await projectAdmissionCoordinator.admitOldest({
|
await projectAdmissionCoordinator.admitOldest({
|
||||||
projectId: cwd,
|
projectId: cwd,
|
||||||
maxConcurrent: (await store.getSettings()).maxConcurrent ?? 2,
|
maxConcurrent: (await store.getSettings()).maxConcurrent ?? 2,
|
||||||
@@ -3895,14 +3912,16 @@ export class ProjectEngine {
|
|||||||
createdAt: mergeCandidate?.createdAt,
|
createdAt: mergeCandidate?.createdAt,
|
||||||
start: async () => {
|
start: async () => {
|
||||||
selected = true;
|
selected = true;
|
||||||
value = await start();
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
if (!selected) return undefined;
|
if (!selected) return undefined;
|
||||||
projectAdmissionCoordinator.releaseReservation(taskId);
|
try {
|
||||||
return value;
|
return await start();
|
||||||
|
} finally {
|
||||||
|
projectAdmissionCoordinator.releaseReservation(taskId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mergeStrategy === "pull-request" && this.options.processPullRequestMerge && !routeWorkspaceDirect) {
|
if (mergeStrategy === "pull-request" && this.options.processPullRequestMerge && !routeWorkspaceDirect) {
|
||||||
|
|||||||
Reference in New Issue
Block a user