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.
This commit is contained in:
Chris Tate
2026-10-28 23:39:17 -05:00
parent 0faade48ae
commit 26e458932e
3 changed files with 63 additions and 3 deletions
@@ -13,6 +13,7 @@
#include <windows.h>
#else
#include <poll.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
@@ -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;
}
}
+36 -2
View File
@@ -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
@@ -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();