Skip to content

Commit ca0216b

Browse files
committed
Save and restore the current plugin classpath container state from disk
Computing the bundle plugin classpath can be an expensive operation for different reasons. In many cases these computation even yields the same results as before especially on workspace restart without target changes. This now stores the last successful computed state per project and read it back on initialization of the classpath container, scheduling a check for actual changes in the background. Only if this check yields a different result or the update is requested as part of a change to the manifest or target state for example the current values are updated. This yields faster startup times without the risk of blocking (other than file I/O) until PDE can deliver the classpath to consumers delaying the heavy work to a later time.
1 parent 847f31d commit ca0216b

9 files changed

Lines changed: 436 additions & 118 deletions

ui/org.eclipse.pde.core/.options

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ org.eclipse.pde.core/model=false
66
# trace for creating targets using a p2 profile
77
org.eclipse.pde.core/target/profile=false
88
# trace when validating plugin.xml contents
9-
org.eclipse.pde.core/validation=false
9+
org.eclipse.pde.core/validation=false
10+
# trace when read/save the current state of the plugin resolution
11+
org.eclipse.pde.core/state=false

ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java

Lines changed: 128 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313
*******************************************************************************/
1414
package org.eclipse.pde.internal.core;
1515

16+
import java.io.BufferedInputStream;
17+
import java.io.BufferedOutputStream;
18+
import java.io.File;
19+
import java.io.FileInputStream;
20+
import java.io.FileNotFoundException;
21+
import java.io.FileOutputStream;
22+
import java.io.InputStream;
1623
import java.util.ArrayList;
1724
import java.util.Arrays;
1825
import java.util.Collection;
@@ -23,6 +30,7 @@
2330
import java.util.List;
2431
import java.util.Map;
2532
import java.util.Map.Entry;
33+
import java.util.Objects;
2634
import java.util.Queue;
2735
import java.util.concurrent.ConcurrentLinkedQueue;
2836
import java.util.regex.Pattern;
@@ -32,13 +40,16 @@
3240
import org.eclipse.core.resources.IFile;
3341
import org.eclipse.core.resources.IProject;
3442
import org.eclipse.core.resources.IResource;
43+
import org.eclipse.core.resources.IResourceChangeEvent;
44+
import org.eclipse.core.resources.IResourceChangeListener;
3545
import org.eclipse.core.resources.ResourcesPlugin;
3646
import org.eclipse.core.runtime.CoreException;
3747
import org.eclipse.core.runtime.IPath;
3848
import org.eclipse.core.runtime.IProgressMonitor;
3949
import org.eclipse.core.runtime.IStatus;
4050
import org.eclipse.core.runtime.MultiStatus;
4151
import org.eclipse.core.runtime.Status;
52+
import org.eclipse.core.runtime.SubMonitor;
4253
import org.eclipse.core.runtime.jobs.Job;
4354
import org.eclipse.jdt.core.IClasspathAttribute;
4455
import org.eclipse.jdt.core.IClasspathContainer;
@@ -62,6 +73,7 @@
6273
import org.eclipse.pde.internal.core.natures.PluginProject;
6374
import org.eclipse.pde.internal.core.project.PDEProject;
6475
import org.eclipse.pde.internal.core.util.CoreUtility;
76+
import org.eclipse.pde.internal.core.util.PDEClasspathContainerSaveHelper;
6577
import org.eclipse.team.core.RepositoryProvider;
6678

6779
public class ClasspathComputer {
@@ -81,6 +93,20 @@ private record ClasspathConfiguration(IPluginModelBase model, IJavaProject javaP
8193
*/
8294
private static final UpdateClasspathsJob fUpdateJob = new UpdateClasspathsJob();
8395

96+
static final IResourceChangeListener CHANGE_LISTENER = new IResourceChangeListener() {
97+
98+
@Override
99+
public void resourceChanged(IResourceChangeEvent event) {
100+
IResource resource = event.getResource();
101+
if (resource instanceof IProject project) {
102+
if (PDECore.DEBUG_STATE) {
103+
System.out.println(String.format("Project %s was deleted.", project.getName())); //$NON-NLS-1$
104+
}
105+
getStateFile(project).delete();
106+
}
107+
}
108+
};
109+
84110
public static void setClasspath(IProject project, IPluginModelBase model) throws CoreException {
85111
IClasspathEntry[] entries = getClasspath(project, model, null, false, true);
86112
JavaCore.create(project).setRawClasspath(entries, null);
@@ -508,14 +534,19 @@ public static void requestClasspathUpdate(Collection<IProject> updateProjects) {
508534
fUpdateJob.addAll(updateProjects);
509535
}
510536

537+
static void requestClasspathUpdate(IProject project, IClasspathContainer savedState) {
538+
fUpdateJob.add(project, savedState);
539+
}
540+
511541
/**
512542
* Job to update class path containers asynchronously. Avoids blocking the
513543
* UI thread. The job is given a workspace lock so other jobs can't run on a
514544
* stale classpath.
515545
*/
516546
private static final class UpdateClasspathsJob extends Job {
517547

518-
private final Queue<IProject> workQueue = new ConcurrentLinkedQueue<>();
548+
private static final int WORK = 10_000;
549+
private final Queue<UpdateRequest> workQueue = new ConcurrentLinkedQueue<>();
519550

520551
/**
521552
* Constructs a new job.
@@ -533,29 +564,32 @@ public boolean belongsTo(Object family) {
533564
}
534565

535566
@Override
536-
protected IStatus run(IProgressMonitor monitor) {
567+
protected IStatus run(IProgressMonitor jobMonitor) {
568+
SubMonitor monitor = SubMonitor.convert(jobMonitor, PDECoreMessages.PluginModelManager_1, WORK);
569+
PluginModelManager.getInstance().initialize(monitor.split(10));
537570
PluginModelManager modelManager = PluginModelManager.getInstance();
538571
Map<IJavaProject, IClasspathContainer> updateProjects = new LinkedHashMap<>();
539572
Map<IProject, IStatus> errorsPerProject = new LinkedHashMap<>();
540-
IProject project;
541-
while (!monitor.isCanceled() && (project = workQueue.poll()) != null) {
573+
UpdateRequest request;
574+
while (!monitor.isCanceled() && (request = workQueue.poll()) != null) {
575+
monitor.setWorkRemaining(WORK);
576+
IProject project = request.project();
542577
if (project.exists() && project.isOpen()) {
543578
IPluginModelBase model = modelManager.findModel(project);
544579
if (model != null && PluginProject.isJavaProject(project)) {
545580
IJavaProject javaProject = JavaCore.create(project);
546581
RequiredPluginsClasspathContainer classpathContainer = new RequiredPluginsClasspathContainer(
547582
model, project);
548583
try {
549-
// eager compute the entries as they will be
550-
// needed soon, if any error occurs here we do
551-
// not update the entry, all errors will be
552-
// reported at the end of the update!
553-
classpathContainer.computeEntries();
554-
updateProjects.put(javaProject, classpathContainer);
555-
errorsPerProject.remove(project);
584+
if (!isUpToDate(project, classpathContainer.computeEntries(), request.container())) {
585+
updateProjects.put(javaProject, classpathContainer);
586+
errorsPerProject.remove(project);
587+
saveState(project, classpathContainer);
588+
}
556589
} catch (CoreException e) {
557590
errorsPerProject.put(project, e.getStatus());
558591
}
592+
monitor.worked(1);
559593
}
560594
}
561595
}
@@ -598,10 +632,92 @@ protected IStatus run(IProgressMonitor monitor) {
598632
* Queues more projects/containers.
599633
*/
600634
void addAll(Collection<IProject> tocheck) {
601-
workQueue.addAll(tocheck);
635+
for (IProject project : tocheck) {
636+
workQueue.add(new UpdateRequest(project, null));
637+
}
602638
schedule();
603639
}
604640

641+
void add(IProject project, IClasspathContainer classpathContainer) {
642+
if (project == null) {
643+
return;
644+
}
645+
workQueue.add(new UpdateRequest(project, classpathContainer));
646+
schedule();
647+
}
648+
649+
}
650+
651+
private static boolean isUpToDate(IProject project, IClasspathEntry[] currentEntries,
652+
IClasspathContainer previousClasspathContainer) {
653+
if (previousClasspathContainer == null) {
654+
if (PDECore.DEBUG_STATE) {
655+
System.out
656+
.println(String.format("%s need update because it has no state to compare", project.getName())); //$NON-NLS-1$
657+
}
658+
return false;
659+
}
660+
IClasspathEntry[] previousEntries = previousClasspathContainer.getClasspathEntries();
661+
if (previousEntries == null || previousEntries.length != currentEntries.length) {
662+
if (PDECore.DEBUG_STATE) {
663+
System.out.println(String.format("%s need update because entries do not match in size!", //$NON-NLS-1$
664+
project.getName()));
665+
}
666+
return false;
667+
}
668+
for (int i = 0; i < previousEntries.length; i++) {
669+
IClasspathEntry previous = previousEntries[i];
670+
IClasspathEntry current = currentEntries[i];
671+
if (!Objects.equals(current, previous)) {
672+
if (PDECore.DEBUG_STATE) {
673+
System.out.println(
674+
String.format("%s need update because entry at position %d is different:\n\t%s\n\t%s", //$NON-NLS-1$
675+
project.getName(), i, current, previous));
676+
}
677+
return false;
678+
}
679+
}
680+
return true;
681+
}
682+
683+
private static void saveState(IProject project, RequiredPluginsClasspathContainer classpathContainer) {
684+
try {
685+
File stateFile = getStateFile(project);
686+
stateFile.getParentFile().mkdirs();
687+
try (BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(stateFile))) {
688+
PDEClasspathContainerSaveHelper.writeContainer(classpathContainer, stream);
689+
}
690+
} catch (Exception e) {
691+
// can't write then...
692+
if (PDECore.DEBUG_STATE) {
693+
System.err.println(String.format("Writing project state for %s failed!", project.getName())); //$NON-NLS-1$
694+
e.printStackTrace();
695+
}
696+
}
697+
}
698+
699+
static IClasspathContainer readState(IProject project) {
700+
try {
701+
File stateFile = getStateFile(project);
702+
try (InputStream stream = new BufferedInputStream(new FileInputStream(stateFile))) {
703+
return PDEClasspathContainerSaveHelper.readContainer(stream);
704+
}
705+
} catch (Exception e) {
706+
if (PDECore.DEBUG_STATE && !(e instanceof FileNotFoundException)) {
707+
System.err.println(String.format("Restoring project state for %s failed!", project.getName())); //$NON-NLS-1$
708+
e.printStackTrace();
709+
}
710+
return null;
711+
}
712+
}
713+
714+
private static File getStateFile(IProject project) {
715+
return PDECore.getDefault().getStateLocation().append("cpc").append(project.getName()) //$NON-NLS-1$
716+
.toFile();
717+
}
718+
719+
private static record UpdateRequest(IProject project, IClasspathContainer container) {
720+
605721
}
606722

607723
}

ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEClasspathContainer.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public String toString() {
4949

5050
private static final IAccessRule EXCLUDE_ALL_RULE = JavaCore.newAccessRule(IPath.fromOSString("**/*"), IAccessRule.K_NON_ACCESSIBLE | IAccessRule.IGNORE_IF_BETTER); //$NON-NLS-1$
5151

52-
protected void addProjectEntry(IProject project, List<Rule> rules, boolean exportsExternalAnnotations,
52+
protected static void addProjectEntry(IProject project, List<Rule> rules, boolean exportsExternalAnnotations,
5353
List<IClasspathEntry> entries) throws CoreException {
5454
if (project.hasNature(JavaCore.NATURE_ID)) {
5555
IAccessRule[] accessRules = rules != null ? getAccessRules(rules) : null;
@@ -61,7 +61,8 @@ protected void addProjectEntry(IProject project, List<Rule> rules, boolean expor
6161
}
6262
}
6363

64-
private IClasspathAttribute[] getClasspathAttributesForProject(IProject project, boolean exportsExternalAnnotations)
64+
private static IClasspathAttribute[] getClasspathAttributesForProject(IProject project,
65+
boolean exportsExternalAnnotations)
6566
throws JavaModelException {
6667
if (exportsExternalAnnotations) {
6768
String annotationPath = JavaCore.create(project).getOutputLocation().toString();

ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDECore.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Map;
2222
import java.util.stream.Stream;
2323

24+
import org.eclipse.core.resources.IResourceChangeEvent;
2425
import org.eclipse.core.resources.ISaveContext;
2526
import org.eclipse.core.resources.ISaveParticipant;
2627
import org.eclipse.core.resources.IWorkspace;
@@ -81,11 +82,13 @@ public class PDECore extends Plugin implements DebugOptionsListener {
8182
public static boolean DEBUG_MODEL = false;
8283
public static boolean DEBUG_TARGET_PROFILE = false;
8384
public static boolean DEBUG_VALIDATION = false;
85+
public static boolean DEBUG_STATE = false;
8486
private static final String DEBUG_FLAG = PLUGIN_ID + "/debug"; //$NON-NLS-1$
8587
private static final String CLASSPATH_DEBUG = PLUGIN_ID + "/classpath"; //$NON-NLS-1$
8688
private static final String MODEL_DEBUG = PLUGIN_ID + "/model"; //$NON-NLS-1$
8789
private static final String TARGET_PROFILE_DEBUG = PLUGIN_ID + "/target/profile"; //$NON-NLS-1$
8890
private static final String VALIDATION_DEBUG = PLUGIN_ID + "/validation"; //$NON-NLS-1$
91+
private static final String STATE_DEBUG = PLUGIN_ID + "/state"; //$NON-NLS-1$
8992

9093
// Shared instance
9194
private static PDECore inst;
@@ -368,6 +371,7 @@ public void doneSaving(ISaveContext saveContext) {
368371
});
369372
bndResourceChangeListener = new BndResourceChangeListener();
370373
workspace.addResourceChangeListener(bndResourceChangeListener);
374+
workspace.addResourceChangeListener(ClasspathComputer.CHANGE_LISTENER, IResourceChangeEvent.PRE_DELETE);
371375
fBundleContext.registerService(Workspace.class, new BndWorkspaceServiceFactory(),
372376
FrameworkUtil.asDictionary(Map.of(Constants.SERVICE_RANKING, -10)));
373377
}
@@ -424,6 +428,7 @@ public void stop(BundleContext context) throws CoreException {
424428
IWorkspace workspace = ResourcesPlugin.getWorkspace();
425429
workspace.removeSaveParticipant(PLUGIN_ID);
426430
workspace.removeResourceChangeListener(bndResourceChangeListener);
431+
workspace.removeResourceChangeListener(ClasspathComputer.CHANGE_LISTENER);
427432

428433
MinimalState.shutdown();
429434
}
@@ -454,6 +459,7 @@ public void optionsChanged(DebugOptions options) {
454459
DEBUG_MODEL = DEBUG && options.getBooleanOption(MODEL_DEBUG, false);
455460
DEBUG_TARGET_PROFILE = DEBUG && options.getBooleanOption(TARGET_PROFILE_DEBUG, false);
456461
DEBUG_VALIDATION = DEBUG && options.getBooleanOption(VALIDATION_DEBUG, false);
462+
DEBUG_STATE = DEBUG & options.getBooleanOption(STATE_DEBUG, false);
457463
}
458464

459465
/**

ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PluginModelManager.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,12 @@ private Map<String, LocalModelEntry> getEntryTable() {
437437
return fEntries;
438438
}
439439

440+
void initialize(IProgressMonitor monitor) {
441+
synchronized (fEntriesSynchronizer) {
442+
initializeTable(monitor);
443+
}
444+
}
445+
440446
/** Has to be called synchronized with fEntriesSynchronizer **/
441447
private void initializeTable(IProgressMonitor monitor) {
442448
if (fEntries != null) {

ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/RequiredPluginsClasspathContainer.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.eclipse.pde.core.target.NameVersionDescriptor;
6262
import org.eclipse.pde.internal.build.BundleHelper;
6363
import org.eclipse.pde.internal.build.IBuildPropertiesConstants;
64+
import org.eclipse.pde.internal.core.PDEClasspathContainer.Rule;
6465
import org.eclipse.pde.internal.core.bnd.BndProjectManager;
6566
import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelBase;
6667
import org.eclipse.pde.internal.core.natures.BndProject;
@@ -71,7 +72,7 @@
7172
import aQute.bnd.build.Workspace;
7273
import aQute.bnd.osgi.Constants;
7374

74-
class RequiredPluginsClasspathContainer extends PDEClasspathContainer implements IClasspathContainer {
75+
class RequiredPluginsClasspathContainer implements IClasspathContainer {
7576

7677
@SuppressWarnings("nls")
7778
private static final Set<String> JUNIT5_RUNTIME_PLUGINS = Set.of("org.junit", //
@@ -459,9 +460,10 @@ private boolean addPlugin(BundleDescription desc, boolean useInclusions, Map<Bun
459460
getClasspathContributors().map(cc -> cc.getEntriesForDependency(hostBundle, desc)).flatMap(Collection::stream)
460461
.forEach(entries::add);
461462
if (resource != null) {
462-
addProjectEntry(resource.getProject(), rules, model.getPluginBase().exportsExternalAnnotations(), entries);
463+
PDEClasspathContainer.addProjectEntry(resource.getProject(), rules,
464+
model.getPluginBase().exportsExternalAnnotations(), entries);
463465
} else {
464-
addExternalPlugin(model, rules, entries);
466+
PDEClasspathContainer.addExternalPlugin(model, rules, entries);
465467
}
466468
return true;
467469
}

0 commit comments

Comments
 (0)