Reading a child process's output without blocking on its descendants.
EOF on a process stream arrives when the LAST holder of the write end closes it, which is not when the command exits: a backgrounded grandchild inherits the pipe and holds it open. Every read here is therefore abandonable — it runs on a daemon thread and is collected with a BOUNDED deref, so a detached descendant cannot extend its caller's deadline.
Leaf namespace: java.io and clojure.java.io only. No hive-weave, so it loads under Babashka.
Reading a child process's output without blocking on its descendants. EOF on a process stream arrives when the LAST holder of the write end closes it, which is not when the command exits: a backgrounded grandchild inherits the pipe and holds it open. Every read here is therefore abandonable — it runs on a daemon thread and is collected with a BOUNDED deref, so a detached descendant cannot extend its caller's deadline. Leaf namespace: java.io and clojure.java.io only. No hive-weave, so it loads under Babashka.
(drain is)Read is on a daemon thread. Returns a promise of the string.
Collect it with a BOUNDED deref — (deref p flush-grace-ms ::open) — so a descendant still holding the pipe cannot block the caller. A drain left uncollected dies with the JVM rather than holding it open.
Read `is` on a daemon thread. Returns a promise of the string. Collect it with a BOUNDED deref — (deref p flush-grace-ms ::open) — so a descendant still holding the pipe cannot block the caller. A drain left uncollected dies with the JVM rather than holding it open.
(drain-lines-capped is limit max-bytes)read-lines-capped on a daemon thread. Returns a promise of its map.
Collect with a BOUNDED deref, for the reason drain gives: a descendant
holding the inherited pipe must not extend the caller's deadline.
`read-lines-capped` on a daemon thread. Returns a promise of its map. Collect with a BOUNDED deref, for the reason `drain` gives: a descendant holding the inherited pipe must not extend the caller's deadline.
How long a stream drain is given once the command itself has exited.
How long a stream drain is given once the command itself has exited.
(pump from-out to-in)Copy from-out into to-in on a daemon thread, closing to-in at EOF so
the downstream process sees the end of its input.
Returns a promise of :done, or of the Throwable that stopped the copy — a pump that died silently would look exactly like one that finished.
Copy `from-out` into `to-in` on a daemon thread, closing `to-in` at EOF so the downstream process sees the end of its input. Returns a promise of :done, or of the Throwable that stopped the copy — a pump that died silently would look exactly like one that finished.
(read-lines-capped is limit max-bytes)Read at most LIMIT lines from is, and at most MAX-BYTES of them.
Returns {:lines […] :truncated? bool :reason …}.
A caller handed exactly LIMIT lines cannot tell a command that printed LIMIT
from one that printed more, and those are different answers — the same
reason fs.core/find-files refuses rather than returning a partial walk.
So one extra line is read purely to decide :truncated?, and discarded; it
is never returned.
That extra read blocks while a slow producer is still computing, which is
the honest behaviour: until the next line arrives or the stream closes,
is there more? genuinely has no answer yet. Bound it with a deadline —
drain-lines-capped is the abandonable form.
A line budget does not bound memory: one pathological line exhausts the heap while the line count sits at 1. Time, admission and output cardinality are independent budgets, and so are the two dimensions of output.
:reason names which bound stopped it — :eof, :max-lines or :max-bytes.
Read at most LIMIT lines from `is`, and at most MAX-BYTES of them.
Returns {:lines […] :truncated? bool :reason …}.
## Why it reads one line PAST the limit
A caller handed exactly LIMIT lines cannot tell a command that printed LIMIT
from one that printed more, and those are different answers — the same
reason `fs.core/find-files` refuses rather than returning a partial walk.
So one extra line is read purely to decide `:truncated?`, and discarded; it
is never returned.
That extra read blocks while a slow producer is still computing, which is
the honest behaviour: until the next line arrives or the stream closes,
`is there more?` genuinely has no answer yet. Bound it with a deadline —
`drain-lines-capped` is the abandonable form.
## Why bytes as well as lines
A line budget does not bound memory: one pathological line exhausts the heap
while the line count sits at 1. Time, admission and output cardinality are
independent budgets, and so are the two dimensions of output.
`:reason` names which bound stopped it — :eof, :max-lines or :max-bytes.(read-stream is)Drain is to a string, blocking until EOF. A closed or faulting stream
yields "".
This blocks for as long as ANY holder keeps the write end open. Call it
directly only where there is no deadline; otherwise use drain.
Drain `is` to a string, blocking until EOF. A closed or faulting stream yields "". This blocks for as long as ANY holder keeps the write end open. Call it directly only where there is no deadline; otherwise use `drain`.
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 |