mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
### What does this PR do? `short` is signed in C++ by default and not unsigned. Switched to `uint16_t` so it's unambiguous. ### How did you verify your code works? There is a test --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
202 lines
7.2 KiB
TypeScript
202 lines
7.2 KiB
TypeScript
import { estimateShallowMemoryUsageOf, heapStats } from "bun:jsc";
|
|
import { describe, expect, it } from "bun:test";
|
|
import { parseHeapSnapshot, summarizeByType } from "./heap";
|
|
|
|
describe("Native types report their size correctly", () => {
|
|
it("FormData", () => {
|
|
var formData = new FormData();
|
|
globalThis.formData = formData;
|
|
let original = estimateShallowMemoryUsageOf(formData);
|
|
formData.append("a", Buffer.alloc(1024 * 1024 * 8, "abc").toString());
|
|
const afterBuffer = estimateShallowMemoryUsageOf(formData);
|
|
expect(afterBuffer).toBeGreaterThan(original + 1024 * 1024 * 8);
|
|
formData.append("a", new Blob([Buffer.alloc(1024 * 1024 * 2, "yooa")]));
|
|
const afterBlob = estimateShallowMemoryUsageOf(formData);
|
|
expect(afterBlob).toBeGreaterThan(afterBuffer + 1024 * 1024 * 2);
|
|
formData.append("a", new Blob([Buffer.alloc(1024 * 1024 * 2, "yooa")]));
|
|
const afterBlob2 = estimateShallowMemoryUsageOf(formData);
|
|
expect(afterBlob2).toBeGreaterThan(afterBlob + 1024 * 1024 * 2);
|
|
|
|
const snapshot = Bun.generateHeapSnapshot();
|
|
const parsed = parseHeapSnapshot(snapshot);
|
|
const summariesList = Array.from(summarizeByType(parsed));
|
|
const summariesMap = new Map(summariesList.map(summary => [summary.name, summary]));
|
|
|
|
expect(summariesMap.get("FormData")?.size).toBeGreaterThan(
|
|
// Test that FormData includes the size of the strings and the blobs
|
|
1024 * 1024 * 8 + 1024 * 1024 * 2 + 1024 * 1024 * 2,
|
|
);
|
|
|
|
delete globalThis.formData;
|
|
});
|
|
|
|
it("Request", () => {
|
|
var request = new Request("https://example.com", {
|
|
body: Buffer.alloc(1024 * 1024 * 2, "yoo"),
|
|
});
|
|
globalThis.request = request;
|
|
|
|
const snapshot = Bun.generateHeapSnapshot();
|
|
const parsed = parseHeapSnapshot(snapshot);
|
|
const summariesList = Array.from(summarizeByType(parsed));
|
|
const summariesMap = new Map(summariesList.map(summary => [summary.name, summary]));
|
|
|
|
expect(summariesMap.get("Request")?.size).toBeGreaterThan(1024 * 1024 * 2);
|
|
expect(summariesMap.get("Request")?.size).toBeLessThan(1024 * 1024 * 4);
|
|
|
|
delete globalThis.request;
|
|
});
|
|
|
|
it("Response", () => {
|
|
var response = new Response(Buffer.alloc(1024 * 1024 * 4, "yoo"), {
|
|
headers: {
|
|
"Content-Type": "text/plain",
|
|
},
|
|
});
|
|
globalThis.response = response;
|
|
|
|
const snapshot = Bun.generateHeapSnapshot();
|
|
const parsed = parseHeapSnapshot(snapshot);
|
|
const summariesList = Array.from(summarizeByType(parsed));
|
|
const summariesMap = new Map(summariesList.map(summary => [summary.name, summary]));
|
|
|
|
expect(summariesMap.get("Response")?.size).toBeGreaterThan(1024 * 1024 * 4);
|
|
|
|
delete globalThis.response;
|
|
});
|
|
|
|
it("URL (heap size reporting bug)", () => {
|
|
for (let i = 0; i < 500; i++) {
|
|
// need to use String.repeat(4096) here to ensure lots of tiny strings get allocated and joined.
|
|
// need to assign it to a global to ensure JSC and Bun do not eliminate it.
|
|
globalThis.url = new URL("Hello, 世界! 🌍".repeat(4096), "https://developer.mozilla.org");
|
|
}
|
|
|
|
// Expected: < 9007199254740991
|
|
// Received: 18446744073706270000
|
|
expect(heapStats().extraMemorySize).toBeLessThan(Number.MAX_SAFE_INTEGER);
|
|
|
|
delete globalThis.url;
|
|
});
|
|
|
|
it("URL", () => {
|
|
const searchParams = new URLSearchParams();
|
|
for (let i = 0; i < 1000; i++) {
|
|
searchParams.set(`a${i}`, `b${i}`);
|
|
}
|
|
|
|
var url = new URL("https://example.com");
|
|
globalThis.url = url;
|
|
url.search = searchParams.toString();
|
|
|
|
const snapshot = Bun.generateHeapSnapshot();
|
|
const parsed = parseHeapSnapshot(snapshot);
|
|
const summariesList = Array.from(summarizeByType(parsed));
|
|
const summariesMap = new Map(summariesList.map(summary => [summary.name, summary]));
|
|
|
|
expect(summariesMap.get("URL")?.size).toBeGreaterThan(searchParams.toString().length);
|
|
|
|
delete globalThis.url;
|
|
});
|
|
|
|
it("URLSearchParams", () => {
|
|
const searchParams = new URLSearchParams();
|
|
globalThis.searchParams = searchParams;
|
|
const original = estimateShallowMemoryUsageOf(searchParams);
|
|
for (let i = 0; i < 1000; i++) {
|
|
searchParams.set(`a${i}`, `b${i}`);
|
|
}
|
|
const after = estimateShallowMemoryUsageOf(searchParams);
|
|
expect(after).toBeGreaterThan(original + 1000 * 2);
|
|
|
|
const snapshot = Bun.generateHeapSnapshot();
|
|
const parsed = parseHeapSnapshot(snapshot);
|
|
const summariesList = Array.from(summarizeByType(parsed));
|
|
const summariesMap = new Map(summariesList.map(summary => [summary.name, summary]));
|
|
|
|
expect(summariesMap.get("URLSearchParams")?.size).toBeGreaterThan(
|
|
// toString() is greater because of the "?" and "&"
|
|
[...searchParams.keys(), ...searchParams.values()].join("").length,
|
|
);
|
|
|
|
delete globalThis.searchParams;
|
|
});
|
|
|
|
it("Headers", () => {
|
|
const headers = new Headers();
|
|
const original = estimateShallowMemoryUsageOf(headers);
|
|
for (let i = 0; i < 1000; i++) {
|
|
headers.set(`a${i}`, `b${i}`);
|
|
}
|
|
const after = estimateShallowMemoryUsageOf(headers);
|
|
expect(after).toBeGreaterThan(original + 1000 * 2);
|
|
|
|
globalThis.headers = headers;
|
|
|
|
const snapshot = Bun.generateHeapSnapshot();
|
|
const parsed = parseHeapSnapshot(snapshot);
|
|
const summariesList = Array.from(summarizeByType(parsed));
|
|
const summariesMap = new Map(summariesList.map(summary => [summary.name, summary]));
|
|
|
|
// Test that Headers includes the size of the strings
|
|
expect(summariesMap.get("Headers")?.size).toBeGreaterThan([...headers.keys(), ...headers.values()].join("").length);
|
|
|
|
delete globalThis.headers;
|
|
});
|
|
|
|
it("WebSocket + ServerWebSocket + Request", async () => {
|
|
using server = Bun.serve({
|
|
port: 0,
|
|
websocket: {
|
|
open(ws) {},
|
|
drain(ws) {},
|
|
message(ws, message) {
|
|
const before = estimateShallowMemoryUsageOf(ws);
|
|
ws.send(message);
|
|
const after = estimateShallowMemoryUsageOf(ws);
|
|
const bufferedAmount = ws.getBufferedAmount();
|
|
if (bufferedAmount > 0) {
|
|
expect(after).toBeGreaterThan(before + bufferedAmount);
|
|
}
|
|
},
|
|
},
|
|
|
|
fetch(req, server) {
|
|
const before = estimateShallowMemoryUsageOf(req);
|
|
server.upgrade(req);
|
|
const after = estimateShallowMemoryUsageOf(req);
|
|
|
|
// We detach the request context from the request object on upgrade.
|
|
expect(after).toBeLessThan(before);
|
|
|
|
return new Response("hello");
|
|
},
|
|
});
|
|
const ws = new WebSocket(server.url);
|
|
const original = estimateShallowMemoryUsageOf(ws);
|
|
globalThis.ws = ws;
|
|
|
|
const { promise, resolve } = Promise.withResolvers();
|
|
ws.onopen = () => {
|
|
// Send more than we can possibly send in a single message
|
|
ws.send(Buffer.alloc(1024 * 128, "hello"));
|
|
};
|
|
ws.onmessage = event => {
|
|
resolve(event.data);
|
|
};
|
|
await promise;
|
|
|
|
const after = estimateShallowMemoryUsageOf(ws);
|
|
expect(after).toBeGreaterThan(original + 1024 * 128);
|
|
|
|
const snapshot = Bun.generateHeapSnapshot();
|
|
const parsed = parseHeapSnapshot(snapshot);
|
|
const summariesList = Array.from(summarizeByType(parsed));
|
|
const summariesMap = new Map(summariesList.map(summary => [summary.name, summary]));
|
|
|
|
expect(summariesMap.get("WebSocket")?.size).toBeGreaterThan(1024 * 128);
|
|
|
|
delete globalThis.ws;
|
|
});
|
|
});
|