Skip to content

Potential Race Condition in jQuery Data Cache Expando Property Assignment #5736

@muzanella11

Description

@muzanella11

Description

There is a potential race condition in the way jQuery assigns expando properties for data caching on DOM elements. When multiple operations interact with data storage in rapid succession or concurrently, the assignment and deletion of expando properties may not be atomic, which could lead to inconsistent states or memory leaks.

Steps to Reproduce

  1. Rapidly add and remove data items to many elements with jQuery's . data() API in a high-throughput loop or asynchronously.
  2. Observe the state of expando properties and memory usage on the elements.
  3. Check for orphaned cache entries or inconsistencies in stored data.

Expected Behavior

Data cache should remain consistent, with expando properties correctly assigned and cleaned up, and no memory leaks even under heavy or concurrent operations.

Actual Behavior

Under some concurrent manipulation scenarios, the cache can become inconsistent or leak memory due to race conditions during expando property assignment and cleanup.

Potential Impact

  • Inconsistent or unexpected data retrieval from . data()
  • Stale or orphaned cached data objects on DOM nodes
  • Increased memory usage in applications with heavy data manipulation

Code References

src/data/Data.js (Lines 14-32)

cache: function( owner ) {
    // Check if the owner object already has a cache
    var value = owner[ this. expando ];

    // If not, create one
    if ( !value ) {
        value = Object.create( null );
        if ( acceptData( owner ) ) {
            if ( owner. nodeType ) {
                owner[ this.expando ] = value;  // NON-ATOMIC OPERATION
            }
        }
    }
    return value;
}

The problem: Between the check if ( !value ) and the assignment owner[ this.expando ] = value, there is a gap where another operation could interfere, especially in scenarios with:

  • Rapid . data() calls in async contexts
  • Multiple event handlers modifying data simultaneously
  • High-frequency DOM manipulation patterns

Related TODO Comment

  • File src/data.js line 6 contains: // Provide a clear path for implementation upgrade to WeakMap in 2014
  • This suggests the maintainers were already aware of potential issues with the current expando-based approach

Suggested Fix

  1. Short-term: Add defensive checks and guards to prevent double-initialization
  2. Medium-term: Consider using WeakMap for safer, automatic memory management
  3. Testing: Add comprehensive tests for concurrent .data() operations with:
    • Large DOM collections
    • Rapid add/remove cycles
    • Async/Promise-based data operations

Example Test Case

QUnit.test("data() concurrent operations", function(assert) {
    var elements = [];
    for (var i = 0; i < 1000; i++) {
        elements.push(jQuery("<div>"). get(0));
    }
    
    // Rapidly set/get/remove data
    elements.forEach(function(elem) {
        jQuery(elem).data("test", Math.random());
    });
    
    // Check for consistency
    elements.forEach(function(elem, i) {
        var value = jQuery(elem).data("test");
        assert.ok(value !== undefined, "Data at index " + i + " should exist");
    });
});

References

  • Related pattern: DOM node data storage and memory management best practices

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