{"record":{"id":"39e4656fd58b753c","repo":"hs-web/hsweb-framework","slug":"recycler-is-closed","errorCode":null,"errorMessage":"Recycler is closed","messagePattern":"Recycler is closed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hsweb-core/src/main/java/org/hswebframework/web/recycler/RecyclerImpl.java","lineNumber":60,"sourceCode":"        }\n        if (rest == null) {\n            throw new IllegalArgumentException(\"rest cannot be null\");\n        }\n        if (destroy == null) {\n            throw new IllegalArgumentException(\"destroy cannot be null\");\n        }\n\n        this.factory = factory;\n        this.rest = rest;\n        this.destroy = destroy;\n        this.managedResource = managedResource;\n        this.queue = new MpmcArrayQueue<>(size);\n    }\n\n    @Override\n    protected ThreadLocalRecyclable<T> initialValue() throws Exception {\n        if (closed.get()) {\n            throw new IllegalStateException(\"Recycler is closed\");\n        }\n        return new ThreadLocalRecyclable<T>(this, factory.get(), null);\n    }\n\n    @Override\n    protected void onRemoval(ThreadLocalRecyclable<T> value) {\n        resetAndDestroy(value.value);\n    }\n\n    private boolean doReset(T val) {\n        try {\n            rest.accept(val);\n            return true;\n        } catch (Throwable e) {\n            log.warn(\"reset object [{}] failed\", val, e);\n            return false;\n        }\n    }","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/hs-web/hsweb-framework/blob/b2cfc85a57c70bf5b5cf6e7edae2d37102652ec8/hsweb-core/src/main/java/org/hswebframework/web/recycler/RecyclerImpl.java#L42-L78","documentation":"IllegalStateException raised from RecyclerImpl.initialValue (the ThreadLocal initialization hook) when the recycler's closed flag has been set. It fires when a thread first tries to obtain a pooled object via get()/withRecycled after the Recycler was closed; since closed recyclers destroy all pooled resources, no new instance can be created, so any late acquisition of a resource from this recycler fails fast with this error.","triggerScenarios":"A thread first accesses the ThreadLocal pool (via take/doWith/get) after RecyclerImpl.close()/shutdown was called; the lazy ThreadLocal map then tries initialValue().","commonSituations":"Objects obtained earlier still used by worker threads after application shutdown or recycler close; closing the recycler while reactive operators still borrow pooled objects.","solutions":["Stop using the recycler and drain/release all borrowed objects before closing","Guard usage with Recycler's closed/alive check before take/doWith","Create a new Recycler instance after closing the old one","Fix lifecycle ordering so close() runs only after all tasks complete"],"exampleFix":"// before\nrecycler.close();\nRecyclable<T> r = recycler.take(true);\n// after\nrecycler.close();\nRecyclable<T> r = recycler.isClosed() ? null : recycler.take(true);","handlingStrategy":"fallback","validationCode":"if (recycler.isClosed()) { /* allocate directly */ }","typeGuard":"boolean usable = recycler != null && !recycler.isClosed();","tryCatchPattern":"try {\n    return recycler.take(sync);\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"Recycler is closed\")) return fallbackAlloc();\n    throw e;\n}","preventionTips":["Close the recycler only after all borrower threads finish","Track lifecycle: init -> use -> drain -> close","Never reuse a closed recycler; create a new one","Add shutdown ordering tests"],"tags":["lifecycle","resource-pool","concurrency"],"backgroundTag":"invalid-state-transition","analyzedSha":"b2cfc85a57c70bf5b5cf6e7edae2d37102652ec8","analyzedAt":"2026-09-13T09:05:02.172Z","contentChangedAt":"2026-09-13T09:05:02.172Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}