{"record":{"id":"929b290c7425b761","repo":"bumptech/glide","slug":"cannot-run-recovery-on-a-thread-other-than-the-wor","errorCode":null,"errorMessage":"Cannot run recovery on a thread other than the work thread!","messagePattern":"Cannot run recovery on a thread other than the work thread!","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/RecoveryManager.java","lineNumber":35,"sourceCode":"  private final Handler recoveryHandler;\n\n  RecoveryManager(\n      JournaledLruDiskCache diskCache, File diskCacheDir, Journal journal, Looper workLooper) {\n    this.diskCache = diskCache;\n    this.journal = journal;\n    this.diskCacheDir = diskCacheDir;\n    this.workLooper = workLooper;\n\n    recoveryHandler = new Handler(workLooper, new RecoveryCallback());\n  }\n\n  void triggerRecovery() {\n    recoveryHandler.obtainMessage(MessageIds.RECOVER).sendToTarget();\n  }\n\n  private void runRecoveryOnWorkThread() {\n    if (!Looper.myLooper().equals(workLooper)) {\n      throw new IllegalStateException(\n          \"Cannot run recovery on a thread other than the work\" + \" thread!\");\n    }\n    recoverPartialWrites();\n    recoverPartialDeletes();\n  }\n\n  private void recoverPartialDeletes() {\n    List<String> pendingDeleteKeys = journal.getPendingDeleteKeys();\n    diskCache.delete(pendingDeleteKeys);\n  }\n\n  private void recoverPartialWrites() {\n    File[] partialWrites =\n        diskCacheDir.listFiles(\n            new FilenameFilter() {\n              @Override\n              public boolean accept(File dir, String filename) {\n                return filename.endsWith(JournaledLruDiskCache.TEMP_FILE_INDICATOR);","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/bumptech/glide/blob/eb14a895d8f866e6a08c3e19d50ea3bd2c12c20a/integration/sqljournaldiskcache/src/main/java/com/bumptech/glide/integration/sqljournaldiskcache/RecoveryManager.java#L17-L53","documentation":"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.","triggerScenarios":"runRecoveryOnWorkThread() executes and Looper.myLooper() does not equal workLooper. Normally recovery is posted via triggerRecovery() -> recoveryHandler message; direct invocation off-thread triggers the guard.","commonSituations":"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.","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."],"exampleFix":"// before (wrong thread)\nrecoveryManager.runRecoveryOnWorkThread() // direct, off-thread\n\n// after (let the library trigger it)\ndiskCache.open() // internally calls triggerRecovery() on the work thread","handlingStrategy":"validation","validationCode":"// Only run recovery on the work thread; otherwise let the library schedule it.\nif (Looper.myLooper() == workLooper) {\n  recoveryManager.runRecoveryOnWorkThread()\n} else {\n  error(\"Recovery must run on the work thread\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["sqljournaldiskcache","integration","threading","looper","recovery","runtime"],"backgroundTag":null,"analyzedSha":"eb14a895d8f866e6a08c3e19d50ea3bd2c12c20a","analyzedAt":"2026-08-14T01:43:54.821Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}