mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
fix(core): Keep jobs a worker enqueued out of its shutdown drain (#39800)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
320cc2dda3
commit
cd9d1ac669
|
|
@ -573,6 +573,52 @@ describe('ActiveExecutions', () => {
|
|||
expect(activeExecutions.getRunningExecutionIds()).toEqual([runningExecutionId]);
|
||||
});
|
||||
|
||||
test('Should not list a running execution attached as enqueued', async () => {
|
||||
const inProcessExecutionId = await addExecutionWithStatus('running');
|
||||
activeExecutions.attachWorkflowExecution(inProcessExecutionId, workflowExecution);
|
||||
|
||||
const enqueuedExecutionId = await addExecutionWithStatus('running');
|
||||
activeExecutions.attachWorkflowExecution(enqueuedExecutionId, workflowExecution, {
|
||||
isQueueJob: true,
|
||||
});
|
||||
|
||||
expect(activeExecutions.getRunningExecutionIds()).toEqual([inProcessExecutionId]);
|
||||
});
|
||||
|
||||
test('Should cancel only the in-process execution when one is enqueued', async () => {
|
||||
const inProcessExecutionId = await addExecutionWithStatus('running');
|
||||
activeExecutions.attachWorkflowExecution(inProcessExecutionId, workflowExecution);
|
||||
const inProcessPostExecutePromise =
|
||||
activeExecutions.getPostExecutePromise(inProcessExecutionId);
|
||||
|
||||
const enqueuedExecutionId = await addExecutionWithStatus('running');
|
||||
const enqueuedWorkflowExecution = new PCancelable<IRun>((resolve) => resolve());
|
||||
enqueuedWorkflowExecution.cancel = vi.fn();
|
||||
activeExecutions.attachWorkflowExecution(enqueuedExecutionId, enqueuedWorkflowExecution, {
|
||||
isQueueJob: true,
|
||||
});
|
||||
const enqueuedPostExecutePromise =
|
||||
activeExecutions.getPostExecutePromise(enqueuedExecutionId);
|
||||
|
||||
await expect(activeExecutions.cancelRunningExecutions()).resolves.toEqual([
|
||||
inProcessExecutionId,
|
||||
]);
|
||||
|
||||
await expect(inProcessPostExecutePromise).rejects.toThrow(
|
||||
SystemShutdownExecutionCancelledError,
|
||||
);
|
||||
expect(workflowExecution.cancel).toHaveBeenCalled();
|
||||
expect(enqueuedWorkflowExecution.cancel).not.toHaveBeenCalled();
|
||||
expect(executionRepository.cancelManyRunning).toHaveBeenCalledWith([inProcessExecutionId]);
|
||||
expect(activeExecutions.has(enqueuedExecutionId)).toBe(true);
|
||||
|
||||
const outcome = await Promise.race([
|
||||
enqueuedPostExecutePromise.then(() => 'settled').catch(() => 'settled'),
|
||||
new Promise((resolve) => setTimeout(() => resolve('pending'), 0)),
|
||||
]);
|
||||
expect(outcome).toBe('pending');
|
||||
});
|
||||
|
||||
test('Should cancel only the executions with a running status', async () => {
|
||||
const runningExecutionId = await addExecutionWithStatus('running');
|
||||
activeExecutions.attachWorkflowExecution(runningExecutionId, workflowExecution);
|
||||
|
|
|
|||
|
|
@ -1240,6 +1240,25 @@ describe('enqueueExecution', () => {
|
|||
|
||||
expect(addJob).toHaveBeenCalledWith(expect.objectContaining(processData), expect.any(Object));
|
||||
});
|
||||
|
||||
it('excludes the execution from getRunningExecutionIds once enqueued', async () => {
|
||||
const workflow = await createWorkflow({}, owner);
|
||||
const activeExecutions = Container.get(ActiveExecutions);
|
||||
const data: IWorkflowExecutionDataProcess = {
|
||||
executionMode: 'trigger',
|
||||
workflowData: workflow,
|
||||
};
|
||||
const executionId = await activeExecutions.add(data);
|
||||
|
||||
const job = mock<Job>({ id: '1', data: { executionId, workflowId: workflow.id } });
|
||||
addJob.mockResolvedValueOnce(job);
|
||||
waitForJob.mockReturnValueOnce(new Promise(() => {}));
|
||||
|
||||
// @ts-expect-error Private method
|
||||
await runner.enqueueExecution(executionId, workflow.id, data);
|
||||
|
||||
expect(activeExecutions.getRunningExecutionIds()).not.toContain(executionId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('workflow timeout with startedAt', () => {
|
||||
|
|
|
|||
|
|
@ -196,8 +196,14 @@ export class ActiveExecutions {
|
|||
* Attaches an execution
|
||||
*/
|
||||
|
||||
attachWorkflowExecution(executionId: string, workflowExecution: PCancelable<IRun>) {
|
||||
this.getExecutionOrFail(executionId).workflowExecution = workflowExecution;
|
||||
attachWorkflowExecution(
|
||||
executionId: string,
|
||||
workflowExecution: PCancelable<IRun>,
|
||||
options?: { isQueueJob?: boolean },
|
||||
) {
|
||||
const execution = this.getExecutionOrFail(executionId);
|
||||
execution.workflowExecution = workflowExecution;
|
||||
execution.isQueueJob = options?.isQueueJob;
|
||||
}
|
||||
|
||||
attachResponsePromise(
|
||||
|
|
@ -320,8 +326,11 @@ export class ActiveExecutions {
|
|||
}
|
||||
|
||||
getRunningExecutionIds(): string[] {
|
||||
// An enqueued execution runs as a Bull job, which the queue drain tracks, so this only returns executions this process runs outside the queue.
|
||||
return Object.keys(this.activeExecutions).filter(
|
||||
(executionId) => this.activeExecutions[executionId].status === 'running',
|
||||
(executionId) =>
|
||||
this.activeExecutions[executionId].status === 'running' &&
|
||||
!this.activeExecutions[executionId].isQueueJob,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ export interface IExecutingWorkflowData {
|
|||
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>;
|
||||
workflowExecution?: PCancelable<IRun>;
|
||||
status: ExecutionStatus;
|
||||
isQueueJob?: boolean;
|
||||
}
|
||||
|
||||
export interface IActiveDirectorySettings {
|
||||
|
|
|
|||
|
|
@ -952,7 +952,9 @@ export class WorkflowRunner {
|
|||
// So we're just preventing crashes here.
|
||||
});
|
||||
|
||||
this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution);
|
||||
this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution, {
|
||||
isQueueJob: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user