hs-web/hsweb-framework · error · IllegalStateException
Recycler is closed
Error message
Recycler is closed
What it means
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.
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
Example fix
// before recycler.close(); Recyclable<T> r = recycler.take(true); // after recycler.close(); Recyclable<T> r = recycler.isClosed() ? null : recycler.take(true);
Defensive patterns
Strategy: fallback
Validate before calling
if (recycler.isClosed()) { /* allocate directly */ } Type guard
boolean usable = recycler != null && !recycler.isClosed();
Try / catch
try {
return recycler.take(sync);
} catch (IllegalStateException e) {
if (e.getMessage().contains("Recycler is closed")) return fallbackAlloc();
throw e;
} Prevention
- 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
When it happens
Trigger: A thread first accesses the ThreadLocal pool (via take/doWith/get) after RecyclerImpl.close()/shutdown was called; the lazy ThreadLocal map then tries initialValue().
Common situations: Objects obtained earlier still used by worker threads after application shutdown or recycler close; closing the recycler while reactive operators still borrow pooled objects.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of hs-web/hsweb-framework@b2cfc85a57 (2026-09-13).
Data as JSON: /api/errors/39e4656fd58b753c.
Report an issue: GitHub.
Appendix: source
Thrown at hsweb-core/src/main/java/org/hswebframework/web/recycler/RecyclerImpl.java:60
}
if (rest == null) {
throw new IllegalArgumentException("rest cannot be null");
}
if (destroy == null) {
throw new IllegalArgumentException("destroy cannot be null");
}
this.factory = factory;
this.rest = rest;
this.destroy = destroy;
this.managedResource = managedResource;
this.queue = new MpmcArrayQueue<>(size);
}
@Override
protected ThreadLocalRecyclable<T> initialValue() throws Exception {
if (closed.get()) {
throw new IllegalStateException("Recycler is closed");
}
return new ThreadLocalRecyclable<T>(this, factory.get(), null);
}
@Override
protected void onRemoval(ThreadLocalRecyclable<T> value) {
resetAndDestroy(value.value);
}
private boolean doReset(T val) {
try {
rest.accept(val);
return true;
} catch (Throwable e) {
log.warn("reset object [{}] failed", val, e);
return false;
}
}View on GitHub (pinned to b2cfc85a57)