mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-23 16: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_TIMEOUT_MS = 500;
|
||||||
const MULTI_CLICK_DISTANCE = 1;
|
const MULTI_CLICK_DISTANCE = 1;
|
||||||
|
|
||||||
|
type ErrorInfo = {
|
||||||
|
readonly message: string;
|
||||||
|
readonly stack?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
readonly error?: Error;
|
readonly error?: ErrorInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Root component for all Ink apps
|
// Root component for all Ink apps
|
||||||
@@ -142,7 +147,7 @@ export default class App extends PureComponent<Props, State> {
|
|||||||
static displayName = 'InternalApp';
|
static displayName = 'InternalApp';
|
||||||
|
|
||||||
static getDerivedStateFromError(error: Error) {
|
static getDerivedStateFromError(error: Error) {
|
||||||
return { error };
|
return { error: { message: error.message, stack: error.stack } };
|
||||||
}
|
}
|
||||||
|
|
||||||
override state = {
|
override state = {
|
||||||
@@ -221,7 +226,7 @@ export default class App extends PureComponent<Props, State> {
|
|||||||
<TerminalFocusProvider>
|
<TerminalFocusProvider>
|
||||||
<ClockProvider>
|
<ClockProvider>
|
||||||
<CursorDeclarationContext.Provider value={this.props.onCursorDeclaration ?? (() => {})}>
|
<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>
|
</CursorDeclarationContext.Provider>
|
||||||
</ClockProvider>
|
</ClockProvider>
|
||||||
</TerminalFocusProvider>
|
</TerminalFocusProvider>
|
||||||
|
|||||||
@@ -23,8 +23,13 @@ function getStackUtils(): StackUtils {
|
|||||||
|
|
||||||
/* eslint-enable custom-rules/no-process-cwd */
|
/* eslint-enable custom-rules/no-process-cwd */
|
||||||
|
|
||||||
|
type ErrorLike = {
|
||||||
|
readonly message: string;
|
||||||
|
readonly stack?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
readonly error: Error;
|
readonly error: ErrorLike;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ErrorOverview({ error }: Props) {
|
export default function ErrorOverview({ error }: Props) {
|
||||||
|
|||||||
@@ -340,6 +340,15 @@ export async function* query(
|
|||||||
terminal?.reason === 'aborted_tools'
|
terminal?.reason === 'aborted_tools'
|
||||||
endTrace(langfuseTrace, undefined, isAborted ? 'interrupted' : undefined)
|
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
|
// Only reached if queryLoop returned normally. Skipped on throw (error
|
||||||
|
|||||||
@@ -1,22 +1,69 @@
|
|||||||
/**
|
/**
|
||||||
* Shared infrastructure for profiler modules (startupProfiler, queryProfiler,
|
* Shared infrastructure for profiler modules (startupProfiler, queryProfiler,
|
||||||
* headlessProfiler). All three use the same perf_hooks timeline and the same
|
* headlessProfiler).
|
||||||
* line format for detailed reports.
|
*
|
||||||
|
* 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'
|
import { formatFileSize } from './format.js'
|
||||||
|
|
||||||
// Lazy-load performance API only when profiling is enabled.
|
/** Minimal PerformanceEntry-like object used by profilers */
|
||||||
// Shared across all profilers — perf_hooks.performance is a process-wide singleton.
|
export interface CheckpointEntry {
|
||||||
let performance: typeof PerformanceType | null = null
|
readonly name: string
|
||||||
|
readonly startTime: number
|
||||||
|
readonly entryType: 'mark'
|
||||||
|
}
|
||||||
|
|
||||||
export function getPerformance(): typeof PerformanceType {
|
/**
|
||||||
if (!performance) {
|
* Lightweight replacement for perf_hooks.performance that stores marks in a
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
* plain JavaScript Map instead of JSC's C++ Vector. This avoids the memory
|
||||||
performance = require('perf_hooks').performance
|
* 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 {
|
export function formatMs(ms: number): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user