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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
import { listen, connect, TCPSocketListener } from "bun";
import { expect, it } from "bun:test";
import * as JSC from "bun:jsc";
var decoder = new TextDecoder();
it("echo server 1 on 1", async () => {
// wrap it in a separate closure so the GC knows to clean it up
// the sockets & listener don't escape the closure
await (async function () {
var resolve, reject, serverResolve, serverReject;
var prom = new Promise((resolve1, reject1) => {
resolve = resolve1;
reject = reject1;
});
var serverProm = new Promise((resolve1, reject1) => {
serverResolve = resolve1;
serverReject = reject1;
});
var serverData, clientData;
const handlers = {
open(socket) {
socket.data.counter = 1;
if (!socket.data?.isServer) {
clientData = socket.data;
clientData.sendQueue = ["client: Hello World! " + 0];
if (!socket.write("client: Hello World! " + 0)) {
socket.data = { pending: "server: Hello World! " + 0 };
}
} else {
serverData = socket.data;
serverData.sendQueue = ["server: Hello World! " + 0];
}
if (clientData) clientData.other = serverData;
if (serverData) serverData.other = clientData;
if (clientData) clientData.other = serverData;
if (serverData) serverData.other = clientData;
},
data(socket, buffer) {
const msg = `${
socket.data.isServer ? "server:" : "client:"
} Hello World! ${socket.data.counter++}`;
socket.data.sendQueue.push(msg);
expect(decoder.decode(buffer)).toBe(socket.data.other.sendQueue.pop());
if (socket.data.counter > 10) {
if (!socket.data.finished) {
socket.data.finished = true;
if (socket.data.isServer) {
setTimeout(() => {
serverResolve();
socket.end();
}, 1);
} else {
setTimeout(() => {
resolve();
socket.end();
}, 1);
}
}
}
if (!socket.write(msg)) {
socket.data.pending = msg;
return;
}
},
error(socket, error) {
reject(error);
},
drain(socket) {
reject(new Error("Unexpected backpressure"));
},
};
var server: TCPSocketListener<any> | undefined = listen({
socket: handlers,
hostname: "localhost",
port: 8084,
data: {
isServer: true,
counter: 0,
},
});
const clientProm = connect({
socket: handlers,
hostname: "localhost",
port: 8084,
data: {
counter: 0,
},
});
await Promise.all([prom, clientProm, serverProm]);
server.stop(true);
server = serverData = clientData = undefined;
})();
});
it("should not leak memory", () => {
// Tell the garbage collector for sure that we're done with the sockets
Bun.gc(true);
// assert we don't leak the sockets
// we expect 1 because that's the prototype / structure
expect(JSC.heapStats().objectTypeCounts.TCPSocket).toBe(1);
expect(JSC.heapStats().objectTypeCounts.Listener).toBe(1);
});
|