Idempotent shutdown: cancels the active pipeline, stops background servers, * and closes stdin to unblock the MCP read loop. Invoked from the signal * handler and from the parent-death watchdog, hence the atomic_exchange guard * so the body runs at most once. Body is async-signal-safe (only atomic stores * and stop calls that themselves only set atomics). */
| 75 | * so the body runs at most once. Body is async-signal-safe (only atomic stores |
| 76 | * and stop calls that themselves only set atomics). */ |
| 77 | static void request_shutdown(void) { |
| 78 | if (atomic_exchange(&g_shutdown, 1)) { |
| 79 | return; /* already shutting down */ |
| 80 | } |
| 81 | |
| 82 | /* Cancel any in-progress pipeline (async-signal-safe: only does atomic_store) */ |
| 83 | if (g_server) { |
| 84 | cbm_pipeline_t *p = cbm_mcp_server_active_pipeline(g_server); |
| 85 | if (p) { |
| 86 | cbm_pipeline_cancel(p); |
| 87 | } |
| 88 | } |
| 89 | /* Release pipeline lock to prevent stale lock on restart */ |
| 90 | cbm_pipeline_unlock(); |
| 91 | |
| 92 | if (g_watcher) { |
| 93 | cbm_watcher_stop(g_watcher); |
| 94 | } |
| 95 | if (g_http_server) { |
| 96 | cbm_http_server_stop(g_http_server); |
| 97 | } |
| 98 | /* Close stdin to unblock getline in the MCP server loop */ |
| 99 | (void)fclose(stdin); |
| 100 | } |
| 101 | |
| 102 | static void signal_handler(int sig) { |
| 103 | (void)sig; |
no test coverage detected