ory/hydra · error
ErrProcessPoolClosed
ErrProcessPoolClosed
Error message
jsonnetsecure: process pool closed
What it means
ErrProcessPoolClosed (oryx/jsonnetsecure/jsonnet_pool.go:70) is the sentinel returned when Jsonnet evaluation is attempted against a process pool whose Close() has already been called. The underlying puddle pool stops creating/acquiring workers after Close, so any subsequent EvaluateAnonymousSnippet acquire fails with this error.
Source
Thrown at oryx/jsonnetsecure/jsonnet_pool.go:70
Close()
// Stat returns a snapshot of the worker pool's statistics.
Stat() *puddle.Stat
private()
}
pool struct {
puddle *puddle.Pool[worker]
}
worker struct {
cmd *exec.Cmd
stdin chan<- []byte
stdout <-chan string
stderr <-chan string
}
contextKeyType string
)
var (
ErrProcessPoolClosed = errors.New("jsonnetsecure: process pool closed")
_ VM = (*processPoolVM)(nil)
_ Pool = (*pool)(nil)
contextValuePath contextKeyType = "argc"
contextValueArgs contextKeyType = "argv"
)
func NewProcessPool(size int) Pool {
size = max(5, min(size, math.MaxInt32))
pud, err := puddle.NewPool(&puddle.Config[worker]{
MaxSize: int32(size), //nolint:gosec // disable G115 // because of the previous min/max, 5 <= size <= math.MaxInt32
Constructor: newWorker,
Destructor: worker.destroy,
})
if err != nil {
panic(err) // this should never happen, see implementation of puddle.NewPool
}View on GitHub (pinned to 4174065ffb)
Solutions
- Do not call EvaluateAnonymousSnippet after pool.Close(); shut down all users of the VM before closing the pool.
- Create a fresh pool with jsonnetsecure.NewProcessPool(size) and rebuild the VM if evaluation is needed again after close.
- Check with pool.Stat() / lifecycle logging that Close() is not invoked earlier than intended (e.g. an early return or defer in the wrong scope).
- Detect the sentinel with errors.Is(err, jsonnetsecure.ErrProcessPoolClosed) and route to a restart/reinit path.
Example fix
// before: pool closed while VM still used
pool := jsonnetsecure.NewProcessPool(5)
defer pool.Close()
go evaluate(vm) // may run after Close -> ErrProcessPoolClosed
// after: wait for evaluations to finish before closing
var wg sync.WaitGroup
wg.Add(1)
go func() { defer wg.Done(); evaluate(vm) }()
wg.Wait()
pool.Close() Defensive patterns
Strategy: try-catch
Validate before calling
select {
case <-poolClosed:
return errors.New("jsonnet pool already closed")
default:
// safe to evaluate
} Try / catch
out, err := vm.EvaluateAnonymousSnippet(filename, snippet)
if errors.Is(err, jsonnetsecure.ErrProcessPoolClosed) {
pool = jsonnetsecure.NewProcessPool(size)
vm, err = jsonnetsecure.MakeSecureVM(pool)
if err == nil {
out, err = vm.EvaluateAnonymousSnippet(filename, snippet)
}
} Prevention
- Ensure pool.Close() runs only after all in-flight evaluations complete (WaitGroup or request draining).
- Keep pool and VM lifecycles coupled: close pool after the VM's last use.
- Avoid reusing an application-scoped pool across shutdown/reload boundaries without rebuilding the VM.
- Track closed state explicitly and reject new evaluations early with a clear message.
When it happens
Trigger: Calling pool.Close() (directly or via defer/shutdown hooks) and afterwards using the same Pool to MakeSecureVM/EvaluateAnonymousSnippet; using a pool retrieved from a shut-down component (e.g. after server graceful shutdown); keeping the VM alive beyond the pool's lifetime.
Common situations: Defer-ordering bugs where the pool closes before in-flight requests finish; reusing an application-scoped pool after a config reload rebuilds and closes it; tests that close the pool in cleanup while async evaluations still run.
Related errors
- newWorker: missing binary path in context
- ErrNoProcessPool
- errors.New(err)
- jsonnetsecure: " + result
- import not available %v
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/1b742ae4f7ad3820.
Report an issue: GitHub.