nostra13/Android-Universal-Image-Loader · error · IllegalStateException
cache is closed
Error message
cache is closed
What it means
IllegalStateException from DiskLruCache.checkNotClosed(), invoked by get/edit/remove/flush and other operations. Closing the cache nulls the journal writer, and any subsequent operation on the closed instance is a usage error, not a data error — the object is deliberately rendered inert.
Source
Thrown at library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/DiskLruCache.java:642
redundantOpCount++;
journalWriter.append(REMOVE + ' ' + key + '\n');
lruEntries.remove(key);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
/** Returns true if this cache has been closed. */
public synchronized boolean isClosed() {
return journalWriter == null;
}
private void checkNotClosed() {
if (journalWriter == null) {
throw new IllegalStateException("cache is closed");
}
}
/** Force buffered operations to the filesystem. */
public synchronized void flush() throws IOException {
checkNotClosed();
trimToSize();
trimToFileCount();
journalWriter.flush();
}
/** Closes this cache. Stored values will remain on the filesystem. */
public synchronized void close() throws IOException {
if (journalWriter == null) {
return; // Already closed.
}
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
if (entry.currentEditor != null) {View on GitHub (pinned to ba33ec64d0)
Solutions
- Synchronize lifecycle: guarantee no cache operations are in flight before close() (drain executors / cancel tasks first).
- Guard call sites with if (!cache.isClosed()) inside the same lock scope, or wrap operations in try/catch for IllegalStateException and re-init the cache.
- Keep a single owner for the cache instance and let it serialize open/close vs. get/edit via one lock.
Example fix
// before
// thread A // thread B
cache.close(); Snapshot s = cache.get(key); // IllegalStateException
// after
// single lifecycle owner:
synchronized (cacheLock) {
if (cache != null && !cache.isClosed()) {
Snapshot s = cache.get(key);
}
}
// close only after workers are cancelled/joined:
executor.shutdownNow();
awaitTermination;
synchronized (cacheLock) { cache.close(); cache = null; } Defensive patterns
Strategy: validation
Validate before calling
synchronized (cacheLock) {
if (cache == null || cache.isClosed()) return; // or lazily reopen
Snapshot s = cache.get(key);
// ...
} Try / catch
try {
value = cache.get(key);
} catch (IllegalStateException closed) {
// closed concurrently: reopen or skip; data is intact on disk
cache = reopenCache(dir);
value = cache.get(key);
} Prevention
- Own the cache with a single lifecycle manager; close it only after load/save executors are drained.
- Check isClosed() inside the same lock you use for close(), so check-then-use is atomic.
- In onDestroy-style shutdowns, cancel in-flight image tasks before tearing down the disk cache.
When it happens
Trigger: Calling cache.get(key), cache.edit(key), cache.remove(key), or cache.flush() after cache.close() (or cache.delete(), which closes first). Commonly a race where one thread closes the cache (e.g. in onDestroy) while a worker thread is still saving images.
Common situations: Calling ImageLoader.getInstance().clearDiskCache() or destroying the component that owns the DiskLruCache while async loads are in flight; double-managed lifecycle where close() is called twice with work queued behind it; unit tests that close the cache in tearDown before a background save finishes.
Related errors
- Newly created entry didn't create value for index {i}
- maxSize <= 0
- maxFileCount <= 0
- valueCount <= 0
- unexpected journal header: [{magic}, {version}, {valueCountS
AI-assisted analysis of nostra13/Android-Universal-Image-Loader@ba33ec64d0 (2026-08-14).
Data as JSON: /api/errors/dc2358cdb25feb85.
Report an issue: GitHub.