Files
bun.sh/test/js/web/fetch/fetch-tcp-stress.test.ts

159 lines
3.6 KiB
TypeScript

// If port exhaustion occurs, these tests fail.
// These tests fail by timing out.
import { expect, test } from "bun:test";
import { getMaxFD, isCI, isMacOS } from "harness";
// Since we bumped MAX_CONNECTIONS to 4, we should halve the threshold on macOS.
const PORT_EXHAUSTION_THRESHOLD = isMacOS ? 8 * 1024 : 16 * 1024;
async function runStressTest({
onServerWritten,
onFetchWritten,
}: {
onServerWritten: (socket) => void;
onFetchWritten: (socket) => void;
}) {
const total = PORT_EXHAUSTION_THRESHOLD * 2;
let sockets = [];
const batch = 48;
let toClose = 0;
let pendingClose = Promise.withResolvers();
const objects = [];
for (let i = 0; i < total; i++) {
objects.push({
method: "POST",
body: "--BYTEMARKER: " + (10 + i) + " ",
keepalive: false,
});
}
const server = await Bun.listen({
port: 0,
socket: {
open(socket) {},
data(socket, data) {
const text = new TextDecoder().decode(data);
const i = parseInt(text.slice(text.indexOf("--BYTEMARKER: ") + "--BYTEMARKER: ".length).slice(0, 3)) - 10;
if (text.includes(objects[i].body)) {
socket.data ??= {};
socket.data.read = true;
sockets[i] = socket;
if (socket.write("200 OK\r\nCo") === "200 OK\r\nCo".length) {
socket.data.written = true;
onServerWritten(socket);
}
return;
}
console.log("Data is missing!");
},
drain(socket) {
if (!socket.data?.read || socket.data?.written) {
return;
}
if (socket.write("200 OK\r\nCo") === "200 OK\r\nCo".length) {
socket.data.written = true;
onServerWritten(socket);
}
},
error(socket, err) {
console.log(err);
},
timeout() {},
close(socket) {
toClose--;
if (toClose === 0) {
pendingClose.resolve();
}
},
},
hostname: "127.0.0.1",
});
let initialMaxFD = -1;
for (let remaining = total; remaining > 0; remaining -= batch) {
pendingClose = Promise.withResolvers();
{
const promises = [];
toClose = batch;
for (let i = 0; i < batch; i++) {
promises.push(
fetch(`http://127.0.0.1:${server.port}`, objects[i]).finally(() => {
onFetchWritten(sockets[i]);
}),
);
}
await Promise.allSettled(promises);
promises.length = 0;
}
await pendingClose.promise;
if (total) sockets = [];
if (initialMaxFD === -1) {
initialMaxFD = getMaxFD();
}
}
server.stop(true);
await Bun.sleep(10);
expect(getMaxFD()).toBeLessThan(initialMaxFD + 10);
}
test.todoIf(isCI && isMacOS)(
"shutdown after timeout",
async () => {
await runStressTest({
onServerWritten(socket) {
socket.end();
},
onFetchWritten(socket) {},
});
},
30 * 1000,
);
test.todoIf(isCI && isMacOS)(
"close after TCP fin",
async () => {
await runStressTest({
onServerWritten(socket) {
socket.shutdown();
},
onFetchWritten(socket) {
socket.end();
},
});
},
30 * 1000,
);
test.todoIf(isCI && isMacOS)(
"shutdown then terminate",
async () => {
await runStressTest({
onServerWritten(socket) {
socket.shutdown();
},
onFetchWritten(socket) {
socket.terminate();
},
});
},
30 * 1000,
);
test.todoIf(isCI && isMacOS)(
"gently close",
async () => {
await runStressTest({
onServerWritten(socket) {
socket.end();
},
onFetchWritten(socket) {},
});
},
30 * 1000,
);