Pure-Java pseudo-terminal for shell_bg — NO JNA, NO extracted native helper,
NO external tmux. Everything is a java.lang.foreign (Panama FFM) downcall
into the platform libc, so it survives GraalVM native-image the same way the
rest of vis's FFM surface (fff / rift / ruff / tree-sitter) does.
Why FFM and not pty4j:
libpty that it extracts to
a temp dir at runtime — two native-image headaches (reflection metadata for
JNA + a resource-extraction dance for the .dylib/.so).forkpty then execve in the
child — does NOT work from a JVM: invoking an FFM downcall MethodHandle after
fork() is not async-signal-safe and SIGBUSes the child. (Verified.)posix_spawn sidesteps that entirely: libc does the fork+exec ATOMICALLY in
native code, so vis only ever issues ONE parent-side downcall and never runs
any JVM code in the child. Paired with openpty (master/slave fds) + a
dup2 of the slave onto the child's 0/1/2, the child gets a real TTY:
isatty() is true, $TERM is honoured, stdin is writable.Public surface — spawn! returns a PLAIN MAP (not a java.lang.Process; a
runtime proxy/gen-class would break native-image), shaped for the
internal.foundation.shell background pump:
{:pid <long> OS pid (a genuine child — ProcessHandle/of
works, unlike a pty4j process)
:in <java.io.InputStream> master-fd reader (a real piped stream)
:send (fn [^bytes b]) write bytes to the master (the stdin channel)
:wait (fn [] <int>) block until exit, reap, return the exit code
:alive? (fn [] <bool>)
:destroy (fn [force?]) SIGTERM (false) / SIGKILL (true) the child}
Pure-Java pseudo-terminal for `shell_bg` — NO JNA, NO extracted native helper,
NO external `tmux`. Everything is a `java.lang.foreign` (Panama FFM) downcall
into the platform libc, so it survives GraalVM native-image the same way the
rest of vis's FFM surface (fff / rift / ruff / tree-sitter) does.
Why FFM and not pty4j:
- pty4j drags in JNA *and* ships its own compiled `libpty` that it extracts to
a temp dir at runtime — two native-image headaches (reflection metadata for
JNA + a resource-extraction dance for the .dylib/.so).
- The obvious pure-FFM shortcut — call libc `forkpty` then `execve` in the
child — does NOT work from a JVM: invoking an FFM downcall MethodHandle after
`fork()` is not async-signal-safe and SIGBUSes the child. (Verified.)
- `posix_spawn` sidesteps that entirely: libc does the fork+exec ATOMICALLY in
native code, so vis only ever issues ONE parent-side downcall and never runs
any JVM code in the child. Paired with `openpty` (master/slave fds) + a
`dup2` of the slave onto the child's 0/1/2, the child gets a real TTY:
`isatty()` is true, `$TERM` is honoured, stdin is writable.
Public surface — `spawn!` returns a PLAIN MAP (not a `java.lang.Process`; a
runtime `proxy`/`gen-class` would break native-image), shaped for the
`internal.foundation.shell` background pump:
{:pid <long> OS pid (a genuine child — `ProcessHandle/of`
works, unlike a pty4j process)
:in <java.io.InputStream> master-fd reader (a real piped stream)
:send (fn [^bytes b]) write bytes to the master (the stdin channel)
:wait (fn [] <int>) block until exit, reap, return the exit code
:alive? (fn [] <bool>)
:destroy (fn [force?]) SIGTERM (false) / SIGKILL (true) the child}(spawn! {:keys [command dir env cols rows] :or {cols 120 rows 40}})Spawn command (a vector of program + args) under a real pseudo-terminal.
Options: :dir (working dir string), :env (Map string->string), :cols, :rows.
Returns the handle map documented on the namespace.
Spawn `command` (a vector of program + args) under a real pseudo-terminal. Options: :dir (working dir string), :env (Map string->string), :cols, :rows. Returns the handle map documented on the namespace.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |