mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Send a zero from an inline RWF_NOWAIT read to the pool
Linux 5.9 and 5.10 answer 0 instead of EAGAIN for an RWF_NOWAIT read of data that is not cached. The inline read took that 0 as end of file and ended the ReadStream early. A zero from the inline read now goes to the pool, whose read returns the data or confirms end of file; this costs one pool round trip at the end of each file. fs-stream-nowait.test.ts preloads a preadv2 shim that answers 0 for every RWF_NOWAIT read and checks that a ReadStream still reads the whole file, as Node does.
This commit is contained in:
@@ -186,7 +186,10 @@ static bool scr_fss_try_inline(ScrFssOp *op) {
|
||||
if (errno == EOPNOTSUPP || errno == EINVAL || errno == ENOSYS) f->inline_ok = false;
|
||||
return false;
|
||||
}
|
||||
if (n == 0 && op->kind == SCR_FSS_WRITE) return false;
|
||||
/* A zero is not taken as EOF here: some kernels (Linux 5.9 and 5.10)
|
||||
* answer 0 instead of EAGAIN for an uncached RWF_NOWAIT read, so the
|
||||
* pool's read decides, as it does for a zero-byte write. */
|
||||
if (n == 0) return false;
|
||||
if (op->kind == SCR_FSS_READ && n > 0 && (size_t)n < op->len) {
|
||||
op->got = (size_t)n;
|
||||
return false;
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Reads a file it writes first through a ReadStream and prints the byte
|
||||
// count and a checksum (fs-stream-nowait.test.ts runs it with a preadv2
|
||||
// shim).
|
||||
import { createReadStream, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const dir = mkdtempSync(join(tmpdir(), "scr-nowait-"));
|
||||
const file = join(dir, "data.bin");
|
||||
const data = Buffer.alloc(300000);
|
||||
for (let i = 0; i < data.length; i++) data[i] = (i * 13) & 0xff;
|
||||
writeFileSync(file, data);
|
||||
let bytes = 0;
|
||||
let sum = 0;
|
||||
createReadStream(file, { highWaterMark: 16384 })
|
||||
.on("data", (c: Buffer) => {
|
||||
bytes += c.length;
|
||||
for (const b of c) sum = (sum + b) % 65521;
|
||||
})
|
||||
.on("end", () => {
|
||||
console.log(`bytes=${bytes} sum=${sum}`);
|
||||
rmSync(dir, { recursive: true });
|
||||
});
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/* LD_PRELOAD shim for fs-stream-nowait.test.ts: preadv2 with RWF_NOWAIT
|
||||
* answers 0, as Linux 5.9 and 5.10 do for an uncached RWF_NOWAIT read;
|
||||
* other calls go to the real preadv2. */
|
||||
#define _GNU_SOURCE
|
||||
#include <dlfcn.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags) {
|
||||
if (flags & RWF_NOWAIT) return 0;
|
||||
ssize_t (*real)(int, const struct iovec *, int, off_t, int) =
|
||||
(ssize_t (*)(int, const struct iovec *, int, off_t, int))dlsym(RTLD_NEXT, "preadv2");
|
||||
return real(fd, iov, iovcnt, offset, flags);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/* fs streams read a regular file with RWF_NOWAIT on the loop thread first
|
||||
* (scr_fs_stream.c). Linux 5.9 and 5.10 answer 0 instead of EAGAIN for
|
||||
* an uncached RWF_NOWAIT read; a preloaded preadv2 shim reproduces that
|
||||
* answer, and the stream must still read the whole file, as Node does. */
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { compile } from "@scriptc/compiler";
|
||||
|
||||
const repoRoot = join(import.meta.dirname, "../..");
|
||||
const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests");
|
||||
const sanitize = process.env["SCRIPTC_SAN"] === "1";
|
||||
|
||||
describe.skipIf(process.platform !== "linux" || sanitize)("fs stream RWF_NOWAIT reads", () => {
|
||||
test("a zero from an RWF_NOWAIT read is not taken as EOF", async () => {
|
||||
const outDir = join(cacheDir, "fs-stream-nowait");
|
||||
const result = await compile(join(repoRoot, "tests/fixtures/fs-stream-nowait-read.ts"), {
|
||||
outPath: join(outDir, "read"),
|
||||
outDir,
|
||||
backend: "llvm",
|
||||
});
|
||||
if (!result.ok) {
|
||||
throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
|
||||
}
|
||||
const shim = join(outDir, "nowait-zero.so");
|
||||
execFileSync("clang", [
|
||||
"-shared",
|
||||
"-fPIC",
|
||||
join(repoRoot, "tests/fixtures/fs-stream-nowait-zero.c"),
|
||||
"-o",
|
||||
shim,
|
||||
"-ldl",
|
||||
]);
|
||||
const expected = "bytes=300000 sum=50681\n";
|
||||
const plain = spawnSync(result.binaryPath, [], { encoding: "utf8" });
|
||||
expect(plain.stdout).toBe(expected);
|
||||
const shimmed = spawnSync(result.binaryPath, [], {
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, LD_PRELOAD: shim },
|
||||
});
|
||||
expect(shimmed.status).toBe(0);
|
||||
expect(shimmed.stdout).toBe(expected);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user