Skip to content

Commit

Permalink
docs(examples/supply-chain-app): fix resource cleanup of shutdown logic
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
petermetz committed Jul 2, 2024
1 parent bf92d3d commit eaba573
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ export interface ISupplyChainAppDummyInfrastructureOptions {
keychain?: IPluginKeychain;
}

function ledgerStopFailErrorMsg(ledgerName: Readonly<string>): 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.
Expand Down Expand Up @@ -131,12 +140,21 @@ export class SupplyChainAppDummyInfrastructure {
public async stop(): Promise<void> {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -441,8 +454,12 @@ export class SupplyChainApp {
}

public async stop(): Promise<void> {
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);
}
}

Expand Down Expand Up @@ -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();

Expand Down

0 comments on commit eaba573

Please sign in to comment.