# Parent-Process Watchdog Mechanism in codebase-memory-mcp: Graceful Shutdown Explained

> Discover the parent-process watchdog mechanism in codebase-memory-mcp. Learn how it ensures graceful shutdown and prevents orphaned processes by monitoring the parent process.

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

---

**The codebase-memory-mcp server implements a parent-process watchdog that polls `getppid()` every 500 milliseconds to detect when its parent process exits, triggering immediate cleanup via `_exit(0)` to prevent orphaned processes.**

The `codebase-memory-mcp` repository provides a long-lived stdio-based JSON-RPC server for indexing and querying codebases. When the launching process—such as an editor or supervisor—terminates unexpectedly, the server would otherwise remain blocked on `stdin` indefinitely, leaking resources. To prevent this, the project implements a **parent-process watchdog** that monitors the parent PID and initiates a graceful shutdown as soon as the parent disappears.

## Why a Parent-Process Watchdog Is Necessary

As a stdio-based MCP (Model Context Protocol) server, `codebase-memory-mcp` relies on standard input for JSON-RPC messages. If the parent process dies while the server is blocked on a `getline` or `read` operation, the server process would persist as an orphan, consuming memory and file descriptors without a functional supervisor. This resource leak is particularly problematic in editor environments where extensions may restart or crash frequently.

The solution implements a POSIX-specific watchdog thread that compares the current parent process ID (`getppid()`) against the PID captured at startup, detecting orphaning when the process is re-parented to PID 1.

## Core Architecture of the Watchdog System

The graceful shutdown system consists of coordinated signal handling, a centralized shutdown routine, and a background watchdog thread defined in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c).

### Signal Handling and the Unified Shutdown Routine

External termination signals converge on a single cleanup path. In [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c) at lines 72–81, the application installs handlers for `SIGTERM` and `SIGINT` that invoke `request_shutdown()`:

```c
// src/main.c L72-L81
static void signal_handler(int sig) {
    (void)sig;
    request_shutdown();
}

```

The `request_shutdown()` function (lines 77–100) acts as the central coordinator for all shutdown scenarios. It atomically marks the global shutdown flag, cancels active pipelines, stops background services, and unblocks the main loop by closing `stdin`:

```c
static void request_shutdown(void) {
    if (atomic_exchange(&g_shutdown, 1)) return; /* already shutting down */

    if (g_server) {
        cbm_pipeline_t *p = cbm_mcp_server_active_pipeline(g_server);
        if (p) cbm_pipeline_cancel(p);
    }
    cbm_pipeline_unlock();

    if (g_watcher)   cbm_watcher_stop(g_watcher);
    if (g_http_server) cbm_http_server_stop(g_http_server);

    (void)fclose(stdin);   /* unblock the MCP read loop */
}

```

### The Background Watchdog Thread

The **parent-process watchdog** runs in a dedicated thread spawned early in `main()` at lines 699–709. Using the platform abstraction layer ([`foundation/compat_thread.h`](https://github.com/DeusData/codebase-memory-mcp/blob/main/foundation/compat_thread.h)), the thread is created with a minimal stack size (`PARENT_WATCHDOG_STACK_SIZE`):

```c
// Excerpt from src/main.c L699-L709
bool parent_watchdog_started = false;
if (cbm_thread_create(&parent_watchdog_tid,
                      PARENT_WATCHDOG_STACK_SIZE,
                      parent_watchdog_thread,
                      &initial_ppid) == 0) {
    parent_watchdog_started = true;
    cbm_log_info("parent.watchdog.start");
} else {
    cbm_log_warn("parent.watchdog.unavailable",
                 "reason", "thread_create_failed");
}

```

The watchdog thread implementation (lines 107–136) polls every 500 milliseconds via `cbm_usleep(500000)`. It checks if the parent PID has changed from the initial value captured at startup:

```c
static void *parent_watchdog_thread(void *arg) {
    pid_t initial_ppid = *(pid_t *)arg;
    const unsigned int poll_interval_us = 500000; /* 500 ms */

    while (!atomic_load(&g_shutdown)) {
        cbm_usleep(poll_interval_us);
        if (atomic_load(&g_shutdown)) break;
        if (initial_ppid > 1 && getppid() != initial_ppid) {
            static const char msg[] =
                "level=warn msg=parent.exited reason=ppid_changed\n";
            (void)write(STDERR_FILENO, msg, sizeof(msg) - 1);
            _exit(0);
        }
    }
    return NULL;
}

```

When the parent exits, the kernel re-parents the server to PID 1 (init). The watchdog detects this change and calls `_exit(0)` immediately, avoiding async-signal-unsafe cleanup that could deadlock.

### Worker Mode Integration

The same mechanism protects **index-worker subprocesses** (lines 59–70). When running with the `--index-worker` flag, the watchdog ensures worker processes also terminate if their supervising parent dies, preventing stale workers from accumulating during large indexing operations.

## Step-by-Step Detection Logic

The parent-process watchdog follows this deterministic sequence:

1. **Startup Capture**: `main()` records the initial parent PID using `initial_ppid = getppid();`.
2. **Thread Launch**: `cbm_thread_create()` spawns `parent_watchdog_thread` before the server begins accepting connections.
3. **Polling Loop**: The thread sleeps for 500ms, then checks `atomic_load(&g_shutdown)` to see if a normal shutdown is already in progress.
4. **PPID Validation**: If `initial_ppid > 1` and `getppid()` no longer matches the initial value, the watchdog detects orphaning.
5. **Immediate Termination**: The thread writes a warning to `stderr` and invokes `_exit(0)`, bypassing standard library cleanup to ensure the process exits even if signal handlers are unsafe.

## Platform Support and Limitations

The parent-process watchdog is implemented only for POSIX platforms (`#ifndef _WIN32`). Windows builds rely on job-object termination semantics, which provide equivalent functionality through native OS mechanisms. The POSIX implementation specifically requires `getppid()` and `_exit()`, available on Linux, macOS, and other Unix-like systems.

## Summary

- **Resource Safety**: The watchdog prevents orphaned server processes that would otherwise persist indefinitely blocked on `stdin`.
- **Polling Mechanism**: It checks `getppid()` every 500ms against the startup PID to detect when the parent process exits.
- **Dual Path Shutdown**: Both signal handlers (`SIGTERM`/`SIGINT`) and the watchdog converge on `request_shutdown()` for consistent cleanup.
- **Safe Termination**: Uses `_exit(0)` instead of `exit()` to avoid deadlocks in async-signal-unsafe states.
- **Cross-Platform**: Implemented for POSIX only; Windows uses alternative job-object mechanisms.

## Frequently Asked Questions

### How does the watchdog detect parent process death?

The watchdog stores the initial parent PID at startup and compares it with the current `getppid()` every 500 milliseconds. When the original parent exits, the operating system re-parents the process to PID 1 (init), causing the comparison to fail and triggering `_exit(0)`.

### What is the difference between `request_shutdown()` and `_exit()`?

`request_shutdown()` is the graceful path used during normal shutdowns (signals or manual requests) that cancels pipelines, stops services, and closes `stdin`. `_exit()` is used by the watchdog only when detecting parent death, as it terminates immediately without invoking cleanup routines that could deadlock in an async-signal-unsafe context.

### Why does the watchdog use a 500ms polling interval?

The 500ms interval (500,000 microseconds) balances responsiveness with CPU efficiency. It detects parent death quickly enough to prevent resource leaks while minimizing overhead from the `getppid()` system call and sleep operations.

### Is the parent-process watchdog available on Windows?

No. The watchdog is conditional on `#ifndef _WIN32` because Windows provides job-object termination semantics that automatically kill child processes when the parent dies. The POSIX-specific `getppid()` and `_exit()` mechanisms are not required on Windows.