From 17adb818bedc390a024fef722d9da67656b93764 Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Sat, 12 Oct 2019 08:44:21 -0500 Subject: [PATCH] Add ability to run server cluster --- packages/server/src/application/Bootstrap.ts | 2 + .../server/src/application/ServerBootstrap.ts | 36 +++++++++++- packages/server/src/cli/run.ts | 55 +++++++++++++------ 3 files changed, 73 insertions(+), 20 deletions(-) diff --git a/packages/server/src/application/Bootstrap.ts b/packages/server/src/application/Bootstrap.ts index 99d09cb..121ccad 100644 --- a/packages/server/src/application/Bootstrap.ts +++ b/packages/server/src/application/Bootstrap.ts @@ -8,6 +8,8 @@ export interface Bootstrap { readonly database: TypeORMDatabase getConfig(): Config listen(port?: number | string, hostname?: string): Promise + startCluster( + workers: number, port?: number | string, hostname?: string): Promise getAddress(): AddressInfo | string close(): Promise } diff --git a/packages/server/src/application/ServerBootstrap.ts b/packages/server/src/application/ServerBootstrap.ts index 8f8fdc2..3817efa 100644 --- a/packages/server/src/application/ServerBootstrap.ts +++ b/packages/server/src/application/ServerBootstrap.ts @@ -1,5 +1,7 @@ +import { TypeORMDatabase, TypeORMLogger } from '@rondo.dev/db-typeorm' import assert from 'assert' import { createNamespace, Namespace } from 'cls-hooked' +import cluster from 'cluster' import { Server } from 'http' import { AddressInfo } from 'net' import { loggerFactory } from '../logger' @@ -8,7 +10,7 @@ import { Bootstrap } from './Bootstrap' import { Config } from './Config' import { ServerConfigurator } from './configureServer' import { createServer } from './createServer' -import { TypeORMDatabase, TypeORMLogger } from '@rondo.dev/db-typeorm' + export interface ServerBootstrapParams { readonly config: Config @@ -79,7 +81,7 @@ export class ServerBootstrap implements Bootstrap { async listen( port: number | string | undefined = process.env.PORT || 3000, - hostname: string | undefined= process.env.BIND_HOST, + hostname: string | undefined = process.env.BIND_HOST, ) { const apiLogger = loggerFactory.getLogger('api') try { @@ -91,6 +93,36 @@ export class ServerBootstrap implements Bootstrap { } } + async startCluster( + workers: number, + port?: number | string, + hostname?: string, + ) { + const apiLogger = loggerFactory.getLogger('api') + + if (cluster.isMaster) { + apiLogger.info('Started master process %d, starting %d workers...', + process.pid, workers) + + // Fork workers. + for (let i = 0; i < workers; i++) { + cluster.fork({ + WORKER_ID: i + 1, + }) + } + + cluster.on('exit', (worker, code, signal) => { + console.log(`worker ${worker.process.pid} died`) + }) + } else { + await this.listen(port, hostname) + apiLogger.info( + 'Started worker %d (worker id %s)', process.pid, process.env.WORKER_ID) + } + } + + + protected async start( port: number | string | undefined = process.env.PORT, hostname?: string, diff --git a/packages/server/src/cli/run.ts b/packages/server/src/cli/run.ts index b292f1a..3b94402 100644 --- a/packages/server/src/cli/run.ts +++ b/packages/server/src/cli/run.ts @@ -1,8 +1,28 @@ -import { Bootstrap } from "../application"; -import { argparse, arg } from "@rondo.dev/argparse"; +import { arg, argparse } from '@rondo.dev/argparse' +import { cpus } from 'os' +import { Bootstrap } from '../application' + +const numberOfCPUs = cpus().length + +const startArgs = { + host: arg('string', { + description: '', + }), + socket: arg('number', { + alias: 's', + description: 'Socket to listen on', + }), + port: arg('number', { + default: 3000, + alias: 'p', + description: 'Port to listen on', + }), + help: arg('boolean', {alias: 'h'}), +} export function run(bootstrap: Bootstrap, argv: string[]) { - const choices: Array = ['start', 'migrate'] + const choices: Array = Object + .keys(commands) as Array const {parse} = argparse({ command: arg('string', { default: 'start', @@ -24,24 +44,23 @@ export function run(bootstrap: Bootstrap, argv: string[]) { const commands = { async start(bootstrap: Bootstrap, argv: string[]) { - const {parse} = argparse({ - host: arg('string', { - description: '', - }), - socket: arg('number', { - alias: 's', - description: 'Socket to listen on', - }), - port: arg('number', { - default: 3000, - alias: 'p', - description: 'Port to listen on', - }), - help: arg('boolean', {alias: 'h'}), - }) + const {parse} = argparse(startArgs, 'Start the server') const args = parse(argv) await bootstrap.listen(args.port || args.socket, args.host) }, + async cluster(bootstrap: Bootstrap, argv: string[]) { + const {parse} = argparse({ + ...startArgs, + workers: arg('number', { + alias: 'w', + description: 'Number of workers to start', + default: numberOfCPUs, + }), + }, 'Start in cluster') + const args = parse(argv) + await bootstrap + .startCluster(args.workers, args.port || args.socket, args.host) + }, async migrate(bootstrap: Bootstrap, argv: string[]) { const {parse} = argparse({ undo: arg('boolean', {