mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-21 14:07:20 +08:00
fix: replace empty catch blocks in memory test helpers with isAlive/trySendSignal utilities
This commit is contained in:
@@ -4,6 +4,31 @@ const MB = 1024 * 1024
|
||||
|
||||
export const PROJECT_ROOT = path.join(__dirname, "../..")
|
||||
|
||||
/**
|
||||
* Check if a process is alive by sending signal 0.
|
||||
* Returns false if the process has already exited.
|
||||
*/
|
||||
export function isAlive(pid: number): boolean {
|
||||
try {
|
||||
process.kill(pid, 0)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a signal to a process, ignoring errors (e.g. process already exited).
|
||||
*/
|
||||
function trySendSignal(pid: number, signal: NodeJS.Signals): void {
|
||||
try {
|
||||
process.kill(pid, signal)
|
||||
} catch (err) {
|
||||
// Expected when process already exited before signal delivery
|
||||
console.log(` signal ${signal} to PID ${pid} failed (likely already exited): ${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force GC multiple times and return stable heap usage in MB.
|
||||
* Multiple passes + sleeps allow GC to finalize weak refs and sweep.
|
||||
@@ -79,14 +104,8 @@ export async function snapshotDescendants(rootPid: number): Promise<Set<number>>
|
||||
export async function assertNoOrphans(before: Set<number>, after: Set<number>): Promise<void> {
|
||||
const orphans = new Set<number>()
|
||||
for (const pid of after) {
|
||||
if (!before.has(pid)) {
|
||||
// Verify the process is still running
|
||||
try {
|
||||
process.kill(pid, 0)
|
||||
orphans.add(pid)
|
||||
} catch {
|
||||
// Process already exited
|
||||
}
|
||||
if (!before.has(pid) && isAlive(pid)) {
|
||||
orphans.add(pid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,16 +132,10 @@ export async function assertNoOrphans(before: Set<number>, after: Set<number>):
|
||||
|
||||
// Force-kill orphans to prevent cascading test failures
|
||||
for (const pid of orphans) {
|
||||
try {
|
||||
process.kill(pid, "SIGKILL")
|
||||
} catch {
|
||||
// Already exited
|
||||
}
|
||||
trySendSignal(pid, "SIGKILL")
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Found ${orphans.size} orphan process(es):\n${details.map((d) => ` ${d}`).join("\n")}`,
|
||||
)
|
||||
throw new Error(`Found ${orphans.size} orphan process(es):\n${details.map((d) => ` ${d}`).join("\n")}`)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,9 +148,7 @@ export async function waitForExit(pids: number[], timeoutMs = 3000): Promise<boo
|
||||
|
||||
while (remaining.size > 0 && Date.now() - start < timeoutMs) {
|
||||
for (const pid of remaining) {
|
||||
try {
|
||||
process.kill(pid, 0)
|
||||
} catch {
|
||||
if (!isAlive(pid)) {
|
||||
remaining.delete(pid)
|
||||
}
|
||||
}
|
||||
@@ -154,10 +165,6 @@ export async function waitForExit(pids: number[], timeoutMs = 3000): Promise<boo
|
||||
*/
|
||||
export function forceKillAll(pids: Set<number> | number[]): void {
|
||||
for (const pid of pids) {
|
||||
try {
|
||||
process.kill(pid, "SIGKILL")
|
||||
} catch {
|
||||
// Already exited
|
||||
}
|
||||
trySendSignal(pid, "SIGKILL")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,7 @@ import { Instance } from "../../src/project/instance"
|
||||
import { LSPClient } from "../../src/lsp/client"
|
||||
import { spawn } from "child_process"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
import {
|
||||
PROJECT_ROOT,
|
||||
snapshotDescendants,
|
||||
assertNoOrphans,
|
||||
forceKillAll,
|
||||
stableHeapMB,
|
||||
} from "./helper"
|
||||
import { PROJECT_ROOT, snapshotDescendants, assertNoOrphans, forceKillAll, stableHeapMB, isAlive } from "./helper"
|
||||
|
||||
const FAKE_LSP_SERVER = path.join(PROJECT_ROOT, "test/fixture/lsp/fake-lsp-server.js")
|
||||
|
||||
@@ -37,129 +31,104 @@ describe("memory: LSP lifecycle", () => {
|
||||
}
|
||||
})
|
||||
|
||||
test(
|
||||
"LSPClient.shutdown kills server process",
|
||||
async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
test("LSPClient.shutdown kills server process", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
|
||||
beforePids = await snapshotDescendants(process.pid)
|
||||
beforePids = await snapshotDescendants(process.pid)
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const serverProcess = spawnLSP(tmp.path)
|
||||
const pid = serverProcess.pid!
|
||||
|
||||
const client = await LSPClient.create({
|
||||
serverID: "test-lsp",
|
||||
server: { process: serverProcess as any },
|
||||
root: tmp.path,
|
||||
})
|
||||
expect(client).toBeTruthy()
|
||||
|
||||
// Verify process is running
|
||||
expect(isAlive(pid)).toBe(true)
|
||||
|
||||
// Shutdown
|
||||
await client!.shutdown()
|
||||
await Bun.sleep(300)
|
||||
|
||||
// Verify process is gone
|
||||
expect(isAlive(pid)).toBe(false)
|
||||
|
||||
await Instance.dispose()
|
||||
},
|
||||
})
|
||||
}, 30_000)
|
||||
|
||||
test("Multiple create/shutdown cycles don't leak processes", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
|
||||
beforePids = await snapshotDescendants(process.pid)
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const serverProcess = spawnLSP(tmp.path)
|
||||
const pid = serverProcess.pid!
|
||||
|
||||
const client = await LSPClient.create({
|
||||
serverID: "test-lsp",
|
||||
serverID: `test-lsp-${i}`,
|
||||
server: { process: serverProcess as any },
|
||||
root: tmp.path,
|
||||
})
|
||||
expect(client).toBeTruthy()
|
||||
|
||||
// Verify process is running
|
||||
let alive = true
|
||||
try {
|
||||
process.kill(pid, 0)
|
||||
} catch {
|
||||
alive = false
|
||||
}
|
||||
expect(alive).toBe(true)
|
||||
|
||||
// Shutdown
|
||||
await client!.shutdown()
|
||||
await Bun.sleep(300)
|
||||
await Bun.sleep(100)
|
||||
}
|
||||
|
||||
// Verify process is gone
|
||||
let stillAlive = false
|
||||
try {
|
||||
process.kill(pid, 0)
|
||||
stillAlive = true
|
||||
} catch {
|
||||
// Expected
|
||||
}
|
||||
expect(stillAlive).toBe(false)
|
||||
await Bun.sleep(300)
|
||||
const afterPids = await snapshotDescendants(process.pid)
|
||||
await assertNoOrphans(beforePids, afterPids)
|
||||
|
||||
await Instance.dispose()
|
||||
},
|
||||
})
|
||||
},
|
||||
30_000,
|
||||
)
|
||||
await Instance.dispose()
|
||||
},
|
||||
})
|
||||
}, 60_000)
|
||||
|
||||
test(
|
||||
"Multiple create/shutdown cycles don't leak processes",
|
||||
async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
test("LSP create/shutdown doesn't leak memory", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
|
||||
beforePids = await snapshotDescendants(process.pid)
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
// Warm-up
|
||||
const warmProc = spawnLSP(tmp.path)
|
||||
const warmClient = await LSPClient.create({
|
||||
serverID: "test-lsp-warm",
|
||||
server: { process: warmProc as any },
|
||||
root: tmp.path,
|
||||
})
|
||||
await warmClient!.shutdown()
|
||||
await Bun.sleep(100)
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const serverProcess = spawnLSP(tmp.path)
|
||||
const client = await LSPClient.create({
|
||||
serverID: `test-lsp-${i}`,
|
||||
server: { process: serverProcess as any },
|
||||
root: tmp.path,
|
||||
})
|
||||
await client!.shutdown()
|
||||
await Bun.sleep(100)
|
||||
}
|
||||
const baseline = await stableHeapMB()
|
||||
|
||||
await Bun.sleep(300)
|
||||
const afterPids = await snapshotDescendants(process.pid)
|
||||
await assertNoOrphans(beforePids, afterPids)
|
||||
|
||||
await Instance.dispose()
|
||||
},
|
||||
})
|
||||
},
|
||||
60_000,
|
||||
)
|
||||
|
||||
test(
|
||||
"LSP create/shutdown doesn't leak memory",
|
||||
async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
// Warm-up
|
||||
const warmProc = spawnLSP(tmp.path)
|
||||
const warmClient = await LSPClient.create({
|
||||
serverID: "test-lsp-warm",
|
||||
server: { process: warmProc as any },
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const serverProcess = spawnLSP(tmp.path)
|
||||
const client = await LSPClient.create({
|
||||
serverID: `test-lsp-mem-${i}`,
|
||||
server: { process: serverProcess as any },
|
||||
root: tmp.path,
|
||||
})
|
||||
await warmClient!.shutdown()
|
||||
await Bun.sleep(100)
|
||||
await client!.shutdown()
|
||||
await Bun.sleep(50)
|
||||
}
|
||||
|
||||
const baseline = await stableHeapMB()
|
||||
const after = await stableHeapMB()
|
||||
const growth = after - baseline
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const serverProcess = spawnLSP(tmp.path)
|
||||
const client = await LSPClient.create({
|
||||
serverID: `test-lsp-mem-${i}`,
|
||||
server: { process: serverProcess as any },
|
||||
root: tmp.path,
|
||||
})
|
||||
await client!.shutdown()
|
||||
await Bun.sleep(50)
|
||||
}
|
||||
console.log(` LSP create/shutdown 10x growth: ${growth.toFixed(2)} MB`)
|
||||
expect(growth).toBeLessThan(5)
|
||||
|
||||
const after = await stableHeapMB()
|
||||
const growth = after - baseline
|
||||
|
||||
console.log(` LSP create/shutdown 10x growth: ${growth.toFixed(2)} MB`)
|
||||
expect(growth).toBeLessThan(5)
|
||||
|
||||
await Instance.dispose()
|
||||
},
|
||||
})
|
||||
},
|
||||
60_000,
|
||||
)
|
||||
await Instance.dispose()
|
||||
},
|
||||
})
|
||||
}, 60_000)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user