{"record":{"id":"dc2358cdb25feb85","repo":"nostra13/Android-Universal-Image-Loader","slug":"cache-is-closed","errorCode":null,"errorMessage":"cache is closed","messagePattern":"cache is closed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/DiskLruCache.java","lineNumber":642,"sourceCode":"\t\tredundantOpCount++;\n\t\tjournalWriter.append(REMOVE + ' ' + key + '\\n');\n\t\tlruEntries.remove(key);\n\n\t\tif (journalRebuildRequired()) {\n\t\t\texecutorService.submit(cleanupCallable);\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/** Returns true if this cache has been closed. */\n\tpublic synchronized boolean isClosed() {\n\t\treturn journalWriter == null;\n\t}\n\n\tprivate void checkNotClosed() {\n\t\tif (journalWriter == null) {\n\t\t\tthrow new IllegalStateException(\"cache is closed\");\n\t\t}\n\t}\n\n\t/** Force buffered operations to the filesystem. */\n\tpublic synchronized void flush() throws IOException {\n\t\tcheckNotClosed();\n\t\ttrimToSize();\n\t\ttrimToFileCount();\n\t\tjournalWriter.flush();\n\t}\n\n\t/** Closes this cache. Stored values will remain on the filesystem. */\n\tpublic synchronized void close() throws IOException {\n\t\tif (journalWriter == null) {\n\t\t\treturn; // Already closed.\n\t\t}\n\t\tfor (Entry entry : new ArrayList<Entry>(lruEntries.values())) {\n\t\t\tif (entry.currentEditor != null) {","sourceCodeStart":624,"sourceCodeEnd":660,"githubUrl":"https://github.com/nostra13/Android-Universal-Image-Loader/blob/ba33ec64d0daaa881d35852460e78c58d086bc18/library/src/main/java/com/nostra13/universalimageloader/cache/disc/impl/ext/DiskLruCache.java#L624-L660","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n// thread A                    // thread B\ncache.close();                  Snapshot s = cache.get(key); // IllegalStateException\n\n// after\n// single lifecycle owner:\nsynchronized (cacheLock) {\n    if (cache != null && !cache.isClosed()) {\n        Snapshot s = cache.get(key);\n    }\n}\n// close only after workers are cancelled/joined:\nexecutor.shutdownNow();\nawaitTermination;\nsynchronized (cacheLock) { cache.close(); cache = null; }","handlingStrategy":"validation","validationCode":"synchronized (cacheLock) {\n    if (cache == null || cache.isClosed()) return; // or lazily reopen\n    Snapshot s = cache.get(key);\n    // ...\n}","typeGuard":null,"tryCatchPattern":"try {\n    value = cache.get(key);\n} catch (IllegalStateException closed) {\n    // closed concurrently: reopen or skip; data is intact on disk\n    cache = reopenCache(dir);\n    value = cache.get(key);\n}","preventionTips":["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."],"tags":["disk-cache","lru","lifecycle","concurrency","illegal-state"],"backgroundTag":null,"analyzedSha":"ba33ec64d0daaa881d35852460e78c58d086bc18","analyzedAt":"2026-08-14T15:41:15.893Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}