fix(FN-6035): cap scheduler dispatch by global slots

This commit is contained in:
gsxdsm
2026-06-08 22:47:51 -07:00
parent 1029a1b8b4
commit a98713d01d
2 changed files with 32 additions and 1 deletions

View File

@@ -1411,6 +1411,34 @@ describe("Scheduler", () => {
expect(store.moveTask).not.toHaveBeenCalled(); expect(store.moveTask).not.toHaveBeenCalled();
}); });
it("caps in-progress dispatch by the global semaphore limit even before executors acquire slots", async () => {
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(readFile).mockResolvedValue("# Task\nDo something");
const semaphore = new AgentSemaphore(3);
const tasks = [
createMockTask({ id: "FN-001", column: "in-progress" }),
createMockTask({ id: "FN-002", column: "in-progress" }),
createMockTask({ id: "FN-003", column: "in-progress" }),
createMockTask({ id: "FN-004", column: "todo", dependencies: [] }),
createMockTask({ id: "FN-005", column: "todo", dependencies: [] }),
];
const store = createMockStore({
listTasks: vi.fn().mockResolvedValue(tasks),
getTask: vi.fn(async (taskId: string) => tasks.find((task) => task.id === taskId) ?? null),
getSettings: vi.fn().mockResolvedValue({ maxConcurrent: 5, maxWorktrees: 10 }),
updateTask: vi.fn().mockResolvedValue(undefined),
moveTask: vi.fn().mockResolvedValue(undefined),
});
const scheduler = new Scheduler(store, { semaphore });
scheduler.start();
await scheduler.schedule();
expect(store.moveTask).not.toHaveBeenCalled();
});
it("respects maxWorktrees limit", async () => { it("respects maxWorktrees limit", async () => {
const tasks = [ const tasks = [
createMockTask({ id: "FN-001", column: "in-progress" }), createMockTask({ id: "FN-001", column: "in-progress" }),

View File

@@ -1277,7 +1277,10 @@ export class Scheduler {
// When a semaphore is provided, factor in its available slots so we // When a semaphore is provided, factor in its available slots so we
// don't schedule more tasks than the global limit allows. // don't schedule more tasks than the global limit allows.
const semaphoreAvailable = this.options.semaphore const semaphoreAvailable = this.options.semaphore
? this.options.semaphore.availableCount ? Math.min(
this.options.semaphore.availableCount,
this.options.semaphore.limit - agentSlots,
)
: Infinity; : Infinity;
const available = Math.min( const available = Math.min(