@@ -1215,6 +1215,78 @@ TEST(snippet_include_neighbors_enabled) {
12151215 PASS ();
12161216}
12171217
1218+ /* ══════════════════════════════════════════════════════════════════
1219+ * POLL/GETLINE FILE* BUFFERING FIX
1220+ * ══════════════════════════════════════════════════════════════════ */
1221+
1222+ #ifndef _WIN32
1223+ #include <unistd.h>
1224+ #include <signal.h>
1225+
1226+ /* Signal handler used by alarm() to abort the test if it hangs */
1227+ static void alarm_handler (int sig ) {
1228+ (void )sig ;
1229+ /* Writing to stderr is async-signal-safe */
1230+ const char msg [] = "FAIL: mcp_server_run_rapid_messages timed out (>5s)\n" ;
1231+ write (STDERR_FILENO , msg , sizeof (msg ) - 1 );
1232+ _exit (1 );
1233+ }
1234+
1235+ TEST (mcp_server_run_rapid_messages ) {
1236+ /* Simulate a client sending initialize + notifications/initialized +
1237+ * tools/list all at once (no delays), which exercises the FILE*
1238+ * buffering fix: the first getline() over-reads kernel data into the
1239+ * libc buffer; without the fix, subsequent poll() calls block for 60s.
1240+ *
1241+ * We use alarm(5) to abort the test process if the server hangs. */
1242+ int fds [2 ];
1243+ ASSERT_EQ (pipe (fds ), 0 );
1244+
1245+ /* Write all 3 messages to the write end in one shot */
1246+ const char * msgs =
1247+ "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\","
1248+ "\"params\":{\"protocolVersion\":\"2025-11-25\",\"capabilities\":{}}}\n"
1249+ "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}\n"
1250+ "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\",\"params\":{}}\n" ;
1251+ ssize_t written = write (fds [1 ], msgs , strlen (msgs ));
1252+ ASSERT_TRUE (written > 0 );
1253+ close (fds [1 ]); /* EOF signals end of input to the server */
1254+
1255+ FILE * in_fp = fdopen (fds [0 ], "r" );
1256+ ASSERT_NOT_NULL (in_fp );
1257+
1258+ FILE * out_fp = tmpfile ();
1259+ ASSERT_NOT_NULL (out_fp );
1260+
1261+ cbm_mcp_server_t * srv = cbm_mcp_server_new (NULL );
1262+ ASSERT_NOT_NULL (srv );
1263+
1264+ /* Install alarm to fail the test if cbm_mcp_server_run blocks */
1265+ signal (SIGALRM , alarm_handler );
1266+ alarm (5 );
1267+
1268+ int rc = cbm_mcp_server_run (srv , in_fp , out_fp );
1269+
1270+ alarm (0 ); /* cancel alarm */
1271+ signal (SIGALRM , SIG_DFL );
1272+
1273+ ASSERT_EQ (rc , 0 );
1274+
1275+ /* Verify that tools/list response is present in output */
1276+ rewind (out_fp );
1277+ char buf [4096 ] = {0 };
1278+ size_t nread = fread (buf , 1 , sizeof (buf ) - 1 , out_fp );
1279+ ASSERT_TRUE (nread > 0 );
1280+ ASSERT_NOT_NULL (strstr (buf , "tools" ));
1281+
1282+ cbm_mcp_server_free (srv );
1283+ fclose (out_fp );
1284+ /* in_fp already EOF; fclose cleans up */
1285+ fclose (in_fp );
1286+ PASS ();
1287+ }
1288+ #endif /* !_WIN32 */
1289+
12181290/* ══════════════════════════════════════════════════════════════════
12191291 * SUITE
12201292 * ══════════════════════════════════════════════════════════════════ */
@@ -1287,6 +1359,11 @@ SUITE(mcp) {
12871359 RUN_TEST (parse_file_uri_windows );
12881360 RUN_TEST (parse_file_uri_invalid );
12891361
1362+ /* Poll/getline FILE* buffering fix */
1363+ #ifndef _WIN32
1364+ RUN_TEST (mcp_server_run_rapid_messages );
1365+ #endif
1366+
12901367 /* Snippet resolution (port of snippet_test.go) */
12911368 RUN_TEST (snippet_exact_qn );
12921369 RUN_TEST (snippet_qn_suffix );
0 commit comments