| 6297 | /* ── Event loop ───────────────────────────────────────────────── */ |
| 6298 | |
| 6299 | int cbm_mcp_server_run(cbm_mcp_server_t *srv, FILE *in, FILE *out) { |
| 6300 | char *line = NULL; |
| 6301 | size_t cap = 0; |
| 6302 | int fd = cbm_fileno(in); |
| 6303 | |
| 6304 | for (;;) { |
| 6305 | /* Poll with idle timeout so we can evict unused stores between requests. |
| 6306 | * |
| 6307 | * IMPORTANT: poll() operates on the raw fd, but getline() reads from a |
| 6308 | * buffered FILE*. When a client sends multiple messages in rapid |
| 6309 | * succession, the first getline() call may drain ALL kernel data into |
| 6310 | * libc's internal FILE* buffer. Subsequent poll() calls then see an |
| 6311 | * empty kernel fd and block for STORE_IDLE_TIMEOUT_S seconds even |
| 6312 | * though the next messages are already in the FILE* buffer. |
| 6313 | * |
| 6314 | * Fix (Unix): use a three-phase approach — |
| 6315 | * Phase 1: non-blocking poll (timeout=0) to check the kernel fd. |
| 6316 | * Phase 2: if Phase 1 returns 0, peek the FILE* buffer via fgetc/ |
| 6317 | * ungetc to detect data buffered by a prior getline() call. |
| 6318 | * The fd is temporarily set O_NONBLOCK so fgetc() returns |
| 6319 | * immediately (EAGAIN → EOF + ferror) instead of blocking |
| 6320 | * when the FILE* buffer is empty, which would otherwise |
| 6321 | * bypass the Phase 3 idle eviction timeout. |
| 6322 | * Phase 3: only if both phases confirm no data, do blocking poll. */ |
| 6323 | #ifdef _WIN32 |
| 6324 | /* Windows: WaitForSingleObject on stdin handle */ |
| 6325 | HANDLE hStdin = (HANDLE)_get_osfhandle(fd); |
| 6326 | DWORD wr = WaitForSingleObject(hStdin, STORE_IDLE_TIMEOUT_S * MCP_TIMEOUT_MS); |
| 6327 | if (wr == WAIT_FAILED) { |
| 6328 | break; |
| 6329 | } |
| 6330 | if (wr == WAIT_TIMEOUT) { |
| 6331 | cbm_mcp_server_evict_idle(srv, STORE_IDLE_TIMEOUT_S); |
| 6332 | continue; |
| 6333 | } |
| 6334 | #else |
| 6335 | int pr = poll_for_input_unix(srv, fd, in); |
| 6336 | if (pr < 0) { |
| 6337 | break; |
| 6338 | } |
| 6339 | if (pr == 0) { |
| 6340 | continue; /* timeout — idle stores evicted */ |
| 6341 | } |
| 6342 | #endif |
| 6343 | |
| 6344 | if (cbm_getline(&line, &cap, in) <= 0) { |
| 6345 | break; |
| 6346 | } |
| 6347 | |
| 6348 | /* Trim trailing newline/CR */ |
| 6349 | size_t len = strlen(line); |
| 6350 | while (len > 0 && (line[len - SKIP_ONE] == '\n' || line[len - SKIP_ONE] == '\r')) { |
| 6351 | line[--len] = '\0'; |
| 6352 | } |
| 6353 | if (len == 0) { |
| 6354 | continue; |
| 6355 | } |
| 6356 |
no test coverage detected