From 26e458932eeec0379db9b40730b2a37cb8b5f73c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Wed, 28 Oct 2026 23:39:17 -0500 Subject: [PATCH] Poll the checker server's socket before a reply read blocks The native compiler sends most checker requests one at a time and blocks in recv for each reply. The server answers most of them in a few microseconds, and a reader the reply wakes takes tens of microseconds more per round trip, over about 130,000 round trips on a large program. scriptc_ts7_read now polls the socket with MSG_DONTWAIT for up to 200 us before it blocks. Windows keeps its blocking pipe read. --- .../compiler/native/ts7-process-child.test.c | 13 ++++++- packages/compiler/native/ts7-process.c | 38 ++++++++++++++++++- packages/compiler/native/ts7-process.test.c | 15 ++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/packages/compiler/native/ts7-process-child.test.c b/packages/compiler/native/ts7-process-child.test.c index d387f735a..c89fcbafb 100644 --- a/packages/compiler/native/ts7-process-child.test.c +++ b/packages/compiler/native/ts7-process-child.test.c @@ -13,6 +13,7 @@ #include #else #include +#include #include #include #include @@ -102,7 +103,9 @@ static int child_main(int argc, char **argv) { #endif } } - if (strcmp(mode, "echo") != 0) return 73; + // "slow-echo" answers each read 20 ms later: after the client's poll. + int slow = strcmp(mode, "slow-echo") == 0; + if (!slow && strcmp(mode, "echo") != 0) return 73; // Unbuffered reads let short writes make progress without waiting for a // complete stdio buffer. Fragment each echoed reply independently. uint8_t bytes[17]; @@ -113,6 +116,14 @@ static int child_main(int argc, char **argv) { ssize_t count = read(STDIN_FILENO, bytes, sizeof(bytes)); #endif if (count <= 0) return count < 0 ? 74 : 0; + if (slow) { +#ifdef _WIN32 + Sleep(20); +#else + struct timespec delay = {0, 20000000L}; + nanosleep(&delay, NULL); +#endif + } if (fwrite(bytes, 1, (size_t)count, stdout) != (size_t)count || fflush(stdout) != 0) return 75; } } diff --git a/packages/compiler/native/ts7-process.c b/packages/compiler/native/ts7-process.c index bf486c87d..e570fa98c 100644 --- a/packages/compiler/native/ts7-process.c +++ b/packages/compiler/native/ts7-process.c @@ -552,6 +552,41 @@ static int valid_range(const uint8_t *bytes, size_t size, uint32_t offset, uint3 return 1; } +#ifndef _WIN32 +/* How long a read polls the socket before it blocks. The server answers + * most requests within a few microseconds; a blocked reader that the reply + * wakes takes tens of microseconds more per round trip. */ +#define TS7_REPLY_POLL_NS 200000L + +static long monotonic_ns(void) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return now.tv_sec * 1000000000L + now.tv_nsec; +} + +/* recv on the server's socket: polls it for TS7_REPLY_POLL_NS, then waits. */ +static ssize_t ts7_recv(int socket, uint8_t *bytes, size_t length) { + long started = -1; + for (;;) { + ssize_t count = recv(socket, bytes, length, MSG_DONTWAIT); + if (count >= 0) return count; + if (errno == EINTR) continue; + if (errno != EAGAIN && errno != EWOULDBLOCK) return -1; + long now = monotonic_ns(); + if (started < 0) started = now; + else if (now - started >= TS7_REPLY_POLL_NS) break; +#if defined(__x86_64__) || defined(__i386__) + __builtin_ia32_pause(); +#elif defined(__aarch64__) + __asm__ volatile("yield"); +#endif + } + ssize_t count; + do { count = recv(socket, bytes, length, 0); } while (count < 0 && errno == EINTR); + return count; +} +#endif + int32_t scriptc_ts7_read(uint32_t handle, uint8_t *bytes, size_t size, uint32_t offset, uint32_t length) { Ts7Process *p = find_process(handle); @@ -565,8 +600,7 @@ int32_t scriptc_ts7_read(uint32_t handle, uint8_t *bytes, size_t size, if (code == ERROR_BROKEN_PIPE) return 0; system_error(code); return -1; #else - ssize_t count; - do { count = recv(p->socket, bytes + offset, length, 0); } while (count < 0 && errno == EINTR); + ssize_t count = ts7_recv(p->socket, bytes + offset, length); if (count < 0) { system_error(errno); return -1; } return (int32_t)count; #endif diff --git a/packages/compiler/native/ts7-process.test.c b/packages/compiler/native/ts7-process.test.c index e07a7d10c..c51541772 100644 --- a/packages/compiler/native/ts7-process.test.c +++ b/packages/compiler/native/ts7-process.test.c @@ -149,6 +149,20 @@ static void exchange_cases(void) { scriptc_ts7_close(newer); } +/* A reply that arrives after the read's poll window: the read waits for it. */ +static void slow_reply_cases(void) { + uint32_t id = start("slow-echo", 0); + assert(id != 0); + const uint8_t bytes[3] = {1, 2, 3}; + uint8_t result[3] = {0}; + for (unsigned round = 0; round < 3; round++) { + write_exact(id, bytes, sizeof(bytes), 0, sizeof(bytes)); + read_exact(id, result, sizeof(result)); + assert(memcmp(bytes, result, sizeof(bytes)) == 0); + } + scriptc_ts7_close(id); +} + static void failure_cases(void) { uint8_t bytes[1] = {42}; const char *saved = executable; @@ -289,6 +303,7 @@ static int process_main(int argc, char **argv) { unsigned before = resource_count(); argument_cases(); exchange_cases(); + slow_reply_cases(); failure_cases(); #ifndef _WIN32 attach_cases();