feat: 尝试改进 Error 处理以提升内存管理效率

This commit is contained in:
claude-code-best
2026-05-05 18:18:13 +08:00
parent d0915fc880
commit 18d6656a6a
4 changed files with 81 additions and 15 deletions

View File

@@ -340,6 +340,15 @@ export async function* query(
terminal?.reason === 'aborted_tools'
endTrace(langfuseTrace, undefined, isAborted ? 'interrupted' : undefined)
}
// Break the closure chain: toolUseContext captures langfuseTrace which
// holds SpanImpl → otperformance (the 571MB Performance object). Nulling
// these after endTrace allows GC to reclaim the span tree.
if (paramsWithTrace !== params) {
paramsWithTrace.toolUseContext.langfuseTrace = null
paramsWithTrace.toolUseContext.langfuseRootTrace = null
paramsWithTrace.toolUseContext.langfuseBatchSpan = null
}
}
// Only reached if queryLoop returned normally. Skipped on throw (error

View File

@@ -1,22 +1,69 @@
/**
* Shared infrastructure for profiler modules (startupProfiler, queryProfiler,
* headlessProfiler). All three use the same perf_hooks timeline and the same
* line format for detailed reports.
* headlessProfiler).
*
* Uses process.hrtime.bigint() for timing instead of perf_hooks.performance
* to avoid a Bun/JSC memory leak: JSC's Performance object stores marks in a
* C++ Vector that never shrinks even after clearMarks(). Long-running sessions
* (daemon, /loop) accumulate hundreds of MB of dead capacity.
*
* The LightweightPerf class provides the same interface the profilers need
* (mark, getEntriesByType, clearMarks, now) backed by a plain JS Map.
*/
import type { performance as PerformanceType } from 'perf_hooks'
import { formatFileSize } from './format.js'
// Lazy-load performance API only when profiling is enabled.
// Shared across all profilers — perf_hooks.performance is a process-wide singleton.
let performance: typeof PerformanceType | null = null
/** Minimal PerformanceEntry-like object used by profilers */
export interface CheckpointEntry {
readonly name: string
readonly startTime: number
readonly entryType: 'mark'
}
export function getPerformance(): typeof PerformanceType {
if (!performance) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
performance = require('perf_hooks').performance
/**
* Lightweight replacement for perf_hooks.performance that stores marks in a
* plain JavaScript Map instead of JSC's C++ Vector. This avoids the memory
* leak where clearMarks() sets the count to 0 but never frees Vector capacity.
*/
class LightweightPerf {
private marks = new Map<string, number>()
private _origin: number
constructor() {
this._origin = Number(process.hrtime.bigint() / 1000n) / 1000
}
return performance!
mark(name: string): void {
this.marks.set(name, this.now())
}
getEntriesByType(type: 'mark'): CheckpointEntry[] {
if (type !== 'mark') return []
const entries: CheckpointEntry[] = []
for (const [name, startTime] of this.marks) {
entries.push({ name, startTime, entryType: 'mark' })
}
return entries
}
clearMarks(name?: string): void {
if (name !== undefined) {
this.marks.delete(name)
} else {
this.marks.clear()
}
}
now(): number {
return Number(process.hrtime.bigint() / 1000n) / 1000 - this._origin
}
}
// Singleton — shared across all profilers (same as the old perf_hooks singleton)
const perf = new LightweightPerf()
export function getPerformance(): LightweightPerf {
return perf
}
export function formatMs(ms: number): string {