From 8c1cfcc6ee4551e29e7466f4c3112b534c51fa6a Mon Sep 17 00:00:00 2001 From: Paul Rumkin Date: Wed, 4 Nov 2020 01:18:48 +0300 Subject: [PATCH 1/3] Add force shutdown --- bin/cli.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/cli.ts b/bin/cli.ts index a47cad8..8d0d611 100755 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -156,7 +156,12 @@ async function run() { const node = await runNode(options) const server = args.rpc ? runRpcServer(node, options) : null - process.on('SIGINT', async () => { + process.once('SIGINT', async () => { + process.once('SIGINT', () => { + logger.info('Force shutdown. Exit immediately.') + process.exit(1) + }) + logger.info('Caught interrupt signal. Shutting down...') if (server) server.http().close() await node.stop() From 45244bc7f894778e77c53aecf164151465ddc9b2 Mon Sep 17 00:00:00 2001 From: Paul Rumkin Date: Wed, 4 Nov 2020 02:45:09 +0300 Subject: [PATCH 2/3] Add client config file and custom logger support --- bin/cli.ts | 38 +++++++++++++--- test/cli/cli.spec.ts | 62 ++++++++++++++++++++++++++ test/cli/fixtures/ethereumjs.config.js | 12 +++++ 3 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 test/cli/cli.spec.ts create mode 100644 test/cli/fixtures/ethereumjs.config.js diff --git a/bin/cli.ts b/bin/cli.ts index 8d0d611..1d6100c 100755 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -7,15 +7,25 @@ const { fromName: serverFromName } = require('../lib/net/server') import Node from '../lib/node' import { Server as RPCServer } from 'jayson' import { Config } from '../lib/config' +import {Logger} from 'winston' const RPCManager = require('../lib/rpc') const level = require('level') const os = require('os') const path = require('path') const fs = require('fs-extra') +const yargs = require('yargs') + +type UserConfig = { + logger? : Logger +} const networks = Object.entries(chains.names) -const args = require('yargs') +const args = yargs .options({ + config: { + describe: 'Path to ethereumjs.config.js', + default: undefined, + }, network: { describe: `Network`, choices: networks.map((n) => n[1]), @@ -80,7 +90,18 @@ const args = require('yargs') }, }) .locale('en_EN').argv -const logger = getLogger({ loglevel: args.loglevel }) + +const config: UserConfig = {} + +if (args.config) { + const userConfig: UserConfig = require( + path.resolve(process.cwd(), args.config) + ) + + Object.assign(config, userConfig) +} + +const logger = config.logger ?? getLogger({ loglevel: args.loglevel }) async function runNode(options: any) { logger.info('Initializing Ethereumjs client...') @@ -153,21 +174,24 @@ async function run() { minPeers: args.minPeers, maxPeers: args.maxPeers, } - const node = await runNode(options) - const server = args.rpc ? runRpcServer(node, options) : null + + let node: Node|null + let server: RPCServer|null process.once('SIGINT', async () => { process.once('SIGINT', () => { logger.info('Force shutdown. Exit immediately.') process.exit(1) }) - logger.info('Caught interrupt signal. Shutting down...') if (server) server.http().close() - await node.stop() + if (node) await node.stop() logger.info('Exiting.') - process.exit() + process.exit(0) }) + + node = await runNode(options) + server = args.rpc ? runRpcServer(node, options) : null } run().catch((err) => logger.error(err)) diff --git a/test/cli/cli.spec.ts b/test/cli/cli.spec.ts new file mode 100644 index 0000000..9786984 --- /dev/null +++ b/test/cli/cli.spec.ts @@ -0,0 +1,62 @@ +import path from 'path' +import { spawn } from 'child_process' + +import tape from 'tape' + +tape('[CLI]', (t) => { + t.test('should handle SIGINT', { timeout: 160000 }, (t) => { + t.plan(1) + const file = require.resolve('../../dist/bin/cli.js') + const child = spawn(process.execPath, [ + file, + '--config', + path.join(__dirname, '/fixtures/ethereumjs.config.js') + ], { + stdio: ['pipe', 'pipe', 'pipe', 'ipc'], + }) + + const timeout = setTimeout(() => { + child.kill('SIGINT') + }, 120000) + + const end = () => { + clearTimeout(timeout) + child.kill('SIGINT') + t.end() + } + + function onServiceStarted([level, message]: [string, string]) { + if (level === 'info') { + if (message === 'Started eth service.') { + child.removeListener('message', onServiceStarted) + child.on('message', onFirstSigintSent) + child.kill('SIGINT') + } + } + } + + function onFirstSigintSent([level, message]: [string, string]) { + if (level === 'info') { + if (message === 'Exiting.') { + child.removeListener('message', onFirstSigintSent) + t.pass('Client exited') + clearTimeout(timeout) + } + } + } + + child.on('message', onServiceStarted) + child.on('message', ([level, message]: [string, string]) => console.log(level, message)) + + child.on('error', (error) => { + t.fail(`Error: ${error}`) + }) + + child.on('close', (code, signal) => { + if (code !== 0) { + t.fail(`child process exited with code ${code}, signal ${signal}`) + end() + } + }) + }) +}) diff --git a/test/cli/fixtures/ethereumjs.config.js b/test/cli/fixtures/ethereumjs.config.js new file mode 100644 index 0000000..b3b99d8 --- /dev/null +++ b/test/cli/fixtures/ethereumjs.config.js @@ -0,0 +1,12 @@ +function send(level, message) { + process.send([level, message]) +} + +module.exports = { + logger: { + warn: send.bind(null, 'warn'), + info: send.bind(null, 'info'), + debug: send.bind(null, 'debug'), + error: send.bind(null, 'error'), + } +} From 6e41def8ada5b37c1391cb318a098849bdee5b6f Mon Sep 17 00:00:00 2001 From: Paul Rumkin Date: Wed, 4 Nov 2020 03:16:55 +0300 Subject: [PATCH 3/3] Fix lint errors --- bin/cli.ts | 22 +++++++++------- test/cli/cli.spec.ts | 36 +++++++++++++------------- test/cli/fixtures/ethereumjs.config.js | 2 +- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/bin/cli.ts b/bin/cli.ts index 1d6100c..75a638b 100755 --- a/bin/cli.ts +++ b/bin/cli.ts @@ -7,7 +7,7 @@ const { fromName: serverFromName } = require('../lib/net/server') import Node from '../lib/node' import { Server as RPCServer } from 'jayson' import { Config } from '../lib/config' -import {Logger} from 'winston' +import { Logger } from 'winston' const RPCManager = require('../lib/rpc') const level = require('level') const os = require('os') @@ -16,7 +16,7 @@ const fs = require('fs-extra') const yargs = require('yargs') type UserConfig = { - logger? : Logger + logger?: Logger } const networks = Object.entries(chains.names) @@ -94,9 +94,7 @@ const args = yargs const config: UserConfig = {} if (args.config) { - const userConfig: UserConfig = require( - path.resolve(process.cwd(), args.config) - ) + const userConfig: UserConfig = require(path.resolve(process.cwd(), args.config)) Object.assign(config, userConfig) } @@ -175,8 +173,8 @@ async function run() { maxPeers: args.maxPeers, } - let node: Node|null - let server: RPCServer|null + let node: Node | null + let server: RPCServer | null process.once('SIGINT', async () => { process.once('SIGINT', () => { @@ -184,8 +182,14 @@ async function run() { process.exit(1) }) logger.info('Caught interrupt signal. Shutting down...') - if (server) server.http().close() - if (node) await node.stop() + if (server) { + server.http().close() + server = null + } + if (node) { + await node.stop() + node = null + } logger.info('Exiting.') process.exit(0) }) diff --git a/test/cli/cli.spec.ts b/test/cli/cli.spec.ts index 9786984..5fd330a 100644 --- a/test/cli/cli.spec.ts +++ b/test/cli/cli.spec.ts @@ -7,13 +7,11 @@ tape('[CLI]', (t) => { t.test('should handle SIGINT', { timeout: 160000 }, (t) => { t.plan(1) const file = require.resolve('../../dist/bin/cli.js') - const child = spawn(process.execPath, [ - file, - '--config', - path.join(__dirname, '/fixtures/ethereumjs.config.js') - ], { - stdio: ['pipe', 'pipe', 'pipe', 'ipc'], - }) + const child = spawn( + process.execPath, + [file, '--config', path.join(__dirname, '/fixtures/ethereumjs.config.js')], + { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] } + ) const timeout = setTimeout(() => { child.kill('SIGINT') @@ -25,28 +23,30 @@ tape('[CLI]', (t) => { t.end() } - function onServiceStarted([level, message]: [string, string]) { + function onSigintSent([level, message]: [string, string]) { if (level === 'info') { - if (message === 'Started eth service.') { - child.removeListener('message', onServiceStarted) - child.on('message', onFirstSigintSent) - child.kill('SIGINT') + if (message === 'Exiting.') { + child.removeListener('message', onSigintSent) + t.pass('Client exited') + clearTimeout(timeout) } } } - function onFirstSigintSent([level, message]: [string, string]) { + function onServiceStarted([level, message]: [string, string]) { if (level === 'info') { - if (message === 'Exiting.') { - child.removeListener('message', onFirstSigintSent) - t.pass('Client exited') - clearTimeout(timeout) + if (message === 'Started eth service.') { + child.removeListener('message', onServiceStarted) + child.on('message', onSigintSent) + child.kill('SIGINT') } } } child.on('message', onServiceStarted) - child.on('message', ([level, message]: [string, string]) => console.log(level, message)) + child.on('message', ([level, message]: [string, string]) => { + process.stdout.write(`${level}: ${message}\n`) + }) child.on('error', (error) => { t.fail(`Error: ${error}`) diff --git a/test/cli/fixtures/ethereumjs.config.js b/test/cli/fixtures/ethereumjs.config.js index b3b99d8..8c39b6a 100644 --- a/test/cli/fixtures/ethereumjs.config.js +++ b/test/cli/fixtures/ethereumjs.config.js @@ -8,5 +8,5 @@ module.exports = { info: send.bind(null, 'info'), debug: send.bind(null, 'debug'), error: send.bind(null, 'error'), - } + }, }