From 4895067f9c7e58a96be3994ebf1ad67c770bc757 Mon Sep 17 00:00:00 2001 From: Changyong Gong Date: Tue, 7 Jul 2026 17:09:16 +0800 Subject: [PATCH] fix: avoid premature refresh race for deleted .classpath files StandardProjectsManager.fileChanged() called JDTUtils.getFileOrFolder(), which eagerly refreshes the resource tree, before IBuildSupport.refresh() had a chance to recover the classpath for a DELETED .classpath event. This notified JDT Core's classpath machinery that the file was gone before the recovery logic ran, causing JDT Core to briefly fall back to a default classpath and create a stray bin output folder. Resolve deleted .classpath files via JDTUtils.findFile() instead, which does not perform the premature refresh; IBuildSupport.refresh() already performs the equivalent refresh once the classpath has been restored. Fixes the stray-bin-folder root cause behind the long-standing flaky MavenProjectMetadataFileTest failures. See eclipse-jdtls/eclipse.jdt.ls#1443 and eclipse-jdtls/eclipse.jdt.ls#1251. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../managers/StandardProjectsManager.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/StandardProjectsManager.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/StandardProjectsManager.java index c15380076f..50f2c84d67 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/StandardProjectsManager.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/StandardProjectsManager.java @@ -243,7 +243,7 @@ public void fileChanged(String uriString, CHANGE_TYPE changeType) { if (configureNeeded) { configureSettings(preferenceManager.getPreferences()); } - IResource resource = JDTUtils.getFileOrFolder(uriString); + IResource resource = getResourceForChange(uriString, changeType); if (resource == null) { return; } @@ -311,6 +311,37 @@ public void fileChanged(String uriString, CHANGE_TYPE changeType) { } } + /** + * Resolves the {@link IResource} for the given change notification. + * + * For most changes we delegate to {@link JDTUtils#getFileOrFolder(String)}, + * which eagerly calls {@code IContainer#refreshLocal(DEPTH_ONE, null)} on the + * parent container to disambiguate files from folders. + * + * That eager refresh is harmful for a deleted {@code .classpath} file: it + * notifies the JDT Core resource-change listeners that the classpath file is + * gone *before* {@link IBuildSupport#fileChanged(IResource, CHANGE_TYPE, IProgressMonitor)} + * gets a chance to remove the Java nature and regenerate the correct + * classpath, causing JDT Core to fall back to a default classpath (and create + * a stray {@code bin} output folder) in between. See + * https://github.com/eclipse-jdtls/eclipse.jdt.ls/issues/1443 and + * https://github.com/eclipse-jdtls/eclipse.jdt.ls/issues/1251. + * + * To avoid the race, a deleted {@code .classpath} file is resolved without + * triggering that premature refresh; {@link IBuildSupport#refresh(IResource, CHANGE_TYPE, IProgressMonitor)} + * already performs the equivalent (and correctly ordered) refresh once it has + * restored the classpath. + */ + private IResource getResourceForChange(String uriString, CHANGE_TYPE changeType) { + if (changeType == CHANGE_TYPE.DELETED) { + IFile file = JDTUtils.findFile(uriString); + if (file != null && IJavaProject.CLASSPATH_FILE_NAME.equals(file.getName())) { + return file; + } + } + return JDTUtils.getFileOrFolder(uriString); + } + private void appendBuildFileMarker(IResource resource) throws CoreException { IMarker[] markers = resource.findMarkers(BUILD_FILE_MARKER_TYPE, false, IResource.DEPTH_ZERO); if (markers.length > 0) {