Skip to content

Infinite Recursion Issue in BazelClasspathContainerRuntimeResolver #37

@runchen0919

Description

@runchen0919

Description:

When fully loading my project, after the classpath calculation is complete, the logs indicate that the process is stuck in "Refresh workspace." The application does not proceed, even after a long wait.

Upon analyzing the call stack using jstack, I found that the program was trapped in a recursive segment of code within the BazelClasspathContainerRuntimeResolver.

Given the massive size of my project, I am uncertain which specific modules trigger this problem. However, the analysis of the recursive code in the call stack clearly points to a potential infinite recursion issue. The relevant code section appears to be:

// this method can be entered recursively; luckily only within the same thread
// therefore we use a ThreadLocal LinkedHashSet to keep track of recursive attempts
var currentlyResolvingProjects = stackOfResolvingProjects.get();
currentlyResolvingProjects.add(project.getProject());

The logic relies on a ThreadLocal LinkedHashSet to track recursion within the same thread. If project.getProject() is already present in the set before the add operation, adding it again will not prevent the code from proceeding, potentially leading to an infinite loop if the recursion logic is flawed and repeatedly enters the same project context.

Steps to Reproduce

  • Open a very large Bazel-based Java project in the IDE (potentially with complex or cyclical project dependencies).

  • Perform a full project load or workspace refresh (e.g., when refreshing the Bazel classpath container).

  • Observe that the process gets stuck in the "Refresh workspace" step.

  • A jstack dump will show the thread infinitely recursing inside the relevant method of BazelClasspathContainerRuntimeResolver.

The thread stack is as follows

"ForkJoinPool.commonPool-worker-1" #93 [83459] daemon prio=5 os_prio=31 cpu=78888.92ms elapsed=609.05s tid=0x00000001282ade00 nid=83459 runnable  [0x0000000841c9b000]
   java.lang.Thread.State: RUNNABLE
        at java.lang.String.intern(java.base@21.0.8/Native Method)
        at java.io.ObjectStreamField.<init>(java.base@21.0.8/ObjectStreamField.java:109)
        at java.io.ObjectStreamClass.readNonProxy(java.base@21.0.8/ObjectStreamClass.java:711)
        at java.io.ObjectInputStream.readClassDescriptor(java.base@21.0.8/ObjectInputStream.java:1014)
        at java.io.ObjectInputStream.readNonProxyDesc(java.base@21.0.8/ObjectInputStream.java:2050)
        at java.io.ObjectInputStream.readClassDesc(java.base@21.0.8/ObjectInputStream.java:1927)
        at java.io.ObjectInputStream.readOrdinaryObject(java.base@21.0.8/ObjectInputStream.java:2252)
        at java.io.ObjectInputStream.readObject0(java.base@21.0.8/ObjectInputStream.java:1762)
        at java.io.ObjectInputStream.readArray(java.base@21.0.8/ObjectInputStream.java:2186)
        at java.io.ObjectInputStream.readObject0(java.base@21.0.8/ObjectInputStream.java:1750)
        at java.io.ObjectInputStream$FieldValues.<init>(java.base@21.0.8/ObjectInputStream.java:2618)
        at java.io.ObjectInputStream.readSerialData(java.base@21.0.8/ObjectInputStream.java:2469)
        at java.io.ObjectInputStream.readOrdinaryObject(java.base@21.0.8/ObjectInputStream.java:2284)
        at java.io.ObjectInputStream.readObject0(java.base@21.0.8/ObjectInputStream.java:1762)
        at java.io.ObjectInputStream.readObject(java.base@21.0.8/ObjectInputStream.java:540)
        at java.io.ObjectInputStream.readObject(java.base@21.0.8/ObjectInputStream.java:498)
        at com.salesforce.bazel.eclipse.core.classpath.BazelClasspathContainerSaveHelper.readContainer(BazelClasspathContainerSaveHelper.java:192)
        at com.salesforce.bazel.eclipse.core.classpath.BazelClasspathManager.getSavedContainer(BazelClasspathManager.java:188)
        at com.salesforce.bazel.eclipse.core.classpath.BazelClasspathContainerRuntimeResolver.populateWithSavedContainer(BazelClasspathContainerRuntimeResolver.java:141)
        at com.salesforce.bazel.eclipse.core.classpath.BazelClasspathContainerRuntimeResolver.resolveRuntimeClasspathEntry(BazelClasspathContainerRuntimeResolver.java:201)
        at org.eclipse.jdt.launching.IRuntimeClasspathEntryResolver.resolveRuntimeClasspathEntry(IRuntimeClasspathEntryResolver.java:104)
        at org.eclipse.jdt.internal.launching.RuntimeClasspathEntryResolver.resolveRuntimeClasspathEntry(RuntimeClasspathEntryResolver.java:110)
        at org.eclipse.jdt.launching.JavaRuntime.resolveRuntimeClasspathEntry(JavaRuntime.java:1557)
        at org.eclipse.jdt.internal.launching.DefaultEntryResolver.resolveRuntimeClasspathEntry(DefaultEntryResolver.java:70)
        at org.eclipse.jdt.launching.JavaRuntime.resolveRuntimeClasspathEntry(JavaRuntime.java:1560)
        at com.salesforce.bazel.eclipse.core.classpath.BazelClasspathContainerRuntimeResolver.populateWithResolvedProject(BazelClasspathContainerRuntimeResolver.java:133)
        at com.salesforce.bazel.eclipse.core.classpath.BazelClasspathContainerRuntimeResolver.populateWithSavedContainer(BazelClasspathContainerRuntimeResolver.java:152)
        at com.salesforce.bazel.eclipse.core.classpath.BazelClasspathContainerRuntimeResolver.resolveRuntimeClasspathEntry(BazelClasspathContainerRuntimeResolver.java:201)
        at org.eclipse.jdt.launching.IRuntimeClasspathEntryResolver.resolveRuntimeClasspathEntry(IRuntimeClasspathEntryResolver.java:104)
        at org.eclipse.jdt.internal.launching.RuntimeClasspathEntryResolver.resolveRuntimeClasspathEntry(RuntimeClasspathEntryResolver.java:110)
        at org.eclipse.jdt.launching.JavaRuntime.resolveRuntimeClasspathEntry(JavaRuntime.java:1557)
        at org.eclipse.jdt.internal.launching.DefaultEntryResolver.resolveRuntimeClasspathEntry(DefaultEntryResolver.java:70)
        at org.eclipse.jdt.launching.JavaRuntime.resolveRuntimeClasspathEntry(JavaRuntime.java:1560)
        at com.salesforce.bazel.eclipse.core.classpath.BazelClasspathContainerRuntimeResolver.populateWithResolvedProject(BazelClasspathContainerRuntimeResolver.java:133)

Solution

The recursive call needs a check to ensure it immediately terminates if it attempts to resolve a project that is already in the process of being resolved within the current thread.

The fix is to check the return value of the add() operation and return early if the project was already present, indicating a cycle in the resolution attempt.

// this method can be entered recursively; luckily only within the same thread
// therefore we use a ThreadLocal LinkedHashSet to keep track of recursive attempts
var currentlyResolvingProjects = stackOfResolvingProjects.get();

// Check if the project is already being resolved to prevent infinite recursion
if (!currentlyResolvingProjects.add(project.getProject())) {
    // Project is already in the set, indicating a cycle. Exit to break the recursion.
    return; // Or similar appropriate early exit based on method signature
}

// ... rest of the resolution logic

I have tested this modification on my large project, and it successfully resolves the issue, allowing the workspace refresh to complete.

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