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

  1. Do not call EvaluateAnonymousSnippet after pool.Close(); shut down all users of the VM before closing the pool.
  2. Create a fresh pool with jsonnetsecure.NewProcessPool(size) and rebuild the VM if evaluation is needed again after close.
  3. 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).
  4. 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

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


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/1b742ae4f7ad3820. Report an issue: GitHub.