mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-07 08:33:50 +08:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user