mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 21:07:28 -08:00
fix(core): Disconnect Redis after pausing queue during worker shutdown (#9928)
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
parent
e5c324753f
commit
c82579bf76
|
@ -9,6 +9,7 @@ import {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { ActiveExecutions } from '@/ActiveExecutions';
|
import { ActiveExecutions } from '@/ActiveExecutions';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
import { HIGHEST_PRIORITY, OnShutdown } from './decorators/OnShutdown';
|
||||||
|
|
||||||
export type JobId = Bull.JobId;
|
export type JobId = Bull.JobId;
|
||||||
export type Job = Bull.Job<JobData>;
|
export type Job = Bull.Job<JobData>;
|
||||||
|
@ -108,11 +109,10 @@ export class Queue {
|
||||||
return await this.jobQueue.client.ping();
|
return await this.jobQueue.client.ping();
|
||||||
}
|
}
|
||||||
|
|
||||||
async pause({
|
@OnShutdown(HIGHEST_PRIORITY)
|
||||||
isLocal,
|
// Stop accepting new jobs, `doNotWaitActive` allows reporting progress
|
||||||
doNotWaitActive,
|
async pause(): Promise<void> {
|
||||||
}: { isLocal?: boolean; doNotWaitActive?: boolean } = {}): Promise<void> {
|
return await this.jobQueue?.pause(true, true);
|
||||||
return await this.jobQueue.pause(isLocal, doNotWaitActive);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getBullObjectInstance(): JobQueue {
|
getBullObjectInstance(): JobQueue {
|
||||||
|
|
|
@ -64,9 +64,6 @@ export class Worker extends BaseCommand {
|
||||||
async stopProcess() {
|
async stopProcess() {
|
||||||
this.logger.info('Stopping n8n...');
|
this.logger.info('Stopping n8n...');
|
||||||
|
|
||||||
// Stop accepting new jobs, `doNotWaitActive` allows reporting progress
|
|
||||||
await Worker.jobQueue.pause({ isLocal: true, doNotWaitActive: true });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.externalHooks?.run('n8n.stop', []);
|
await this.externalHooks?.run('n8n.stop', []);
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,10 @@ import { Container } from 'typedi';
|
||||||
import { ApplicationError } from 'n8n-workflow';
|
import { ApplicationError } from 'n8n-workflow';
|
||||||
import { type ServiceClass, ShutdownService } from '@/shutdown/Shutdown.service';
|
import { type ServiceClass, ShutdownService } from '@/shutdown/Shutdown.service';
|
||||||
|
|
||||||
|
export const LOWEST_PRIORITY = 0;
|
||||||
|
export const DEFAULT_PRIORITY = 100;
|
||||||
|
export const HIGHEST_PRIORITY = 200;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decorator that registers a method as a shutdown hook. The method will
|
* Decorator that registers a method as a shutdown hook. The method will
|
||||||
* be called when the application is shutting down.
|
* be called when the application is shutting down.
|
||||||
|
@ -22,7 +26,7 @@ import { type ServiceClass, ShutdownService } from '@/shutdown/Shutdown.service'
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export const OnShutdown =
|
export const OnShutdown =
|
||||||
(priority = 100): MethodDecorator =>
|
(priority = DEFAULT_PRIORITY): MethodDecorator =>
|
||||||
(prototype, propertyKey, descriptor) => {
|
(prototype, propertyKey, descriptor) => {
|
||||||
const serviceClass = prototype.constructor as ServiceClass;
|
const serviceClass = prototype.constructor as ServiceClass;
|
||||||
const methodName = String(propertyKey);
|
const methodName = String(propertyKey);
|
||||||
|
|
|
@ -40,10 +40,6 @@ class RedisServiceBase {
|
||||||
}
|
}
|
||||||
this.redisClient = this.redisClientService.createClient({ type });
|
this.redisClient = this.redisClientService.createClient({ type });
|
||||||
|
|
||||||
this.redisClient.on('close', () => {
|
|
||||||
this.logger.warn('Redis unavailable - trying to reconnect...');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.redisClient.on('error', (error) => {
|
this.redisClient.on('error', (error) => {
|
||||||
if (!String(error).includes('ECONNREFUSED')) {
|
if (!String(error).includes('ECONNREFUSED')) {
|
||||||
this.logger.warn('Error with Redis: ', error);
|
this.logger.warn('Error with Redis: ', error);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Logger } from '@/Logger';
|
||||||
import ioRedis from 'ioredis';
|
import ioRedis from 'ioredis';
|
||||||
import type { Cluster, RedisOptions } from 'ioredis';
|
import type { Cluster, RedisOptions } from 'ioredis';
|
||||||
import type { RedisClientType } from './RedisServiceBaseClasses';
|
import type { RedisClientType } from './RedisServiceBaseClasses';
|
||||||
import { OnShutdown } from '@/decorators/OnShutdown';
|
import { LOWEST_PRIORITY, OnShutdown } from '@/decorators/OnShutdown';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class RedisClientService {
|
export class RedisClientService {
|
||||||
|
@ -23,7 +23,7 @@ export class RedisClientService {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnShutdown()
|
@OnShutdown(LOWEST_PRIORITY)
|
||||||
disconnectClients() {
|
disconnectClients() {
|
||||||
for (const client of this.clients) {
|
for (const client of this.clients) {
|
||||||
client.disconnect();
|
client.disconnect();
|
||||||
|
@ -144,6 +144,8 @@ export class RedisClientService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.warn('Redis unavailable - trying to reconnect...');
|
||||||
|
|
||||||
return RETRY_INTERVAL;
|
return RETRY_INTERVAL;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const setupTestCommand = <T extends BaseCommand>(Command: Class<T>) => {
|
||||||
|
|
||||||
// mock SIGINT/SIGTERM registration
|
// mock SIGINT/SIGTERM registration
|
||||||
process.once = jest.fn();
|
process.once = jest.fn();
|
||||||
|
process.exit = jest.fn() as never;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await testDb.init();
|
await testDb.init();
|
||||||
|
|
Loading…
Reference in a new issue