Skip to content

[Detail Bug] Storage: Fallback chain can hang app when multiple backends are unavailable #166

@detail-app

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_ea7bf3e3-a2f4-4402-9351-baa0e1eaa1f5/bugs/bug_c4647461-cec0-4024-8b8a-fef399e1a901

Summary

  • Context: StorageManager manages multiple storage backends (cookies, localStorage, sessionStorage, memory) and provides a fallback mechanism in case a requested storage type is not available in the current environment.
  • Bug: The getStorage method contains an infinite loop because the fallback logic uses the original, unchanging type parameter to find the index of the next storage to try.
  • Actual vs. expected: When a storage type is unavailable, the code should iterate through the TYPES fallback chain until it finds an available one; instead, it repeatedly attempts to create the same fallback storage type (e.g., always trying localStorage if cookieStorage is unavailable).
  • Impact: Severe. If both the requested storage and its immediate fallback are unavailable (common in certain privacy-restricted browser settings), the application will enter an infinite loop, hanging the process and eventually crashing with a JavaScript heap out of memory (OOM) error.

Code with Bug

  getStorage(type: StorageType): IStorage {
    if (!this.storages.has(type)) {
      let storage = this.createStorage(type);
      // If storage is not available, try next
      while (!storage.isAvailable()) {
        const index = TYPES.indexOf(type); // <-- BUG 🔴 type never changes, so index is always the same
        logger.warn(
          `Storage ${type} is not available, trying ${TYPES[index + 1]}`
        );
        storage = this.createStorage(TYPES[index + 1]); // <-- BUG 🔴 always retries the same fallback type
      }

      // Add to cache
      this.storages.set(type, storage);
    }
    return this.storages.get(type)!;
  }

Explanation

The while loop is intended to advance through the TYPES fallback list until an available storage is found. However, it computes index from the original type parameter each iteration, and never updates type (or a separate “current type” variable). If the first fallback (TYPES[index + 1]) is also unavailable, the loop repeats forever, continually recreating and checking the same fallback storage.

Failing Test

it("should fall into an infinite loop when fallback storage is also unavailable", function () {
  this.timeout(10000);
  sinon.stub(CookieStorage.prototype, "isAvailable").returns(false);
  sinon.stub(WebStorage.prototype, "isAvailable").returns(false);

  // This hangs and crashes the process
  storageManager.getStorage("cookieStorage");
});

Test output:

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

Recommended Fix

Track the current storage type being attempted (e.g., currentType) and advance it on each fallback step; also guard against reaching the end of TYPES.

History

This bug was introduced in commit 72186ce. This commit refactored the storage management into a centralized StorageManager with a fallback mechanism, but the while loop logic used the original storage type to calculate the fallback index instead of updating it, causing an infinite loop when multiple storage types are unavailable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions