Просмотр исходного кода

tui: remove memory leak fixes documentation after implementation

Dax Raad 1 месяц назад
Родитель
Сommit
b84a1f714b

+ 11 - 6
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

@@ -1447,10 +1447,11 @@ function InlineTool(props: { icon: string; complete: any; pending: string; child
   )
 }
 
-function BlockTool(props: { title: string; children: JSX.Element; onClick?: () => void }) {
+function BlockTool(props: { title: string; children: JSX.Element; onClick?: () => void; part?: ToolPart }) {
   const { theme } = useTheme()
   const renderer = useRenderer()
   const [hover, setHover] = createSignal(false)
+  const error = createMemo(() => (props.part?.state.status === "error" ? props.part.state.error : undefined))
   return (
     <box
       border={["left"]}
@@ -1473,6 +1474,9 @@ function BlockTool(props: { title: string; children: JSX.Element; onClick?: () =
         {props.title}
       </text>
       {props.children}
+      <Show when={error()}>
+        <text fg={theme.error}>{error()}</text>
+      </Show>
     </box>
   )
 }
@@ -1483,7 +1487,7 @@ function Bash(props: ToolProps<typeof BashTool>) {
   return (
     <Switch>
       <Match when={props.metadata.output !== undefined}>
-        <BlockTool title={"# " + (props.input.description ?? "Shell")}>
+        <BlockTool title={"# " + (props.input.description ?? "Shell")} part={props.part}>
           <box gap={1}>
             <text fg={theme.text}>$ {props.input.command}</text>
             <text fg={theme.text}>{output()}</text>
@@ -1514,7 +1518,7 @@ function Write(props: ToolProps<typeof WriteTool>) {
   return (
     <Switch>
       <Match when={props.metadata.diagnostics !== undefined}>
-        <BlockTool title={"# Wrote " + normalizePath(props.input.filePath!)}>
+        <BlockTool title={"# Wrote " + normalizePath(props.input.filePath!)} part={props.part}>
           <line_number fg={theme.textMuted} minWidth={3} paddingRight={1}>
             <code
               conceal={false}
@@ -1629,6 +1633,7 @@ function Task(props: ToolProps<typeof TaskTool>) {
               ? () => navigate({ type: "session", sessionID: props.metadata.sessionId! })
               : undefined
           }
+          part={props.part}
         >
           <box>
             <text style={{ fg: theme.textMuted }}>
@@ -1685,7 +1690,7 @@ function Edit(props: ToolProps<typeof EditTool>) {
   return (
     <Switch>
       <Match when={props.metadata.diff !== undefined}>
-        <BlockTool title={"← Edit " + normalizePath(props.input.filePath!)}>
+        <BlockTool title={"← Edit " + normalizePath(props.input.filePath!)} part={props.part}>
           <box paddingLeft={1}>
             <diff
               diff={diffContent()}
@@ -1735,7 +1740,7 @@ function Patch(props: ToolProps<typeof PatchTool>) {
   return (
     <Switch>
       <Match when={props.output !== undefined}>
-        <BlockTool title="# Patch">
+        <BlockTool title="# Patch" part={props.part}>
           <box>
             <text fg={theme.text}>{props.output?.trim()}</text>
           </box>
@@ -1754,7 +1759,7 @@ function TodoWrite(props: ToolProps<typeof TodoWriteTool>) {
   return (
     <Switch>
       <Match when={props.metadata.todos?.length}>
-        <BlockTool title="# Todos">
+        <BlockTool title="# Todos" part={props.part}>
           <box>
             <For each={props.input.todos ?? []}>
               {(todo) => <TodoItem status={todo.status} content={todo.content} />}

+ 0 - 118
packages/opencode/src/session/MEMORY_LEAK_FIXES.md

@@ -1,118 +0,0 @@
-# Memory Leak Fixes Plan
-
-## Summary
-
-This document outlines the memory leak issues identified in the session module and the proposed fixes.
-
-## Issues Identified
-
-### Issue 1: Instance Dispose Callback Missing Callback Rejection (HIGH)
-
-**File**: `prompt.ts:69-73`
-
-**Problem**: When an instance is disposed, the dispose callback only aborts the AbortControllers but doesn't reject the pending promise callbacks. This leaves hanging promises that never resolve or reject.
-
-**Current Code**:
-
-```typescript
-async (current) => {
-  for (const item of Object.values(current)) {
-    item.abort.abort()
-  }
-},
-```
-
-**Fix**: Add callback rejection in the dispose handler:
-
-```typescript
-async (current) => {
-  for (const item of Object.values(current)) {
-    item.abort.abort()
-    for (const callback of item.callbacks) {
-      callback.reject()
-    }
-  }
-},
-```
-
----
-
-### Issue 2: Abort Listener Not Removed on Timeout (MEDIUM)
-
-**File**: `retry.ts:10-22`
-
-**Problem**: If the timeout resolves before the abort signal fires, the abort event listener remains attached to the signal. While `{ once: true }` ensures it fires only once if aborted, it doesn't remove the listener if the timeout fires first. This causes a minor memory leak for long-lived signals.
-
-**Current Code**:
-
-```typescript
-export async function sleep(ms: number, signal: AbortSignal): Promise<void> {
-  return new Promise((resolve, reject) => {
-    const timeout = setTimeout(resolve, Math.min(ms, RETRY_MAX_DELAY))
-    signal.addEventListener(
-      "abort",
-      () => {
-        clearTimeout(timeout)
-        reject(new DOMException("Aborted", "AbortError"))
-      },
-      { once: true },
-    )
-  })
-}
-```
-
-**Fix**: Store the abort handler and remove it when timeout resolves:
-
-```typescript
-export async function sleep(ms: number, signal: AbortSignal): Promise<void> {
-  return new Promise((resolve, reject) => {
-    const abortHandler = () => {
-      clearTimeout(timeout)
-      reject(new DOMException("Aborted", "AbortError"))
-    }
-    const timeout = setTimeout(
-      () => {
-        signal.removeEventListener("abort", abortHandler)
-        resolve()
-      },
-      Math.min(ms, RETRY_MAX_DELAY),
-    )
-    signal.addEventListener("abort", abortHandler, { once: true })
-  })
-}
-```
-
----
-
-### Issue 3: Orphaned AbortControllers (LOW - Optional)
-
-**Files**:
-
-- `summary.ts:102`, `summary.ts:143`
-- `prompt.ts:884-892`, `prompt.ts:945-953`
-
-**Problem**: New `AbortController()` instances are created inline and passed to functions, but the controllers are never stored or explicitly aborted. While this isn't a significant leak (GC handles them when streams complete), it's a code smell.
-
-**Example**:
-
-```typescript
-abort: new AbortController().signal,
-```
-
-**Recommendation**: Leave as-is. The overhead is minimal and the code is clearer. The streams complete naturally and the objects are garbage collected.
-
----
-
-## Implementation Checklist
-
-- [ ] Fix Issue 1: Add callback rejection in `prompt.ts` dispose handler
-- [ ] Fix Issue 2: Clean up abort listener in `retry.ts` sleep function
-- [ ] (Optional) Issue 3: No action needed
-
-## Testing Notes
-
-After implementing fixes:
-
-1. Verify existing tests pass
-2. Manually test session cancellation during active processing
-3. Verify instance disposal properly cleans up all pending sessions