@@ -2375,8 +2375,19 @@ int cbm_mcp_server_run(cbm_mcp_server_t *srv, FILE *in, FILE *out) {
23752375
23762376 for (;;) {
23772377 /* Poll with idle timeout so we can evict unused stores between requests.
2378- * MCP is request-response (one line at a time), so mixing poll() on the
2379- * raw fd with getline() on the buffered FILE* is safe in practice. */
2378+ *
2379+ * IMPORTANT: poll() operates on the raw fd, but getline() reads from a
2380+ * buffered FILE*. When a client sends multiple messages in rapid
2381+ * succession, the first getline() call may drain ALL kernel data into
2382+ * libc's internal FILE* buffer. Subsequent poll() calls then see an
2383+ * empty kernel fd and block for STORE_IDLE_TIMEOUT_S seconds even
2384+ * though the next messages are already in the FILE* buffer.
2385+ *
2386+ * Fix (Unix): use a two-phase approach —
2387+ * Phase 1: non-blocking poll (timeout=0) to check the kernel fd.
2388+ * Phase 2: if Phase 1 returns 0, peek the FILE* buffer via fgetc/
2389+ * ungetc to detect data buffered by a prior getline() call.
2390+ * Phase 3: only if both phases confirm no data, do blocking poll. */
23802391#ifdef _WIN32
23812392 /* Windows: WaitForSingleObject on stdin handle */
23822393 HANDLE hStdin = (HANDLE )_get_osfhandle (fd );
@@ -2389,16 +2400,40 @@ int cbm_mcp_server_run(cbm_mcp_server_t *srv, FILE *in, FILE *out) {
23892400 continue ;
23902401 }
23912402#else
2403+ /* Phase 1: non-blocking poll — catches data already in the kernel fd
2404+ * AND handles the case where a prior getline() drained the kernel fd
2405+ * into libc's FILE* buffer (raw fd appears empty but data is buffered).
2406+ * We always try a zero-timeout poll first; if it misses buffered data,
2407+ * phase 2 below catches it via an explicit FILE* peek. */
23922408 struct pollfd pfd = {.fd = fd , .events = POLLIN };
2393- int pr = poll (& pfd , 1 , STORE_IDLE_TIMEOUT_S * 1000 );
2409+ int pr = poll (& pfd , 1 , 0 ); /* non-blocking */
23942410
23952411 if (pr < 0 ) {
23962412 break ; /* error or signal */
23972413 }
23982414 if (pr == 0 ) {
2399- /* Timeout — evict idle store to free resources */
2400- cbm_mcp_server_evict_idle (srv , STORE_IDLE_TIMEOUT_S );
2401- continue ;
2415+ /* Raw fd appears empty. Check whether libc has already buffered
2416+ * data from a previous over-read by peeking one byte via fgetc.
2417+ * If successful, push it back and proceed to getline without
2418+ * blocking. If not (EOF or EAGAIN), do the blocking poll. */
2419+ int c = fgetc (in );
2420+ if (c == EOF ) {
2421+ if (feof (in )) {
2422+ break ; /* true EOF */
2423+ }
2424+ /* No buffered data and fd is empty — do blocking poll */
2425+ pr = poll (& pfd , 1 , STORE_IDLE_TIMEOUT_S * 1000 );
2426+ if (pr < 0 ) {
2427+ break ;
2428+ }
2429+ if (pr == 0 ) {
2430+ cbm_mcp_server_evict_idle (srv , STORE_IDLE_TIMEOUT_S );
2431+ continue ;
2432+ }
2433+ } else {
2434+ /* Buffered data found — push back and fall through to getline */
2435+ (void )ungetc (c , in );
2436+ }
24022437 }
24032438#endif
24042439
0 commit comments