Skip to content

Fix stale buffer reuse when jar files are recreated with same path#14

Draft
carstenartur with Copilot wants to merge 2 commits into
masterfrom
copilot/fix-race-condition-buffer-cache
Draft

Fix stale buffer reuse when jar files are recreated with same path#14
carstenartur with Copilot wants to merge 2 commits into
masterfrom
copilot/fix-race-condition-buffer-cache

Conversation

Copilot AI commented Jan 18, 2026

Copy link
Copy Markdown

The BufferManager LRU cache retains buffers after jar deletion. When a jar is recreated with the same path, the cache returns the stale buffer with null contents, causing source=null errors in tests (see eclipse-jdt/eclipse.jdt.ui#736).

Changes

  • ClassFile.java: Added buffer validation in openBuffer() to detect stale buffers (null contents) and evict them from cache before recreation
  • ModularClassFile.java: Same validation logic; also added cache lookup that was previously missing
  • ClassFileTests.java: Added testStaleBufferAfterJarRecreation() to verify fix

Implementation

Both openBuffer() methods now validate cached buffers:

IBuffer buffer = getBufferManager().getBuffer(...);

if (buffer != null) {
    if (buffer instanceof NullBuffer) {
        return null;  // Valid case: class file without source
    }
    if (buffer.getCharacters() == null) {
        // Stale buffer from deleted jar - evict and recreate
        getBufferManager().removeBuffer(buffer);
        buffer = null;
    }
}

This ensures buffers with null contents (stale from deleted jars) are removed from cache and recreated with current jar contents.

Original prompt

Problem

The AnnotateAssistTest1d8 test in eclipse-jdt/eclipse.jdt.ui fails intermittently with source=null errors, particularly on macOS. This is tracked in eclipse-jdt/eclipse.jdt.ui#736

The root cause is a race condition in buffer cache management where stale buffers are reused when jar files are recreated with the same path between tests.

Root Cause Analysis

  1. Buffer Caching with LRU: The BufferManager uses a BufferCache (LRU cache) to store buffers for class files.

  2. Stale Buffer Reuse: When tests create and delete jar files with the same name (e.g., lib.jar) between test methods:

    • Test 1 creates lib.jar → buffer created for X.class → buffer added to cache
    • Test 1 deletes lib.jarbuffer NOT removed from cache
    • Test 2 creates new lib.jar with same path → creates new X.class
    • ClassFile.openBuffer() calls getBufferManager().getBuffer(...)
    • Cache returns old stale buffer (because the key matches based on path)
    • Stale buffer has no valid contents → getSource() returns null
  3. Test Failure: The test fails with:

    java.lang.AssertionError: cu=[Working copy] X.class [in pack.age [in lib.jar [in TestSetupProject1d8]]]
    package pack.age
    interface X
    java.lang.String test(...) source=null
    

Solution

Modify ClassFile.openBuffer() to validate that a cached buffer is still valid before returning it. If the buffer has null contents (indicating it's stale), remove it from the cache and create a new one.

Files to Modify

org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ClassFile.java

In the openBuffer() method, add validation after retrieving the buffer from cache:

@Override
protected IBuffer openBuffer(IProgressMonitor pm, IElementInfo info) throws JavaModelException {
    // Check the cache for the top-level type first
    IType outerMostEnclosingType = getOuterMostEnclosingType();
    IBuffer buffer = getBufferManager().getBuffer(outerMostEnclosingType.getClassFile());
    
    // Validate the cached buffer is still valid (not stale from a deleted/recreated jar)
    if (buffer != null) {
        if (buffer instanceof NullBuffer) {
            // NullBuffer is valid - it represents a class file without source
            return null;
        }
        // Check if buffer contents are still valid
        if (buffer.getCharacters() == null) {
            // Stale buffer - remove from cache and recreate
            getBufferManager().removeBuffer(buffer);
            buffer = null;
        }
    }
    
    if (buffer == null) {
        SourceMapper mapper = getSourceMapper();
        IBinaryType typeInfo = info instanceof IBinaryType ? (IBinaryType) info : null;
        if (mapper != null) {
            buffer = mapSource(mapper, typeInfo, outerMostEnclosingType.getClassFile());
        }
    }
    return buffer;
}

org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ModularClassFile.java

Apply the same fix to the openBuffer() method:

@Override
protected IBuffer openBuffer(IProgressMonitor pm, IElementInfo info) throws JavaModelException {
    // First check if there's an existing buffer in the cache
    IBuffer buffer = getBufferManager().getBuffer(this);
    
    // Validate the cached buffer is still valid
    if (buffer != null) {
        if (buffer instanceof NullBuffer) {
            return null;
        }
        if (buffer.getCharacters() == null) {
            // Stale buffer - remove from cache and recreate
            getBufferManager().removeBuffer(buffer);
            buffer = null;
        }
    }
    
    if (buffer == null) {
        SourceMapper mapper = getSourceMapper();
        if (mapper != null) {
            buffer = mapSource(mapper);
        }
    }
    return buffer;
}

Testing

This fix should resolve the flaky test failures in AnnotateAssistTest1d8 by ensuring that stale buffers from deleted/recreated jar files are properly detected and replaced with fresh buffers.

References

This pull request was created from Copilot chat.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Add validation to detect and remove stale cached buffers when jar files are recreated with the same path. This prevents reusing buffers with null contents that can cause source=null errors in tests.

Co-authored-by: carstenartur <3164220+carstenartur@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix race condition in buffer cache management Fix stale buffer reuse when jar files are recreated with same path Jan 18, 2026
Copilot AI requested a review from carstenartur January 18, 2026 21:20
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