# How Signal Handling Achieves Graceful Shutdown on SIGTERM and SIGINT in codebase-memory-mcp

> Learn how DeusData/codebase-memory-mcp achieves graceful shutdown using POSIX and Windows signal handlers for SIGTERM and SIGINT. Discover atomic flags and async-signal-safe operations.

- Repository: [Martin Vogel/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)
- Tags: internals
- Published: 2026-07-10

---

**The MCP server installs POSIX and Windows signal handlers that convert SIGTERM and SIGINT into a coordinated, idempotent shutdown sequence using atomic flags and async-signal-safe operations.**

The `DeusData/codebase-memory-mcp` repository implements robust process lifecycle management through sophisticated signal handling. When an operator or orchestrator sends a termination signal, the server transforms an abrupt interruption into an orderly teardown of pipelines, background threads, and I/O streams. Understanding this signal handling mechanism reveals how the codebase achieves reliable graceful shutdown on SIGTERM and SIGINT without data loss or resource leaks.

## Installing Signal Handlers at Startup

During initialization in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c), the `setup_signal_handlers()` function registers handlers for both SIGTERM and SIGINT at lines 95-106.

On **Unix** systems, the function prepares a `sigaction` structure to ensure reliable signal delivery and avoid signal handler races. On **Windows** builds, it falls back to the standard `signal()` API, which provides adequate handling for console-process termination.

```c
/* src/main.c (simplified) */
void setup_signal_handlers(void) {
#ifdef _WIN32
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);
#else
    struct sigaction sa = {0};
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);
#endif
}

```

## Converting Signals to Shutdown Requests

When the operating system delivers either SIGTERM or SIGINT, execution jumps to the static `signal_handler(int sig)` function defined at lines 102-105 in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c). This handler immediately delegates to `request_shutdown()`, intentionally discarding the specific signal number to treat both termination requests identically.

```c
/* src/main.c lines 102-105 */
static void signal_handler(int sig) {
    (void)sig;  /* Signal number ignored */
    request_shutdown();
}

```

This delegation pattern keeps the signal handler itself minimal and compliant with async-signal-safe constraints, deferring complex work to the main execution context.

## Idempotent Graceful Shutdown Logic

The core shutdown logic resides in `request_shutdown()`, implemented at lines 72-88 in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c). This function uses **atomic flag guards** to guarantee the shutdown sequence executes exactly once, even if both SIGTERM and SIGINT arrive simultaneously or in rapid succession.

The function performs the following **async-signal-safe** operations:

```c
static void request_shutdown(void) {
    /* Atomic guard: returns true if already shutting down */
    if (atomic_exchange(&g_shutdown, 1)) return;
    
    /* Cancel any active processing pipeline */
    cbm_pipeline_cancel();
    
    /* Stop background file-watcher thread */
    cbm_watcher_stop();
    
    /* Stop optional HTTP UI server */
    cbm_http_server_stop();
    
    /* Close stdin to unblock main loop's getline() */
    fclose(stdin);
}

```

Key implementation details include:

- **Atomic exchange**: `atomic_exchange(&g_shutdown, 1)` ensures thread-safe, single execution without mutexes or locks, satisfying POSIX async-signal-safety requirements.
- **Pipeline cancellation**: `cbm_pipeline_cancel` aborts active data processing gracefully.
- **Thread coordination**: `cbm_watcher_stop` and `cbm_http_server_stop` signal background threads (watcher and HTTP server) to exit cleanly before the main thread joins them.

## Unblocking the MCP Read Loop

A critical aspect of the graceful shutdown involves **closing `stdin`**. The MCP server's main loop blocks on `getline()` to read messages from standard input. By closing the file descriptor, `getline()` returns EOF immediately, allowing `cbm_mcp_server_run` to return control to `main` instead of hanging indefinitely.

After the server function returns, the program proceeds with the final teardown sequence at lines 200-210 in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c):

```c
/* Main shutdown sequence */
int main(void) {
    /* ... initialization ... */
    cbm_mcp_server_run();
    
    /* Signal watchdog thread to complete */
    atomic_store(&g_shutdown, 1);
    
    /* Join threads and release global resources */
    /* ... */
    return 0;
}

```

## Practical Implementation Examples

### Adding Custom Cleanup Steps

To extend the shutdown sequence with custom resource cleanup, modify `request_shutdown()` in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c):

```c
static void request_shutdown(void) {
    if (atomic_exchange(&g_shutdown, 1)) return;
    
    /* Existing cleanup */
    cbm_pipeline_cancel();
    cbm_watcher_stop();
    cbm_http_server_stop();
    fclose(stdin);
    
    /* Your custom cleanup */
    cbm_custom_resource_release();
    cbm_logger_flush();
}

```

### Registering SIGUSR1 for Hot-Reload

To add configuration reload capabilities alongside graceful shutdown:

```c
static void reload_handler(int sig) {
    (void)sig;
    cbm_reload_configuration();
}

void setup_signal_handlers(void) {
    /* Existing SIGTERM/SIGINT setup ... */
    
#ifndef _WIN32
    struct sigaction ra = {0};
    ra.sa_handler = reload_handler;
    sigemptyset(&ra.sa_mask);
    sigaction(SIGUSR1, &ra, NULL);
#endif
}

```

**Note**: Windows does not support SIGUSR1, so this requires platform guards.

### Testing Graceful Shutdown from Command Line

Verify the signal handling behavior manually:

```bash
$ ./codebase-memory-mcp &        # Start server in background

$ kill -TERM $!                  # Send SIGTERM

```

The server logs the shutdown sequence and exits after completing any in-progress pipeline operations.

## Summary

- **Atomic guard**: `atomic_exchange(&g_shutdown, 1)` prevents double-execution of shutdown logic when multiple signals arrive.
- **Async-signal-safety**: The handler only manipulates atomic variables and calls functions that set atomics, respecting POSIX restrictions.
- **I/O unblocking**: Closing `stdin` forces the blocking `getline()` call to return EOF, preventing indefinite hangs.
- **Thread-aware cleanup**: Background threads (file watcher, HTTP UI) receive stop signals before the main thread joins them, preventing resource leaks.

## Frequently Asked Questions

### Why use `atomic_exchange` instead of a simple boolean flag?

`atomic_exchange(&g_shutdown, 1)` ensures the shutdown sequence runs exactly once even if SIGTERM and SIGINT arrive concurrently. A non-atomic boolean could trigger race conditions where the shutdown logic executes twice, potentially causing double-free errors or inconsistent state during resource cleanup.

### Is the shutdown handler safe to call from within a signal handler context?

Yes, because `request_shutdown()` only performs async-signal-safe operations: atomic variable stores and function calls that themselves only manipulate atomics or set simple stop flags. It avoids unsafe operations like malloc, printf, or mutex locks, which are forbidden inside POSIX signal handlers.

### How does the server prevent hanging on input during shutdown?

The shutdown sequence explicitly closes `stdin` via `fclose(stdin)`. This causes the `getline()` call in the MCP server's read loop to return EOF immediately, allowing the main event loop to exit cleanly rather than blocking indefinitely on I/O.

### What happens to active pipelines when a shutdown signal arrives?

The handler invokes `cbm_pipeline_cancel()` (defined in [`src/pipeline/pipeline.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/pipeline/pipeline.c)) to abort any running pipeline before proceeding with thread termination. This ensures in-flight operations receive termination signals and can release partial resources or temporary files before the process exits.