bumptech/glide · error · IllegalStateException
Cannot run recovery on a thread other than the work thread!
Error message
Cannot run recovery on a thread other than the work thread!
What it means
Thrown by RecoveryManager.runRecoveryOnWorkThread() when called from a thread whose Looper is not the work-thread Looper. Recovery rewrites/deletes journal entries and files and must run on the single work thread for safety.
Source
Thrown at integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/RecoveryManager.java:35
private final Handler recoveryHandler;
RecoveryManager(
JournaledLruDiskCache diskCache, File diskCacheDir, Journal journal, Looper workLooper) {
this.diskCache = diskCache;
this.journal = journal;
this.diskCacheDir = diskCacheDir;
this.workLooper = workLooper;
recoveryHandler = new Handler(workLooper, new RecoveryCallback());
}
void triggerRecovery() {
recoveryHandler.obtainMessage(MessageIds.RECOVER).sendToTarget();
}
private void runRecoveryOnWorkThread() {
if (!Looper.myLooper().equals(workLooper)) {
throw new IllegalStateException(
"Cannot run recovery on a thread other than the work" + " thread!");
}
recoverPartialWrites();
recoverPartialDeletes();
}
private void recoverPartialDeletes() {
List<String> pendingDeleteKeys = journal.getPendingDeleteKeys();
diskCache.delete(pendingDeleteKeys);
}
private void recoverPartialWrites() {
File[] partialWrites =
diskCacheDir.listFiles(
new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
return filename.endsWith(JournaledLruDiskCache.TEMP_FILE_INDICATOR);View on GitHub (pinned to eb14a895d8)
Solutions
- Use the public disk-cache API; never invoke RecoveryManager methods directly.
- In tests, post recovery work to the work-thread Handler or use provided test utilities.
- Confirm a single disk-cache instance owns the work Looper and it is not shared.
- File a bug if this surfaces during normal usage, since recovery should always be internally scheduled.
Example fix
// before (wrong thread) recoveryManager.runRecoveryOnWorkThread() // direct, off-thread // after (let the library trigger it) diskCache.open() // internally calls triggerRecovery() on the work thread
Defensive patterns
Strategy: validation
Validate before calling
// Only run recovery on the work thread; otherwise let the library schedule it.
if (Looper.myLooper() == workLooper) {
recoveryManager.runRecoveryOnWorkThread()
} else {
error("Recovery must run on the work thread")
} Prevention
- Use the public disk-cache API; recovery is triggered internally via triggerRecovery().
- In tests, post recovery to the work-thread Handler.
- Keep a single RecoveryManager per cache directory.
When it happens
Trigger: runRecoveryOnWorkThread() executes and Looper.myLooper() does not equal workLooper. Normally recovery is posted via triggerRecovery() -> recoveryHandler message; direct invocation off-thread triggers the guard.
Common situations: Calling internal recovery methods directly instead of through the public API; a test that invokes recovery on the wrong thread; reflection triggering recovery off-thread. Should not occur in normal library use because recovery is always message-driven.
Related errors
- Cannot call evictOnWorkThread on thread: {}
- Failed to find entries to evict.
- Size mismatch, expected to be able to evict at least {} byte
- Failed to delete entry, key: {}, size: {}, actually deleted:
- Failed to create cache directory: {}
AI-assisted analysis of bumptech/glide@eb14a895d8 (2026-08-14).
Data as JSON: /api/errors/929b290c7425b761.
Report an issue: GitHub.