An optional bounded-concurrency gate for running sandboxed scripts under load.
Every ExBashkit.exec/1 and ExBashkit.Session.exec/2 call blocks a dirty
scheduler thread for the whole duration of the script (the native interpreter
runs synchronously inside a dirty NIF). The dirty pool is bounded — roughly one
thread per scheduler — so if you let untrusted, possibly-slow scripts run with
unbounded concurrency, enough of them in flight at once can occupy every dirty
thread and starve all native work on the node until they time out.
This pool caps how many run concurrently. It hands out a fixed number of
permits; callers over that limit wait in a bounded queue, and callers past
the queue are rejected with {:error, :overloaded} so you can shed load instead
of piling work onto exhausted schedulers. It is opt-in: add it to your
supervision tree and route untrusted execs through it.
children = [
{ExBashkit.Pool, size: 8, max_queue: 100}
]
# Anywhere you'd run an untrusted script:
ExBashkit.Pool.run(fn -> ExBashkit.Session.exec(session, script) end)
#=> {:ok, %ExBashkit.Result{}} | {:error, :overloaded}size and max_queue may also be set in config and are overridden by the
options you pass to the child spec:
config :ex_bashkit, pool_size: 8, pool_max_queue: 100size defaults to System.schedulers_online/0 and max_queue to 50. Pick a
size no larger than your dirty-scheduler budget — keeping it below the
scheduler count leaves headroom for other native work.
Notes
- The script runs in the calling process, not in the pool — the pool only
gates entry, so throughput still scales across
sizeschedulers. - A permit is released when the work returns, raises, or the caller dies: each holder is monitored, so a crashed or killed caller can never leak a slot.
run/1,2blocks (with no call timeout) while queued; it returns only once a permit is granted or the queue is full. A script's own:timeout_msstill bounds how long any one permit is held.- A process must not call
run/1,2reentrantly on a pool it already holds a permit on — that would deadlock waiting for a slot only it could free, so it raises instead. Nested work belongs on a different pool. - The pool is a singleton registered under
:name(defaultExBashkit.Pool). To run several isolated pools, give each a distinct:name.
Summary
Functions
Returns a specification to start this module under a supervisor.
Convenience wrapper: run(pool, fn -> ExBashkit.exec(script) end).
Run fun (a 0-arity function) under a pool permit, returning its value.
Convenience wrapper: run(pool, fn -> ExBashkit.Session.exec(session, script) end).
Start the pool. Options
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Convenience wrapper: run(pool, fn -> ExBashkit.exec(script) end).
Run fun (a 0-arity function) under a pool permit, returning its value.
Blocks while waiting for a permit if the pool is at capacity; returns
{:error, :overloaded} immediately if the wait queue is also full. The permit
is released when fun returns or raises (and automatically if the caller dies).
Convenience wrapper: run(pool, fn -> ExBashkit.Session.exec(session, script) end).
Start the pool. Options:
:name- the registered name (defaultExBashkit.Pool).:size- max concurrent permits (default:config :ex_bashkit, :pool_size, elseSystem.schedulers_online/0).:max_queue- max callers that may wait for a permit before new callers are rejected with{:error, :overloaded}(default:config :ex_bashkit, :pool_max_queue, else50).