Commit Graph

2 Commits

Author SHA1 Message Date
Levi Neuwirth 61a31b3ad4 fix(completion): address TUI-validation findings (LSP query gaps, scoping, prefix keys, window scope)
Five findings from the manual validation pass, all in-branch:

1. LSP-only words never queried the server: the auto-open path fired
   request_completion only when the sync providers already produced
   rows. An empty sweep now leaves a pending session and the request
   always fires; isIncomplete responses re-request on further typing.
   Corollary: attachment_for_request now flushes-if-attached but NEVER
   attaches -- the first cut wrapped attached_for_active, which spawns
   servers on demand, i.e. per-keystroke spawn attempts in unattached
   buffers (wedged the parallel m4 suite; serial ran 3x slower).
   Attachment stays buffer-open policy.

2. Cross-buffer LSP leak: the built-in provider's no-uri fallback was
   the legacy global store drain, so scratch/unattached buffers could
   show another file's cached completions. Strict now: no uri, no rows.

3. Pending prefixes own the keyboard: Action::Pending (C-x ...)
   dismisses the popup and the popup shadow is guarded on an empty
   dispatcher prefix, so the sequence's continuation and its C-g abort
   reach the dispatcher instead of the popup.

4. Window-scoped sessions: CompletionPopupState.window_id (stamped by
   completion_popup_open; Lua never sees it). Only the owning window's
   overlay paints -- same-buffer splits each carry a persistent
   overlay -- and a focus change invalidates the session.

5. Flaky worker test: the /proc thread-count probe and the idempotence
   check both build EditorStates and could run concurrently, polluting
   the baseline; merged into one test (non-Linux keeps a portable
   idempotence variant).

Regression tests for 1-4; framing doc gains the as-built notes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 16:54:14 -04:00
Levi Neuwirth a1f5b1ffd7 fix(worker): deterministic pool teardown; EditorState::drop signals workers
Every EditorState leaked its entire worker pool (cores-1 threads, each
waking every 100ms): the Rc<AsyncRuntime> is cloned into dozens of Lua
closures, and registries those closures capture store mlua::Function
values -- reference cycles through the Lua VM that keep the Rc from
ever reaching zero, so WorkerPool::Drop never ran. Harmless for one
editor per process; in the m4 acceptance suite (54 editor-building
tests) it accumulated 1000+ live threads (observed: 60 complete
pmacs-worker-0..14 pools at once) plus ~9k spurious wakeups/second.

Fix: WorkerPool::signal_shutdown() -- set the shutdown flag + wake the
parkers from a shared reference; parked workers exit within their
100ms park timeout. Exposed as AsyncRuntime::shutdown_workers(), called
from a new impl Drop for EditorState. Deliberately signal-only, NO
join on the drop path: a worker can be blocked publishing its reply
onto the bus only the main thread drains, and a first cut that joined
in Drop deadlocked the m4 suite at teardown (fake-LSP tests wedged
2h+). WorkerPool::shutdown() (signal + join) remains for owners with
no bus consumer to deadlock against; Drop delegates to it as before.

Measured on the m4 suite: peak live threads 1026 -> 122.
Regression: tests/worker_shutdown_acceptance.rs (thread-count probe is
/proc-based, Linux-gated; the idempotence test runs everywhere).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:20:53 -04:00