# How OpenWA Handles Graceful Shutdown of Active Session Engines

> Learn how OpenWA gracefully shuts down active session engines. Discover the coordinated shutdown sequence ensuring clean WebSocket connection termination and resource release before process exit.

- Repository: [Yudhi Armyndharis/OpenWA](https://github.com/rmyndharis/OpenWA)
- Tags: internals
- Published: 2026-05-21

---

**OpenWA implements a coordinated shutdown sequence where the `ShutdownService` orchestrates NestJS application closure, triggering the `SessionService`'s `onModuleDestroy` hook to iteratively await each engine's `destroy()` method, ensuring all WhatsApp WebSocket connections terminate cleanly and resources release before the process exits.**

The OpenWA framework manages long-lived WhatsApp session engines that maintain persistent WebSocket connections to WhatsApp Web. When the application receives a restart or termination signal, it must gracefully dismantle these active sessions to prevent message loss, connection leaks, and orphaned processes. This article examines how the graceful shutdown process active session engines in OpenWA use ensures clean resource disposal, tracing the flow from HTTP triggers to engine destruction across the NestJS application lifecycle.

## Shutdown Orchestration Architecture

The graceful shutdown architecture centers on a dedicated service that coordinates the timing and execution of application termination.

### The ShutdownService Central Coordinator

The `ShutdownService` acts as the single source of truth for shutdown timing. Located at [`src/common/services/shutdown.service.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/common/services/shutdown.service.ts), this injectable provider maintains a private callback reference that ultimately closes the Nest application. When invoked, the `shutdown(delay: number)` method schedules the stored callback using a JavaScript timer and, upon execution, terminates the Node process with `process.exit(0)`.

This design decouples the shutdown trigger from the shutdown execution, allowing any part of the application to request termination without needing direct access to the Nest application instance.

### Bootstrap Registration in main.ts

The connection between the shutdown service and the application lifecycle is established during bootstrap in [`src/main.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/main.ts). After creating the Nest application, the code retrieves the `ShutdownService` and registers a callback that simply closes the Nest container:

```typescript
const shutdownService = app.get(ShutdownService);
shutdownService.setShutdownCallback(async () => {
  await app.close();
});

```

This registration allows the service to trigger `app.close()` asynchronously, which in turn begins the NestJS module destruction sequence.

## Triggering the Graceful Shutdown Sequence

Shutdown can be initiated programmatically or through exposed HTTP endpoints, depending on operational requirements.

### HTTP Endpoint Initiation

The most common trigger in production environments is the Infrastructure controller at [`src/modules/infra/infra.controller.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/modules/infra/infra.controller.ts). This controller exposes a restart endpoint that orchestrates Docker container operations and then schedules the application shutdown:

```typescript
@Post('restart')
@ApiOperation({ summary: 'Restart the server' })
async restart(@Body() dto: RestartDto) {
  // Docker orchestration logic...
  void this.shutdownService.shutdown(3000);
  return { message: 'Server is restarting', restarting: true };
}

```

The call `this.shutdownService.shutdown(3000)` schedules the shutdown callback to execute after a **3-second delay**, giving the HTTP response time to reach the client before the server begins closing connections.

### Programmatic Invocation

For administrative scripts or external monitors, the shutdown service can be accessed directly from the Nest application context:

```typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ShutdownService } from './common/services/shutdown.service';

async function stopServer() {
  const app = await NestFactory.create(AppModule);
  const shutdown = app.get(ShutdownService);
  shutdown.shutdown(2000);
}
stopServer();

```

This pattern allows custom automation tools to specify custom delays, such as the **2-second timer** shown above, based on environment-specific requirements.

## Terminating Active Session Engines

When `app.close()` executes, NestJS automatically invokes the `onModuleDestroy` lifecycle hook on providers bound to active modules. This mechanism drives the engine termination sequence.

### SessionService Lifecycle Hook Implementation

The `SessionService` at [`src/modules/session/session.service.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/modules/session/session.service.ts) implements `OnModuleDestroy` to handle bulk engine termination. Inside `onModuleDestroy()`, the service iterates over its internal `engines` Map (typed as `Map<string, IWhatsAppEngine>`) and gracefully destroys each instance:

```typescript
async onModuleDestroy(): Promise<void> {
  for (const [sessionId, engine] of this.engines) {
    this.logger.log(`Destroying engine for session ${sessionId}`, {
      sessionId,
      action: 'shutdown',
    });
    await engine.destroy();
  }
  this.engines.clear();

  for (const [, state] of this.reconnectStates) {
    if (state.timer) clearTimeout(state.timer);
  }
  this.reconnectStates.clear();
}

```

The method performs three critical operations: it awaits each engine's `destroy()` promise to ensure asynchronous cleanup completes, clears the engines Map to release object references, and cancels any pending reconnection timers stored in `reconnectStates` to prevent memory leaks or post-shutdown connection attempts.

### Engine-Specific Resource Cleanup

Each concrete engine adapter implements the `destroy(): Promise<void>` contract. For example, the WhatsApp Web.js adapter at [`src/engine/adapters/whatsapp-web-js.adapter.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/engine/adapters/whatsapp-web-js.adapter.ts) performs client-specific teardown:

- Stops the underlying `WhatsAppWebJs` client
- Removes event listeners established during initialization
- Resolves the promise only after the WebSocket connection has fully disconnected

This ensures that by the time the `await engine.destroy()` call returns in `SessionService`, the specific WhatsApp session has cleanly severed its connection to WhatsApp servers and released all file handles or encryption keys.

## Summary

- **ShutdownService** schedules shutdown with a configurable delay and executes the final `process.exit(0)` after the Nest application closes.
- **SessionService** implements `onModuleDestroy()` to iterate all active engines in the internal Map and await their `destroy()` methods sequentially.
- **Engine adapters** terminate their underlying WhatsApp WebSocket clients, remove listeners, and resolve only after complete disconnection.
- **Reconnection timers** are explicitly cleared from `reconnectStates` to prevent zombie asynchronous operations during shutdown.
- The process exits with code **0** only after all engines release resources and the Nest container finishes its destruction routine.

## Frequently Asked Questions

### What triggers the graceful shutdown in OpenWA?

The most common trigger is the Infrastructure controller ([`src/modules/infra/infra.controller.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/modules/infra/infra.controller.ts)), which exposes a POST endpoint for server restarts. After processing Docker orchestration logic, the controller calls `this.shutdownService.shutdown(3000)`, scheduling the shutdown with a 3-second delay. The process can also be triggered programmatically by injecting the `ShutdownService` into custom scripts or administrative modules.

### How does OpenWA ensure active WhatsApp sessions terminate cleanly?

The framework leverages NestJS's `onModuleDestroy` lifecycle hook implemented in `SessionService`. This hook iterates through the internal `engines` Map and awaits each engine's `destroy()` method, giving adapters time to close WebSocket connections, remove event listeners, and clear cryptographic resources before the process exits. The sequential await pattern prevents premature process termination while connections remain open.

### What is the default shutdown delay, and can it be modified?

The default delay is **3000 milliseconds** (3 seconds), as specified in the infra controller's call to `shutdown(3000)`. This value is fully configurable when invoking the method directly, allowing operators to specify longer delays for complex cleanup operations or shorter delays for rapid restarts in development environments.

### Where is the final process exit handled?

The actual `process.exit(0)` call resides in [`src/common/services/shutdown.service.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/common/services/shutdown.service.ts) within the `ShutdownService`. This centralization ensures the Node process terminates only after the Nest application has fully closed via `app.close()` and all registered shutdown callbacks—including the `onModuleDestroy` hooks that clean up session engines—have completed execution.