{"record":{"id":"1b742ae4f7ad3820","repo":"ory/hydra","slug":"errprocesspoolclosed","errorCode":"ErrProcessPoolClosed","errorMessage":"jsonnetsecure: process pool closed","messagePattern":"jsonnetsecure: process pool closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/jsonnetsecure/jsonnet_pool.go","lineNumber":70,"sourceCode":"\t\tClose()\n\t\t// Stat returns a snapshot of the worker pool's statistics.\n\t\tStat() *puddle.Stat\n\t\tprivate()\n\t}\n\tpool struct {\n\t\tpuddle *puddle.Pool[worker]\n\t}\n\tworker struct {\n\t\tcmd    *exec.Cmd\n\t\tstdin  chan<- []byte\n\t\tstdout <-chan string\n\t\tstderr <-chan string\n\t}\n\tcontextKeyType string\n)\n\nvar (\n\tErrProcessPoolClosed = errors.New(\"jsonnetsecure: process pool closed\")\n\n\t_ VM   = (*processPoolVM)(nil)\n\t_ Pool = (*pool)(nil)\n\n\tcontextValuePath contextKeyType = \"argc\"\n\tcontextValueArgs contextKeyType = \"argv\"\n)\n\nfunc NewProcessPool(size int) Pool {\n\tsize = max(5, min(size, math.MaxInt32))\n\tpud, err := puddle.NewPool(&puddle.Config[worker]{\n\t\tMaxSize:     int32(size), //nolint:gosec // disable G115 // because of the previous min/max, 5 <= size <= math.MaxInt32\n\t\tConstructor: newWorker,\n\t\tDestructor:  worker.destroy,\n\t})\n\tif err != nil {\n\t\tpanic(err) // this should never happen, see implementation of puddle.NewPool\n\t}","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/jsonnetsecure/jsonnet_pool.go#L52-L88","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: pool closed while VM still used\npool := jsonnetsecure.NewProcessPool(5)\ndefer pool.Close()\ngo evaluate(vm) // may run after Close -> ErrProcessPoolClosed\n\n// after: wait for evaluations to finish before closing\nvar wg sync.WaitGroup\nwg.Add(1)\ngo func() { defer wg.Done(); evaluate(vm) }()\nwg.Wait()\npool.Close()","handlingStrategy":"try-catch","validationCode":"select {\ncase <-poolClosed:\n    return errors.New(\"jsonnet pool already closed\")\ndefault:\n    // safe to evaluate\n}","typeGuard":null,"tryCatchPattern":"out, err := vm.EvaluateAnonymousSnippet(filename, snippet)\nif errors.Is(err, jsonnetsecure.ErrProcessPoolClosed) {\n    pool = jsonnetsecure.NewProcessPool(size)\n    vm, err = jsonnetsecure.MakeSecureVM(pool)\n    if err == nil {\n        out, err = vm.EvaluateAnonymousSnippet(filename, snippet)\n    }\n}","preventionTips":["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."],"tags":["jsonnet","process-pool","lifecycle","use-after-close"],"backgroundTag":"pool-closed","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}