mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
feat(core): Leave the poll timeout headroom under the scheduler lease (no-changelog)
Some checks failed
CI: Python / Checks (push) Has been cancelled
Some checks failed
CI: Python / Checks (push) Has been cancelled
This commit is contained in:
parent
4d87c502b7
commit
e00a09a9b4
|
|
@ -43,7 +43,7 @@ describe('SchedulerConfig', () => {
|
|||
expect(scheduler.allowSkipDurableScheduler).toBe(false);
|
||||
expect(scheduler.maxAttempts).toBe(5);
|
||||
expect(scheduler.enabledForPollTriggers).toBe(false);
|
||||
expect(scheduler.pollTimeoutSeconds).toBe(60);
|
||||
expect(scheduler.pollTimeoutSeconds).toBe(45);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -270,20 +270,22 @@ export class SchedulerConfig {
|
|||
|
||||
/**
|
||||
* How long, in seconds, a single poll of an external source (an inbox, an API)
|
||||
* may take before it is abandoned. Defaults to 60 seconds.
|
||||
* may take before it is abandoned. Defaults to 45 seconds.
|
||||
*
|
||||
* An abandoned poll skips no data: its position in the source is left where it
|
||||
* was and the next scheduled poll covers the same ground. It counts as a poll
|
||||
* failure, so a source that keeps timing out is polled at a widening interval.
|
||||
* Guards against a poll stuck on an unresponsive source running indefinitely.
|
||||
*
|
||||
* Keep it at or below {@link leaseDurationSeconds}: a poll allowed to run
|
||||
* longer than the claim on its run can still be in flight when that claim
|
||||
* expires and another instance takes the run over. The scheduler warns at
|
||||
* startup if it doesn't. Must be greater than 0.
|
||||
* Keep it below {@link leaseDurationSeconds}: the deadline only starts after
|
||||
* the occurrence's setup reads, so a poll allowed to run as long as the claim
|
||||
* on its run can still be in flight when that claim expires and another
|
||||
* instance takes the run over. The default leaves that headroom, and the
|
||||
* scheduler warns at startup when the timeout reaches the lease duration.
|
||||
* Must be greater than 0.
|
||||
*/
|
||||
@Env('N8N_SCHEDULER_POLL_TIMEOUT', positiveIntSchema)
|
||||
pollTimeoutSeconds: number = Time.minutes.toSeconds;
|
||||
pollTimeoutSeconds: number = 45;
|
||||
|
||||
/**
|
||||
* Whether a poll trigger's cursor advance and the execution it produced are saved
|
||||
|
|
|
|||
|
|
@ -478,7 +478,7 @@ describe('GlobalConfig', () => {
|
|||
maxConcurrentPasses: 10,
|
||||
triggerNodeMode: 'legacy',
|
||||
enabledForPollTriggers: false,
|
||||
pollTimeoutSeconds: 60,
|
||||
pollTimeoutSeconds: 45,
|
||||
allowSkipDurableScheduler: false,
|
||||
maxAttempts: 5,
|
||||
misfireGraceSeconds: 60,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ describe('DurableScheduler', () => {
|
|||
materializationWindowSeconds = 60,
|
||||
misfireGraceSeconds = 60,
|
||||
enabledForPollTriggers = false,
|
||||
pollTimeoutSeconds = 60,
|
||||
pollTimeoutSeconds = 45,
|
||||
leaseDurationSeconds = 60,
|
||||
} = {}) {
|
||||
const inner = mock<Scheduler & SchedulerPasses>();
|
||||
|
|
@ -185,13 +185,28 @@ describe('DurableScheduler', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('does not warn when the timeout fits inside the lease', () => {
|
||||
// The poll deadline starts after the occurrence's setup reads, so a timeout
|
||||
// equal to the lease already lets a full-length poll outlive it.
|
||||
it('warns when the timeout equals the lease', () => {
|
||||
const { logger } = makeScheduler({
|
||||
enabledForPollTriggers: true,
|
||||
pollTimeoutSeconds: 60,
|
||||
leaseDurationSeconds: 60,
|
||||
});
|
||||
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining('poll timeout'),
|
||||
expect.objectContaining({ pollTimeoutSeconds: 60, leaseDurationSeconds: 60 }),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not warn when the timeout fits inside the lease', () => {
|
||||
const { logger } = makeScheduler({
|
||||
enabledForPollTriggers: true,
|
||||
pollTimeoutSeconds: 45,
|
||||
leaseDurationSeconds: 60,
|
||||
});
|
||||
|
||||
expect(logger.warn).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining('poll timeout'),
|
||||
expect.anything(),
|
||||
|
|
|
|||
|
|
@ -166,13 +166,14 @@ function warnOnDrainRate(logger: Logger, config: GlobalConfig['scheduler']): voi
|
|||
/**
|
||||
* Warn when a poll may still be in flight after the lease on its occurrence has
|
||||
* expired: the reaper can then reclaim the occurrence and another instance can
|
||||
* start the same poll while the first one is still running.
|
||||
* start the same poll while the first one is still running. Equality counts
|
||||
* too, since the poll deadline only starts after the occurrence's setup reads.
|
||||
*/
|
||||
function warnOnPollTimeout(logger: Logger, config: GlobalConfig['scheduler']): void {
|
||||
const { enabledForPollTriggers, pollTimeoutSeconds, leaseDurationSeconds } = config;
|
||||
if (enabledForPollTriggers && pollTimeoutSeconds > leaseDurationSeconds) {
|
||||
if (enabledForPollTriggers && pollTimeoutSeconds >= leaseDurationSeconds) {
|
||||
logger.warn(
|
||||
'Scheduler poll timeout is above the lease duration; a poll can still be running when its lease expires and another instance takes the run over',
|
||||
'Scheduler poll timeout reaches the lease duration; a poll can still be running when its lease expires and another instance takes the run over',
|
||||
{ pollTimeoutSeconds, leaseDurationSeconds },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user