Skip to content

perf(terminal): optimistic close — instant pane removal, busy check off-thread#145

Merged
matej21 merged 1 commit into
mainfrom
fix/optimistic-terminal-close
Jun 19, 2026
Merged

perf(terminal): optimistic close — instant pane removal, busy check off-thread#145
matej21 merged 1 commit into
mainfrom
fix/optimistic-terminal-close

Conversation

@matej21

@matej21 matej21 commented Jun 18, 2026

Copy link
Copy Markdown
Member

Problem

Closing a terminal had a perceptible few-hundred-ms lag on macOS (not reproducible on Linux).

The grace-period soft-close busy check ran synchronously on the GPUI thread, before the pane was removed from the layout. On macOS that check forks subprocesses:

  • get_foreground_shell_pid → spawns tmux list-panes (tmux backend) or lsof ×2 (dtach backend)
  • has_child_processes → spawns pgrep -P

On Linux these are sub-millisecond /proc reads, so the lag was macOS-only. Each fork+exec on macOS is expensive (dyld, AMFI/codesign), and they sat directly on the close keypath.

Fix

Make every interactive close optimistic:

  1. Eject the pane from the layout immediately (PTY stays alive) — instant, no subprocess work on the GPUI thread.
  2. Run the busy probe off the GPUI thread via smol::unblock.
  3. When it returns: idle → kill the PTY now (no toast); busy → show the undo toast + grace timer (unchanged behaviour).

The end state is identical to before — only the ordering changed — and the UI now updates instantly regardless of session backend (tmux/dtach/plain). Applies to both single (CloseTerminal) and multi (CloseTerminals) close.

Changes

  • okena-workspace: Workspace::decide_pending_closePendingDecision { Raced, Finalized, KeepForUndo }. Pure decision logic, unit-tested; the UI layer only handles the toast.
  • okena-app: soft_close::try_beginbegin (optimistic); extracted build_soft_close_toast. A stable toast id is reserved up front so the toast is posted later only if the terminal turns out busy.
  • okena-state: Toast::with_id.

Races

Unchanged. If the shell exits during the probe or grace window, the existing exit-path cancel_pending_close / reap_restored_close handle it, and decide_pending_close no-ops when the pending record is already gone (Raced).

Tests

  • decide_keeps_busy_terminal_for_undo, decide_finalizes_idle_terminal, decide_is_noop_when_pending_already_gone (okena-workspace).
  • Full workspace build, existing soft-close suite, and clippy all green.

Follow-up PR replaces the macOS subprocess introspection (pgrep/lsof) with libproc syscalls — orthogonal perf/battery win.

🤖 Generated with Claude Code

…ndo off-thread

Closing a terminal had a few-hundred-ms lag on macOS. The grace-period
soft-close busy check ran synchronously on the GPUI thread *before* the pane
was removed from the layout, and on macOS that check forks subprocesses:
`get_foreground_shell_pid` spawns `tmux list-panes` / `lsof` (dtach) and
`has_child_processes` spawns `pgrep -P`. On Linux these are sub-ms `/proc`
reads, so the lag was macOS-only.

Make every interactive close optimistic: eject the pane from the layout
immediately (PTY stays alive), then run the busy probe off the GPUI thread via
`smol::unblock`. Once it returns, idle terminals are killed right away and busy
ones get the undo toast + grace timer — same end state as before, but the UI
updates instantly regardless of session backend.

- okena-workspace: add `Workspace::decide_pending_close` returning
  `PendingDecision` {Raced, Finalized, KeepForUndo} (pure, unit-tested) so the
  kill-vs-undo decision is testable and the UI layer only handles the toast.
- okena-app: `soft_close::try_begin` → `begin` (optimistic); extract
  `build_soft_close_toast`. Reserve a stable toast id up front so the toast can
  be posted later only if the terminal turns out busy.
- okena-state: add `Toast::with_id`.

Race handling (shell exits during the probe / grace window) is unchanged — the
existing exit-path `cancel_pending_close` / `reap_restored_close` cover it, and
`decide_pending_close` no-ops when the pending record is already gone.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019JZRfqVneyJnKjdrtSp3ro
@matej21 matej21 merged commit 8af3482 into main Jun 19, 2026
8 checks passed
@matej21 matej21 deleted the fix/optimistic-terminal-close branch June 19, 2026 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant