Skip to content

Commit

Permalink
Merge branch 'BrunnerLivio-fix/process-memory-leak' into 6.0.0-next
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Mar 5, 2019
2 parents 9f1a32c + c4b92e9 commit 3935279
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 24 deletions.
29 changes: 27 additions & 2 deletions integration/hooks/e2e/on-app-shutdown.spec.ts
@@ -1,8 +1,9 @@
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as Sinon from 'sinon';
import { Injectable, OnApplicationShutdown } from '@nestjs/common';
import { spawn } from 'child_process';
import { Injectable, OnApplicationShutdown, ShutdownSignal } from '@nestjs/common';
import { spawn, spawnSync } from 'child_process';
import { join } from 'path';

@Injectable()
class TestInjectable implements OnApplicationShutdown {
Expand All @@ -20,4 +21,28 @@ describe('OnApplicationShutdown', () => {
const instance = module.get(TestInjectable);
expect(instance.onApplicationShutdown.called).to.be.true;
});

it('should call onApplicationShutdown if any shutdown signal gets invoked', done => {
const result = spawnSync('ts-node', [join(__dirname, '../src/main.ts'), 'SIGHUP']);
expect(result.stdout.toString().trim()).to.be.eq('Signal SIGHUP');
done();
});

it('should call onApplicationShutdown if a specific shutdown signal gets invoked', done => {
const result = spawnSync('ts-node', [join(__dirname, '../src/main.ts'), 'SIGINT', 'SIGINT']);
expect(result.stdout.toString().trim()).to.be.eq('Signal SIGINT');
done();
});

it('should ignore system signals which are not specified', done => {
const result = spawnSync('ts-node', [join(__dirname, '../src/main.ts'), 'SIGINT', 'SIGHUP']);
expect(result.stdout.toString().trim()).to.be.eq('');
done();
});

it('should ignore system signals if "enableShutdownHooks" was not called', done => {
const result = spawnSync('ts-node', [join(__dirname, '../src/main.ts'), 'SIGINT', 'NONE']);
expect(result.stdout.toString().trim()).to.be.eq('');
done();
});
});
31 changes: 31 additions & 0 deletions integration/hooks/src/main.ts
@@ -0,0 +1,31 @@
import { Injectable, OnApplicationShutdown, Module } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
const SIGNAL = process.argv[2];
const SIGNAL_TO_LISTEN = process.argv[3];

@Injectable()
class TestInjectable implements OnApplicationShutdown {
onApplicationShutdown(signal: string) {
console.log('Signal ' + signal);
}
}

@Module({
providers: [TestInjectable],
})
class AppModule { }

async function bootstrap() {
const app = await NestFactory.create(AppModule, { logger: true });

if (SIGNAL_TO_LISTEN && SIGNAL_TO_LISTEN !== 'NONE') {
app.enableShutdownHooks([SIGNAL_TO_LISTEN]);
} else if (SIGNAL_TO_LISTEN !== 'NONE') {
app.enableShutdownHooks();
}

await app.listen(1800);
process.kill(process.pid, SIGNAL);
}

bootstrap();
1 change: 1 addition & 0 deletions packages/common/enums/index.ts
@@ -1,2 +1,3 @@
export * from './request-method.enum';
export * from './http-status.enum';
export * from './shutdown-signal.enum';
16 changes: 16 additions & 0 deletions packages/common/enums/shutdown-signal.enum.ts
@@ -0,0 +1,16 @@
/**
* System signals which shut down a process
*/
export enum ShutdownSignal {
SIGHUP = 'SIGHUP',
SIGINT = 'SIGINT',
SIGQUIT = 'SIGQUIT',
SIGILL = 'SIGILL',
SIGTRAP = 'SIGTRAP',
SIGABRT = 'SIGABRT',
SIGBUS = 'SIGBUS',
SIGFPE = 'SIGFPE',
SIGSEGV = 'SIGSEGV',
SIGUSR2 = 'SIGUSR2',
SIGTERM = 'SIGTERM',
}
10 changes: 10 additions & 0 deletions packages/common/interfaces/nest-application-context.interface.ts
@@ -1,6 +1,7 @@
import { LoggerService } from '../services/logger.service';
import { Abstract } from './abstract.interface';
import { Type } from './type.interface';
import { ShutdownSignal } from '../enums/shutdown-signal.enum';

export interface INestApplicationContext {
/**
Expand Down Expand Up @@ -29,4 +30,13 @@ export interface INestApplicationContext {
* @returns {void}
*/
useLogger(logger: LoggerService): void;

/**
* Enables the usage of shutdown hooks. Will call the
* `onApplicationShutdown` function of a provider if the
* process receives a shutdown signal.
*
* @returns {this} The Nest application context instance
*/
enableShutdownHooks(signals?: ShutdownSignal[] | string[]): this;
}
13 changes: 0 additions & 13 deletions packages/core/constants.ts
Expand Up @@ -9,16 +9,3 @@ export const APP_INTERCEPTOR = 'APP_INTERCEPTOR';
export const APP_PIPE = 'APP_PIPE';
export const APP_GUARD = 'APP_GUARD';
export const APP_FILTER = 'APP_FILTER';
export const SHUTDOWN_SIGNALS = [
'SIGHUP',
'SIGINT',
'SIGQUIT',
'SIGILL',
'SIGTRAP',
'SIGABRT',
'SIGBUS',
'SIGFPE',
'SIGSEGV',
'SIGUSR2',
'SIGTERM',
];
68 changes: 60 additions & 8 deletions packages/core/nest-application-context.ts
@@ -1,7 +1,12 @@
import { INestApplicationContext, Logger, LoggerService } from '@nestjs/common';
import {
INestApplicationContext,
Logger,
LoggerService,
ShutdownSignal,
} from '@nestjs/common';
import { Abstract } from '@nestjs/common/interfaces';
import { Type } from '@nestjs/common/interfaces/type.interface';
import { SHUTDOWN_SIGNALS } from './constants';
import { isEmpty } from '@nestjs/common/utils/shared.utils';
import { UnknownModuleException } from './errors/exceptions/unknown-module.exception';
import {
callAppShutdownHook,
Expand All @@ -17,6 +22,7 @@ import { ModuleTokenFactory } from './injector/module-token-factory';
export class NestApplicationContext implements INestApplicationContext {
private readonly moduleTokenFactory = new ModuleTokenFactory();
private readonly containerScanner: ContainerScanner;
private readonly activeShutdownSignals: string[] = new Array<string>();

constructor(
protected readonly container: NestContainer,
Expand Down Expand Up @@ -60,7 +66,6 @@ export class NestApplicationContext implements INestApplicationContext {
public async init(): Promise<this> {
await this.callInitHook();
await this.callBootstrapHook();
await this.listenToShutdownSignals();
return this;
}

Expand All @@ -73,14 +78,61 @@ export class NestApplicationContext implements INestApplicationContext {
Logger.overrideLogger(logger);
}

protected listenToShutdownSignals() {
SHUTDOWN_SIGNALS.forEach((signal: any) =>
process.on(signal, async code => {
/**
* Enables the usage of shutdown hooks. Will call the
* `onApplicationShutdown` function of a provider if the
* process receives a shutdown signal.
*
* @param {ShutdownSignal[]} [signals=[]] The system signals it should listen to
*
* @returns {this} The Nest application context instance
*/
public enableShutdownHooks(signals: (ShutdownSignal | string)[] = []): this {
if (isEmpty(signals)) {
signals = Object.keys(ShutdownSignal).map(
(key: string) => ShutdownSignal[key],
);
} else {
// Given signals array should be unique because
// process shouldn't listen to the same signal more than once.
signals = Array.from(new Set(signals));
}

signals = signals
.map((signal: ShutdownSignal | string): string =>
signal
.toString()
.toUpperCase()
.trim(),
)
// Filter out the signals which is already listening to
.filter(
(signal: string) => !this.activeShutdownSignals.includes(signal),
) as string[];

this.listenToShutdownSignals(signals);
return this;
}

/**
* Listens to shutdown signals by listening to
* process events
*
* @param {string[]} signals The system signals it should listen to
*/
protected listenToShutdownSignals(signals: string[]) {
signals.forEach((signal: string) => {
this.activeShutdownSignals.push(signal);

process.on(signal as any, async code => {
// Call the destroy and shutdown hook
// in case the process receives a shutdown signal
await this.callDestroyHook();
await this.callShutdownHook(signal);

process.exit(code || 1);
}),
);
});
});
}

/**
Expand Down
1 change: 0 additions & 1 deletion packages/core/nest-application.ts
Expand Up @@ -126,7 +126,6 @@ export class NestApplication extends NestApplicationContext
await this.callInitHook();
await this.registerRouterHooks();
await this.callBootstrapHook();
await this.listenToShutdownSignals();

this.isInitialized = true;
this.logger.log(MESSAGES.APPLICATION_READY);
Expand Down

0 comments on commit 3935279

Please sign in to comment.