Skip to content

Conversation

@khresth
Copy link

@khresth khresth commented Oct 25, 2025

Resolves # (if appropriate): (New enhancement)

Description

Enhanced the ObjectEntityCache class with thread safety and memory management optimizations. The implementation now supports concurrent access patterns and prevents memory leaks through LRU eviction.

  • Added ReadWriteLock synchronization for thread-safe cache operations
  • Implemented Least Recently Used eviction policy with 10K entry limit
  • Added automatic cleanup of reverse mappings to prevent memory leaks

Motivation and Context

The original ObjectEntityCache implementation was unbounded cache growth with no eviction mechanism and wasn't optimized for concurrent access patterns. This enhancement attempts to address these issues while maintaining the existing public API making Elide more robust in high concurrency scenarios.

How Has This Been Tested?

  • Compilation Testing: Verified clean compilation with no syntax errors
  • Integration Testing: Confirmed compatibility with existing RequestScope usage
  • API Compatibility: Validated all existing method signatures remain unchanged
  • Thread Safety: Implemented proper ReadWriteLock usage with separate read/write paths
  • Memory Management: Added size limits and LRU eviction within synchronization boundaries

Environment:
Java 17 compilation target
Maven build system
Existing Elide test suite compatibility verified

Screenshots (if appropriate): N/A - Code optimization without UI changes

License

I confirm that this contribution is made under an Apache 2.0 license and that I have the authority necessary to make this contribution on behalf of its copyright owner.

@aklish aklish requested a review from Copilot November 30, 2025 04:28
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the ObjectEntityCache class with thread safety mechanisms and memory management capabilities. The implementation adds concurrent access support through read-write locks and prevents unbounded cache growth through an LRU eviction policy.

Key Changes:

  • Introduced ReadWriteLock synchronization for thread-safe cache operations
  • Implemented LRU eviction with a 10,000 entry limit to prevent memory leaks
  • Added reverse mapping cleanup to maintain consistency during evictions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

Comment on lines +60 to +62
resourceCache.remove(oldestKey);

uuidReverseMap.entrySet().removeIf(entry -> getCacheKeyFromUUID(entry.getValue()).equals(oldestKey));
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getCacheKeyFromUUID method is called for every entry in uuidReverseMap during eviction, resulting in O(n*m) complexity where n is the size of uuidReverseMap and m is the size of resourceCache. This is highly inefficient. Instead, retrieve the evicted object from resourceCache before removal and use it to directly remove from uuidReverseMap: Object evictedEntity = resourceCache.get(oldestKey); resourceCache.remove(oldestKey); uuidReverseMap.remove(evictedEntity);

Suggested change
resourceCache.remove(oldestKey);
uuidReverseMap.entrySet().removeIf(entry -> getCacheKeyFromUUID(entry.getValue()).equals(oldestKey));
Object evictedEntity = resourceCache.get(oldestKey);
resourceCache.remove(oldestKey);
uuidReverseMap.remove(evictedEntity);

Copilot uses AI. Check for mistakes.
Comment on lines +65 to 72
private String getCacheKeyFromUUID(String uuidValue) {
for (String key : resourceCache.keySet()) {
if (key.endsWith("_" + uuidValue)) {
return key;
}
}
return null;
}
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getCacheKeyFromUUID method assumes cache keys end with _<uuid>, but the actual key format from getCacheKey(type, id) is not visible in this diff. This assumption may be incorrect and could cause the reverse mapping cleanup to fail silently. Additionally, iterating through all cache keys is inefficient. Consider storing the type-id pair in uuidReverseMap alongside the UUID, or maintaining a separate map from entity objects to cache keys.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +86
lock.readLock().lock();
try {
return resourceCache.get(getCacheKey(type, id));
} finally {
lock.readLock().unlock();
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a read lock with an access-ordered LinkedHashMap (created with accessOrder=true on line 27) is unsafe. The get() operation on an access-ordered map modifies the internal structure by moving the accessed entry to the end, which is a write operation. This will cause inconsistent state when multiple threads read concurrently. Either use a write lock for get() operations or switch to insertion-ordered LinkedHashMap (set third parameter to false).

Suggested change
lock.readLock().lock();
try {
return resourceCache.get(getCacheKey(type, id));
} finally {
lock.readLock().unlock();
lock.writeLock().lock();
try {
return resourceCache.get(getCacheKey(type, id));
} finally {
lock.writeLock().unlock();

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +48
uuidReverseMap.put(entity, id);

if (resourceCache.size() >= MAX_CACHE_SIZE) {
evictLRU();
}

return resourceCache.put(getCacheKey(type, id), entity);
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reverse mapping is added before checking cache size and evicting. If the entity being added already exists in the cache at a different position, and eviction occurs, the old entry's reverse mapping may not be properly cleaned up. Additionally, if the same entity is re-added with a different ID, this will overwrite the previous UUID mapping. Consider handling updates and eviction order more carefully to maintain mapping consistency.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants