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();