From d6de982119fd0aa6592fa75c4b8cd103fa74716a Mon Sep 17 00:00:00 2001 From: Peter Somogyvari Date: Wed, 26 Jun 2024 19:34:26 -0700 Subject: [PATCH] docs(examples/supply-chain-app): fix resource cleanup of shutdown logic 1. Now you can run the supply chain app example locally and then hit CTRL+C on the terminal and it will gracefully shut down all the containers hosting the infrastructure and only after that the NodeJS process itself will exit. 2. Previously we had a bug where it wouldn't wait for the containers to wind down and it left them running which was causing problems when people didn't notice this behavior and their machines would get into this broken state where the previous execution's containers were hanging around and one of them blocking ports too. Signed-off-by: Peter Somogyvari --- .../supply-chain-app-dummy-infrastructure.ts | 28 ++++++++++--- .../main/typescript/supply-chain-app-cli.ts | 2 +- .../src/main/typescript/supply-chain-app.ts | 39 ++++++++++++++++--- 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts b/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts index f39c4d298b..f08fc7fc56 100644 --- a/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts +++ b/examples/cactus-example-supply-chain-backend/src/main/typescript/infrastructure/supply-chain-app-dummy-infrastructure.ts @@ -45,6 +45,15 @@ export interface ISupplyChainAppDummyInfrastructureOptions { keychain?: IPluginKeychain; } +function ledgerStopFailErrorMsg(ledgerName: Readonly): string { + return ( + `Failed to stop ${ledgerName} ledger. This is most likely safe to ignore if the ` + + `error states that the container was not running to begin with. This usually means` + + `that the process exited before the application boot has finished and it did not` + + `have enough time to start launching the ${ledgerName} ledger yet.` + ); +} + /** * Contains code that is meant to simulate parts of a production grade deployment * that would otherwise not be part of the application itself. @@ -131,12 +140,21 @@ export class SupplyChainAppDummyInfrastructure { public async stop(): Promise { try { this.log.info(`Stopping...`); - await Promise.all([ - this.besu.stop().then(() => this.besu.destroy()), - this.quorum.stop().then(() => this.quorum.destroy()), - this.fabric.stop().then(() => this.fabric.destroy()), + await Promise.allSettled([ + this.besu + .stop() + .then(() => this.besu.destroy()) + .catch((ex) => this.log.warn(ledgerStopFailErrorMsg("Besu"), ex)), + this.quorum + .stop() + .then(() => this.quorum.destroy()) + .catch((ex) => this.log.warn(ledgerStopFailErrorMsg("Quorum"), ex)), + this.fabric + .stop() + .then(() => this.fabric.destroy()) + .catch((ex) => this.log.warn(ledgerStopFailErrorMsg("Fabric"), ex)), ]); - this.log.info(`Stopped OK`); + this.log.info(`Ledgers of dummy infrastructure Stopped OK`); } catch (ex) { this.log.error(`Stopping crashed: `, ex); throw ex; diff --git a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app-cli.ts b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app-cli.ts index 79f1e43a3a..d38d55f29f 100755 --- a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app-cli.ts +++ b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app-cli.ts @@ -21,7 +21,7 @@ export async function launchApp( await supplyChainApp.start(); } catch (ex) { console.error(`SupplyChainApp crashed. Existing...`, ex); - await supplyChainApp?.stop(); + await supplyChainApp.stop(); process.exit(-1); } } diff --git a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts index dd45b099e9..21e4d8a2ca 100644 --- a/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts +++ b/examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts @@ -191,15 +191,28 @@ export class SupplyChainApp { this.log.debug(`Starting SupplyChainApp...`); if (!this.options.disableSignalHandlers) { - exitHook((callback: IAsyncExitHookDoneCallback) => { - console.log(`Executing Registered signal handler to stop container.`); - this.stop().then(callback); + exitHook((onHookDone: IAsyncExitHookDoneCallback) => { + this.log.info("Starting async-exit-hook for supply-chain-app ..."); + this.stop() + .catch((ex: unknown) => { + this.log.warn("Failed async-exit-hook for supply-chain-app", ex); + throw ex; + }) + .finally(() => { + this.log.info("Concluded async-exit-hook for supply-chain-app ..."); + onHookDone(); + }); + this.log.info("Started async-exit-hook for supply-chain-app OK"); }); - this.log.debug(`Registered signal handlers for graceful auto-shutdown`); + this.log.info("Registered async-exit-hook for supply-chain-app shutdown"); } + this.onShutdown(async () => { + this.log.info("SupplyChainApp onShutdown() - stopping ledgers..."); + await this.ledgers.stop(); + this.log.info("SupplyChainApp onShutdown() - stopped ledgers OK"); + }); await this.ledgers.start(); - this.onShutdown(() => this.ledgers.stop()); const contractsInfo = await this.ledgers.deployContracts(); @@ -441,8 +454,12 @@ export class SupplyChainApp { } public async stop(): Promise { + let i = 0; for (const hook of this.shutdownHooks) { + i++; + this.log.info("Executing exit hook #%d...", i); await hook(); // FIXME add timeout here so that shutdown does not hang + this.log.info("Executed exit hook #%d OK", i); } } @@ -590,15 +607,25 @@ export class SupplyChainApp { properties.authorizationConfigJson = await this.getOrCreateAuthorizationConfig(); properties.crpcPort = 0; + // We must disable the API server's own shutdown hooks because if we didn't + // it would clash with the supply chain app's own shutdown hooks and the + // async functions wouldn't be waited for their conclusion leaving the containers + // running after the supply chain app NodeJS process has exited. + properties.enableShutdownHook = false; const apiServer = new ApiServer({ config: properties, httpServerApi, httpServerCockpit, pluginRegistry, + enableShutdownHook: false, }); - this.onShutdown(() => apiServer.shutdown()); + this.onShutdown(async () => { + this.log.info("SupplyChainApp onShutdown() - stopping API server"); + await apiServer.shutdown(); + this.log.info("SupplyChainApp onShutdown() - stopped API server OK"); + }); await apiServer.start();