-
Notifications
You must be signed in to change notification settings - Fork 20.5k
Description
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
- Rapidly add and remove data items to many elements with jQuery's
. data()API in a high-throughput loop or asynchronously. - Observe the state of expando properties and memory usage on the elements.
- 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.jsline 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
- Short-term: Add defensive checks and guards to prevent double-initialization
- Medium-term: Consider using
WeakMapfor safer, automatic memory management - 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