Threads sized for the work, not for the default.
Every other thread primitive in this library inherits the JVM's default thread stack, which is fine for Clojure work and quietly fatal for native work that recurses. A native stack overflow is not a StackOverflowError: it is a SIGSEGV that kills the process outright, with no Result, no exception and no hs_err file for anyone to read afterwards.
Measured case (vtranslate, 2026-09-14, linux-x86_64 / JDK 26): ONNX Runtime 1.29 and later recurse deeply while building and optimizing a session graph, on the CALLER's stack. Creating a session on a default-stack thread killed the engine; the same call under -Xss4m succeeded. Older runtimes merely happened to fit, so the crash looked like a version regression it was not.
A launch flag fixes that only for whoever remembers to pass it, and a library
whose whole job is handing work to threads is the place that should not need
remembering: use call-with-stack for a one-shot call, or pass :stack-bytes
to pool/make-pool / parallel/bounded-pmap when the work those threads run
is native.
Threads sized for the work, not for the default. Every other thread primitive in this library inherits the JVM's default thread stack, which is fine for Clojure work and quietly fatal for native work that recurses. A native stack overflow is not a StackOverflowError: it is a SIGSEGV that kills the process outright, with no Result, no exception and no hs_err file for anyone to read afterwards. Measured case (vtranslate, 2026-09-14, linux-x86_64 / JDK 26): ONNX Runtime 1.29 and later recurse deeply while building and optimizing a session graph, on the CALLER's stack. Creating a session on a default-stack thread killed the engine; the same call under -Xss4m succeeded. Older runtimes merely happened to fit, so the crash looked like a version regression it was not. A launch flag fixes that only for whoever remembers to pass it, and a library whose whole job is handing work to threads is the place that should not need remembering: use `call-with-stack` for a one-shot call, or pass `:stack-bytes` to `pool/make-pool` / `parallel/bounded-pmap` when the work those threads run is native.
(call-with-stack f)(call-with-stack stack-bytes f)Invoke f on a fresh thread with a stack-bytes stack and return its value.
A throwable from f is rethrown on the CALLING thread, so this is invisible
to the error handling around it: try/catch and Result guards keep working
exactly as they did. Blocking, so the caller waits for the thread to finish,
and an interrupt of the caller surfaces there as InterruptedException.
One thread per call is the point. This is for a rare, expensive native call
(loading a model, building a session), not for a hot path; for repeated work
build a pool with thread-factory instead.
Invoke `f` on a fresh thread with a `stack-bytes` stack and return its value. A throwable from `f` is rethrown on the CALLING thread, so this is invisible to the error handling around it: try/catch and Result guards keep working exactly as they did. Blocking, so the caller waits for the thread to finish, and an interrupt of the caller surfaces there as InterruptedException. One thread per call is the point. This is for a rare, expensive native call (loading a model, building a session), not for a hot path; for repeated work build a pool with `thread-factory` instead.
16 MiB. -Xss4m was enough for the ONNX session that motivated this; the margin costs nothing, since a stack is reserved address space and only the pages actually touched are committed.
16 MiB. -Xss4m was enough for the ONNX session that motivated this; the margin costs nothing, since a stack is reserved address space and only the pages actually touched are committed.
(thread-factory
{:keys [name stack-bytes daemon?]
:or {name "weave-stack" stack-bytes default-stack-bytes daemon? true}})A ThreadFactory whose threads carry an explicit stack size.
Options:
:name thread-name prefix, <prefix>-<n> (default "weave-stack")
:stack-bytes requested stack size (default default-stack-bytes)
:daemon? daemon threads, so they never block JVM shutdown (default true)
The JVM treats stack size as a HINT: a platform may round it or ignore it entirely. Every platform this library targets honours it.
A ThreadFactory whose threads carry an explicit stack size. Options: :name thread-name prefix, `<prefix>-<n>` (default "weave-stack") :stack-bytes requested stack size (default `default-stack-bytes`) :daemon? daemon threads, so they never block JVM shutdown (default true) The JVM treats stack size as a HINT: a platform may round it or ignore it entirely. Every platform this library targets honours it.
(with-stack & body)Evaluate body on a thread with default-stack-bytes of stack.
=> the body's value; a throwable is rethrown on the caller.
Evaluate `body` on a thread with `default-stack-bytes` of stack. => the body's value; a throwable is rethrown on the caller.
(with-stack-of stack-bytes & body)with-stack with an explicit stack size in bytes.
`with-stack` with an explicit stack size in bytes.
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 |