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

  1. Synchronize lifecycle: guarantee no cache operations are in flight before close() (drain executors / cancel tasks first).
  2. 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.
  3. 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

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


AI-assisted analysis of nostra13/Android-Universal-Image-Loader@ba33ec64d0 (2026-08-14). Data as JSON: /api/errors/dc2358cdb25feb85. Report an issue: GitHub.