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

  1. Use the public disk-cache API; never invoke RecoveryManager methods directly.
  2. In tests, post recovery work to the work-thread Handler or use provided test utilities.
  3. Confirm a single disk-cache instance owns the work Looper and it is not shared.
  4. 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

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


AI-assisted analysis of bumptech/glide@eb14a895d8 (2026-08-14). Data as JSON: /api/errors/929b290c7425b761. Report an issue: GitHub.