Problem
The MCP server watchdog correctly detects idle timeout and attempts to close stdin to wake up the main thread, but the process doesn't actually exit because the main thread is blocked on a blocking readLine() call.
Current Behavior
idleWatchdog thread detects idle timeout after 10 minutes (or custom timeout via --timeout)
- Watchdog calls
stdin.close() and sets shutdown = true
- Main thread is blocked in
mcpj.readLine() waiting for input
- Process remains running indefinitely
This was reported by @destroyer22719 in #243 - they observed the MCP process still running even after the timeout period.
Root Cause
The main MCP loop (src/mcp.zig:427-428):
while (true) {
const msg = mcpj.readLine(alloc, stdin) orelse break;
Uses blocking I/O. When stdin.close() is called from the watchdog thread, the blocking read doesn't get interrupted on all platforms/pipe configurations.
Potential Solutions
-
Non-blocking reads with timeout: Use poll() before reading to check if data is available, with a timeout that lets us periodically check the shutdown flag
-
Signal-based interruption: Have the watchdog send a signal (SIGUSR1) to the main thread to interrupt the read
-
Thread cancellation: More drastic - cancel the main thread from the watchdog (complex and risky)
-
Process self-termination: Instead of waiting for the main thread to exit, the watchdog could call std.process.exit() directly after logging the idle timeout
Workaround
Users can currently work around this by:
- Using
pkill codedb or similar to force kill the process
- Setting a longer timeout with
--timeout=N to reduce how often this happens
Related
Problem
The MCP server watchdog correctly detects idle timeout and attempts to close stdin to wake up the main thread, but the process doesn't actually exit because the main thread is blocked on a blocking
readLine()call.Current Behavior
idleWatchdogthread detects idle timeout after 10 minutes (or custom timeout via--timeout)stdin.close()and setsshutdown = truemcpj.readLine()waiting for inputThis was reported by @destroyer22719 in #243 - they observed the MCP process still running even after the timeout period.
Root Cause
The main MCP loop (
src/mcp.zig:427-428):Uses blocking I/O. When
stdin.close()is called from the watchdog thread, the blocking read doesn't get interrupted on all platforms/pipe configurations.Potential Solutions
Non-blocking reads with timeout: Use
poll()before reading to check if data is available, with a timeout that lets us periodically check the shutdown flagSignal-based interruption: Have the watchdog send a signal (SIGUSR1) to the main thread to interrupt the read
Thread cancellation: More drastic - cancel the main thread from the watchdog (complex and risky)
Process self-termination: Instead of waiting for the main thread to exit, the watchdog could call
std.process.exit()directly after logging the idle timeoutWorkaround
Users can currently work around this by:
pkill codedbor similar to force kill the process--timeout=Nto reduce how often this happensRelated