Expand description
async_precmd submodule — run precmd-style hooks on the worker pool so they
don’t block prompt rendering (writes into the shared param table).
async_precmd hook — run precmd-style functions on a POOL WORKER THREAD so
they never block prompt rendering.
zsh’s precmd hooks run synchronously before the prompt paints, so a slow
hook stalls the prompt. async_precmd is a new lifecycle hook (no zsh
equivalent): its functions run on the shared worker pool AFTER the prompt is
built/rendered, writing their results into the shared, RwLock-synchronized
global param table. The prompt reads whatever is currently there and never
waits — a slow segment simply updates a prompt or two later.
Registration mirrors zsh’s hook arrays:
- a function literally named
async_precmd, and/or - members of the
async_precmd_functionsarray.
Built on the Phase-1 crate::vm_helper::ShellExecutor::new_worker
lightweight worker executor. No isolation is needed here: async_precmd
WANTS its typeset -g writes to land in the shared table.
§The batch never overlaps shell code on the shell thread
A hook is a shell FUNCTION, and running one moves state C keeps for a
single thread of execution: locallevel (Src/params.c:5837
locallevel++ in startparamscope, :5856 locallevel-- in
endparamscope) and the level-stamped entries of the one paramtab,
which scanendscope (Src/params.c:5904-5907, if (pm->level > locallevel)) restores or deletes on every scope exit. The worker’s
pushes and pops land on the same counter and the same table as the
shell thread’s.
The batch is fired from preprompt(), and the first thing the shell
thread does after the prompt paints is usually run a widget: a
shell-function widget brackets its body with startparamscope(); makezleparams(0); … endparamscope(); (Src/Zle/zle_main.c:1533-1540),
and makezleparams stamps $BUFFER and its family with the scope level
(Src/Zle/zle_params.c:206). A worker scope exit that lands inside that
window deletes the widget’s $BUFFER, and the write-back in
zle_param_sync::sync_from_paramtab then copies the now-empty value
into the editor: every character typed so far on the line disappears.
Measured with a self-insert wrapper (zle -N self-insert f; f() { zle .self-insert }, the shape zpwr’s zpwrSelfInsert has) and a hook that
runs for half a second: most typed lines lost a prefix, and a trace
showed the worker’s endparamscope removing BUFFER at the widget’s
level immediately before the editor was overwritten with “”.
A top-level typeset X=1 typed during the batch was stamped with the
worker’s level and vanished when the hook returned.
So shell code on the shell thread waits for a batch that is already
running (quiesce), and withdraws one the pool has not started yet.
Plain typing is not held up: builtin widgets never open a scope. The
first scope usually opens after the prompt is on screen; a
zle-line-init widget is the exception, because this port’s zleread
runs it before its first zrefresh (C paints first, c:1353, then calls
the hook, c:1357), so a running batch delays that paint by the hook’s
runtime.
Functions§
- fire_
async_ precmd - Fire the
async_precmdhooks on a worker thread. Called frompreprompt()AFTER precmd + prompt render, so the prompt is already on screen. Returns immediately (non-blocking): it submits ONE closure to the shared worker pool and lets it run in the background. Debounced via [BATCH]. - quiesce
- Make the shell thread safe to run shell code: wait for a batch a worker is running, or withdraw one that is only queued (its hooks run on the next prompt instead). Returns at once when no batch is in flight, and on any thread but the shell’s — a worker must never wait on its own batch.
- set_
session_ pool - Publish the session worker pool. Called once from
ShellExecutor::new().