1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
import { expect, it } from "bun:test";
import { bunExe, bunEnv } from "../../harness.js";
import { mkdirSync, rmSync, writeFileSync, readFileSync, mkdtempSync } from "fs";
import { tmpdir } from "os";
import { dirname, join } from "path";
import { sleep, spawn, spawnSync, which } from "bun";
// https://github.com/oven-sh/bun/issues/2499
it("onAborted() and onWritable are not called after receiving an empty response body due to a promise rejection", async testDone => {
var timeout = AbortSignal.timeout(10_000);
timeout.onabort = e => {
testDone(new Error("Test timed out, which means it failed"));
};
const invalidJSON = Buffer.from("invalid json");
// We want to test that the server isn't keeping the connection open in a
// zombie-like state when an error occurs due to an unhandled rejected promise
//
// At the time of writing, this can only happen when:
// - development mode is enabled
// - the server didn't send the complete response body in one send()
// - renderMissing() is called
//
// In that case, it finalizes the response in the middle of an incomplete body
//
// On an M1, this reproduces 1 out of every 4 calls to this function
// It's inherently going to be flaky without simulating system calls or overriding libc
//
// So to make sure we catch it
// 1) Run this test 40 times
// 2) Set a timeout for this test of 10 seconds.
//
// In debug builds, this test should complete in 1-2 seconds.
for (let i = 0; i < 40; i++) {
let bunProcess;
try {
bunProcess = spawn({
cmd: [bunExe(), "run", join(import.meta.dir, "./02499-repro.ts")],
stdin: "pipe",
stderr: "ignore",
stdout: "pipe",
env: bunEnv,
});
const reader = bunProcess.stdout.getReader();
let hostname, port;
{
const chunks: Buffer[] = [];
var decoder = new TextDecoder();
while (!hostname && !port) {
var { value, done } = await reader.read();
if (done) break;
if (chunks.length > 0) {
chunks.push(value!);
}
try {
if (chunks.length > 0) {
value = Buffer.concat(chunks);
}
({ hostname, port } = JSON.parse(decoder.decode(value).trim()));
} catch {
chunks.push(value!);
}
}
}
try {
await fetch(`http://${hostname}:${port}/upload`, {
body: invalidJSON,
keepalive: false,
method: "POST",
timeout: true,
signal: timeout,
});
} catch (e) {}
bunProcess.stdin?.write("--CLOSE--");
await bunProcess.stdin?.flush();
await bunProcess.stdin?.end();
expect(await bunProcess.exited).toBe(0);
} catch (e) {
timeout.onabort = () => {};
testDone(e);
throw e;
} finally {
bunProcess?.kill(9);
}
}
timeout.onabort = () => {};
testDone();
}, 30_000);
|