Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Shoubhit Dash
d0a08c2267 test(webfetch): deflake abort leak coverage 2026-04-09 21:33:17 +05:30

View file

@ -22,40 +22,56 @@ const ITERATIONS = 50
const getHeapMB = () => { const getHeapMB = () => {
Bun.gc(true) Bun.gc(true)
Bun.sleepSync(25)
return process.memoryUsage().heapUsed / MB return process.memoryUsage().heapUsed / MB
} }
describe("memory: abort controller leak", () => { describe("memory: abort controller leak", () => {
test("webfetch does not leak memory over many invocations", async () => { test("webfetch clears abort timers over many invocations", async () => {
await Instance.provide({ type TimerID = number
directory: projectRoot,
fn: async () => {
const tool = await WebFetchTool.init()
// Warm up const prevFetch = globalThis.fetch
await tool.execute({ url: "https://example.com", format: "text" }, ctx).catch(() => {}) const prevSetTimeout = globalThis.setTimeout
const prevClearTimeout = globalThis.clearTimeout
const active = new Set<TimerID>()
Bun.gc(true) globalThis.fetch = (async () =>
const baseline = getHeapMB() new Response("hello from webfetch", {
status: 200,
headers: {
"content-type": "text/plain; charset=utf-8",
},
})) as unknown as typeof fetch
globalThis.setTimeout = ((handler: TimerHandler, timeout?: number, ...args: any[]) => {
const id = prevSetTimeout(handler, timeout, ...args) as unknown as TimerID
active.add(id)
return id as unknown as ReturnType<typeof setTimeout>
}) as unknown as typeof setTimeout
globalThis.clearTimeout = ((id?: Parameters<typeof clearTimeout>[0]) => {
if (id !== undefined) active.delete(id as unknown as TimerID)
return prevClearTimeout(id)
}) as unknown as typeof clearTimeout
try {
await Instance.provide({
directory: projectRoot,
fn: async () => {
const tool = await WebFetchTool.init()
// Run many fetches
for (let i = 0; i < ITERATIONS; i++) {
await tool.execute({ url: "https://example.com", format: "text" }, ctx).catch(() => {}) await tool.execute({ url: "https://example.com", format: "text" }, ctx).catch(() => {})
}
Bun.gc(true) for (let i = 0; i < ITERATIONS; i++) {
const after = getHeapMB() await tool.execute({ url: "https://example.com", format: "text" }, ctx).catch(() => {})
const growth = after - baseline }
console.log(`Baseline: ${baseline.toFixed(2)} MB`) expect(active.size).toBe(0)
console.log(`After ${ITERATIONS} fetches: ${after.toFixed(2)} MB`) },
console.log(`Growth: ${growth.toFixed(2)} MB`) })
} finally {
// Memory growth should be minimal - less than 1MB per 10 requests globalThis.fetch = prevFetch
// With the old closure pattern, this would grow ~0.5MB per request globalThis.setTimeout = prevSetTimeout
expect(growth).toBeLessThan(ITERATIONS / 10) globalThis.clearTimeout = prevClearTimeout
}, }
})
}, 60000) }, 60000)
test("compare closure vs bind pattern directly", async () => { test("compare closure vs bind pattern directly", async () => {