Skip to content

Commit

Permalink
M2Eclipse gets stuck in endless update loop
Browse files Browse the repository at this point in the history
Fix #123
  • Loading branch information
laeubi authored and mickaelistria committed May 3, 2022
1 parent 5500f96 commit 026330e
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2008-2010 Sonatype, Inc.
* Copyright (c) 2008-2022 Sonatype, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
Expand All @@ -9,6 +9,7 @@
*
* Contributors:
* Sonatype, Inc. - initial API and implementation
* Christoph Läubrich - M2Eclipse gets stuck in endless update loop
*******************************************************************************/

package org.eclipse.m2e.core.internal.project;
Expand Down Expand Up @@ -37,7 +38,7 @@ public synchronized boolean isEmpty() {
return pomFiles.isEmpty();
}

public synchronized void forcePomFiles(Set<IFile> pomFiles) {
public synchronized void forcePomFiles(Collection<IFile> pomFiles) {
this.pomFiles.addAll(pomFiles);
}

Expand All @@ -47,4 +48,9 @@ public synchronized IFile pop() {
i.remove();
return pom;
}

synchronized Set<IFile> getCurrent() {
return new LinkedHashSet<>(pomFiles);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*******************************************************************************
* Copyright (c) 2022 Christoph Läubrich and others
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.m2e.core.internal.project;

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.eclipse.core.resources.IFile;


/**
* ProjectProcessingTracker keeps track of the progress in refreshing projects.
*
*/
public class ProjectProcessingTracker {

static final Logger log = LoggerFactory.getLogger(ProjectProcessingTracker.class);

private Set<IFile> processed = new LinkedHashSet<>();

private Set<IFile> changedWhileRunning = new LinkedHashSet<>();

private Set<IFile> seed;

private DependencyResolutionContext context;

/**
* @param allProcessedPoms
*/
public ProjectProcessingTracker(DependencyResolutionContext context) {
this.context = context;
this.seed = context.getCurrent();
}

/**
* @param context
* @return
*/
public boolean needsImprovement() {
if(changedWhileRunning.isEmpty()) {
log.debug("Nothing changed in this cycle.");
return false;
}
if(seed.isEmpty()) {
log.debug("Seed is empty.");
return false;
}
log.debug("seed = {}", seed);
log.debug("processed = {}", processed);
log.debug("changed = {}", changedWhileRunning);
boolean removed = false;
for(Iterator<IFile> iterator = seed.iterator(); iterator.hasNext();) {
IFile file = iterator.next();
if(changedWhileRunning.contains(file)) {
//we need to keep this in the seed...
continue;
}
log.debug("{} was improved!", file);
iterator.remove();
removed = true;
}
if(removed) {
//we have an improvement...
context.forcePomFiles(changedWhileRunning);
//add all possibly new ones to the seed...
seed.addAll(changedWhileRunning);
//clear everything for next iteration...
changedWhileRunning.clear();
processed.clear();
return true;
}
log.debug("No new project was refreshed -> no improvement found!");
return false;
}

/**
* @param pom
* @return
*/
public boolean shouldProcess(IFile pom) {
if(processed.add(pom)) {
return true;
}
changedWhileRunning.add(pom);
return false;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2008-2010 Sonatype, Inc.
* Copyright (c) 2008-2022 Sonatype, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
Expand All @@ -9,6 +9,7 @@
*
* Contributors:
* Sonatype, Inc. - initial API and implementation
* Christoph Läubrich - M2Eclipse gets stuck in endless update loop
*******************************************************************************/

package org.eclipse.m2e.core.internal.project.registry;
Expand Down Expand Up @@ -103,6 +104,7 @@
import org.eclipse.m2e.core.internal.markers.MarkerUtils;
import org.eclipse.m2e.core.internal.project.DependencyResolutionContext;
import org.eclipse.m2e.core.internal.project.IManagedCache;
import org.eclipse.m2e.core.internal.project.ProjectProcessingTracker;
import org.eclipse.m2e.core.internal.project.ResolverConfigurationIO;
import org.eclipse.m2e.core.lifecyclemapping.model.IPluginExecutionMetadata;
import org.eclipse.m2e.core.project.IMavenProjectChangedListener;
Expand Down Expand Up @@ -478,6 +480,8 @@ private void refresh(final MutableProjectRegistry newState, final DependencyReso
context.forcePomFiles(allProcessedPoms);

// phase 2: resolve project dependencies
ProjectProcessingTracker tracker = new ProjectProcessingTracker(context);
do {
while(!context.isEmpty()) {
if(monitor.isCanceled()) {
throw new OperationCanceledException();
Expand All @@ -488,41 +492,44 @@ private void refresh(final MutableProjectRegistry newState, final DependencyReso
}

final IFile pom = context.pop();
if(tracker.shouldProcess(pom)) {

MavenProjectFacade newFacade = null;
if(pom.isAccessible() && pom.getProject().hasNature(IMavenConstants.NATURE_ID)) {
newFacade = newState.getProjectFacade(pom);
}
if(newFacade != null) {
MavenProject mavenProject = getMavenProject(newFacade);
if(!allProcessedPoms.contains(newFacade.getPom())) {
// facade from workspace state that has not been refreshed yet
newFacade = readMavenProjectFacades(Collections.singletonList(pom), newState, monitor).get(pom);
MavenProjectFacade newFacade = null;
if(pom.isAccessible() && pom.getProject().hasNature(IMavenConstants.NATURE_ID)) {
newFacade = newState.getProjectFacade(pom);
}
if(newFacade != null) {
MavenProject mavenProject = getMavenProject(newFacade);
if(!allProcessedPoms.contains(newFacade.getPom())) {
// facade from workspace state that has not been refreshed yet
newFacade = readMavenProjectFacades(Collections.singletonList(pom), newState, monitor).get(pom);
} else {
// recreate facade instance to trigger project changed event
// this is only necessary for facades that are refreshed because their dependencies changed
// but this is relatively cheap, so all facades are recreated here
putMavenProject(newFacade, null);
newFacade = new MavenProjectFacade(newFacade);
putMavenProject(newFacade, mavenProject);
}
}

if(newFacade != null) {
final MavenProjectFacade _newFacade = newFacade;
final ResolverConfiguration resolverConfiguration = _newFacade.getResolverConfiguration();
createExecutionContext(newState, pom, resolverConfiguration).execute(getMavenProject(newFacade),
(executionContext, pm) -> {
refreshPhase2(newState, context, originalCapabilities, originalRequirements, pom, _newFacade, pm);
return null;
}, monitor);
} else {
// recreate facade instance to trigger project changed event
// this is only necessary for facades that are refreshed because their dependencies changed
// but this is relatively cheap, so all facades are recreated here
putMavenProject(newFacade, null);
newFacade = new MavenProjectFacade(newFacade);
putMavenProject(newFacade, mavenProject);
refreshPhase2(newState, context, originalCapabilities, originalRequirements, pom, newFacade, monitor);
}
}

if(newFacade != null) {
final MavenProjectFacade _newFacade = newFacade;
final ResolverConfiguration resolverConfiguration = _newFacade.getResolverConfiguration();
createExecutionContext(newState, pom, resolverConfiguration).execute(getMavenProject(newFacade),
(executionContext, pm) -> {
refreshPhase2(newState, context, originalCapabilities, originalRequirements, pom, _newFacade, pm);
return null;
}, monitor);
} else {
refreshPhase2(newState, context, originalCapabilities, originalRequirements, pom, newFacade, monitor);
monitor.worked(1);
}

monitor.worked(1);
}
}
} while(tracker.needsImprovement());
}

void refreshPhase2(MutableProjectRegistry newState, DependencyResolutionContext context,
Map<IFile, Set<Capability>> originalCapabilities, Map<IFile, Set<RequiredCapability>> originalRequirements,
Expand Down

0 comments on commit 026330e

Please sign in to comment.