Architecture
Principle
Use native zsh coproc to create processes, then escape the single-p
limitation by duplicating file descriptors into normal numbered descriptors.
Why not a coproc function?
The first design question was whether a shell function named coproc could
inspect tokens and delegate native forms back to zsh.
That does not work cleanly because zsh parses coproc as a reserved word. A
function cannot sit in front of the reserved word while preserving grammar for
native forms such as coproc { ... } and coproc while ....
Disabling the reserved word lets a function named coproc exist, but then zsh
no longer parses native coprocess grammar. That violates the compatibility goal.
Chosen design
- Scriptable API:
co-proc ... - Optional interactive sugar: a ZLE
accept-linewidget - Token inspection:
${(z)BUFFER}instead of a command-line regex - Conservative rewrite: only simple extended forms are rewritten
FD ownership
co-proc start NAME COMMAND performs:
coproc COMMAND
exec {outfd}<&p
exec {infd}>&p
The numbered descriptors are then owned by the registry entry for NAME.
Cleanup
co-proc stop closes both descriptors, terminates the child, waits for it, and
removes the active registry entry.
co-proc cleanup stops every registered process and is registered with
add-zsh-hook zshexit.
Current limitations
- Complex native-like forms should use native
coprocor explicitco-proc start NAME zsh -c '...'. - The interactive rewrite intentionally refuses lines with redirection, pipes, separators, grouped commands, or control operators.
- zsh still retargets the special
phandle whenever any native coprocess is started. - Shell-local
startentries still use numbered descriptors owned by the sourcing zsh process. Cross-process consumers must explicitly usespawn.
Attachable transport
co-proc spawn adds named channels that independent processes can attach to
without inheriting the registry shell's descriptors. It is an extension, not a
replacement for the existing native-coproc-backed API.
The implementation uses owner-only FIFO pairs beneath a secure per-user runtime
directory and bounded newline-delimited control messages. Frames stay below
PIPE_BUF so simultaneous client writes are atomic. Binary payloads stay out of
band and are referenced by path. co-proc pump uses zselect plus sysread to
drain multiple ready outputs into bounded, supervisor-owned per-name buffers.
See Attachable cross-process IPC for the staged contract.