mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
feat: 尝试改进 Error 处理以提升内存管理效率
This commit is contained in:
@@ -131,8 +131,13 @@ type Props = {
|
||||
const MULTI_CLICK_TIMEOUT_MS = 500;
|
||||
const MULTI_CLICK_DISTANCE = 1;
|
||||
|
||||
type ErrorInfo = {
|
||||
readonly message: string;
|
||||
readonly stack?: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
readonly error?: Error;
|
||||
readonly error?: ErrorInfo;
|
||||
};
|
||||
|
||||
// Root component for all Ink apps
|
||||
@@ -142,7 +147,7 @@ export default class App extends PureComponent<Props, State> {
|
||||
static displayName = 'InternalApp';
|
||||
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
return { error };
|
||||
return { error: { message: error.message, stack: error.stack } };
|
||||
}
|
||||
|
||||
override state = {
|
||||
@@ -221,7 +226,7 @@ export default class App extends PureComponent<Props, State> {
|
||||
<TerminalFocusProvider>
|
||||
<ClockProvider>
|
||||
<CursorDeclarationContext.Provider value={this.props.onCursorDeclaration ?? (() => {})}>
|
||||
{this.state.error ? <ErrorOverview error={this.state.error as Error} /> : this.props.children}
|
||||
{this.state.error ? <ErrorOverview error={this.state.error} /> : this.props.children}
|
||||
</CursorDeclarationContext.Provider>
|
||||
</ClockProvider>
|
||||
</TerminalFocusProvider>
|
||||
|
||||
@@ -23,8 +23,13 @@ function getStackUtils(): StackUtils {
|
||||
|
||||
/* eslint-enable custom-rules/no-process-cwd */
|
||||
|
||||
type ErrorLike = {
|
||||
readonly message: string;
|
||||
readonly stack?: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
readonly error: Error;
|
||||
readonly error: ErrorLike;
|
||||
};
|
||||
|
||||
export default function ErrorOverview({ error }: Props) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user