Skip to content

Commit ccda0fc

Browse files
vogellaclaude
andcommitted
Enable parallel refresh by scheduling one WorkspaceJob per project
Previously a single WorkspaceJob was scheduled with a combined MultiRule across all selected projects. This still refreshed all resources sequentially and held every project lock simultaneously, which was more restrictive than needed. Now RefreshAction.run() groups resources by their scheduling rule (project or workspace root) and calls scheduleRefreshJob() once per group. Each group gets its own WorkspaceModifyOperation protected by only that project's rule, so independent projects can be refreshed in parallel while concurrent workspace access to a single project is still prevented. Two new protected hook methods are added to RefreshAction: - createRefreshJob(): builds the WorkspaceModifyOperation-wrapped WorkspaceJob without scheduling it, so callers can attach IJobChangeListeners before scheduling (no race condition). - scheduleRefreshJob(): default implementation calls createRefreshJob().schedule(); subclasses override to customise. ResourceMgmtActionProvider no longer duplicates the run() logic. It overrides scheduleRefreshJob() instead, attaches a JobChangeAdapter that triggers a Navigator viewer refresh after each project's job completes, then schedules the job. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 676351a commit ccda0fc

2 files changed

Lines changed: 114 additions & 80 deletions

File tree

bundles/org.eclipse.ui.ide/extensions/org/eclipse/ui/actions/RefreshAction.java

Lines changed: 98 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
import java.lang.reflect.InvocationTargetException;
1919
import java.net.URI;
2020
import java.util.ArrayList;
21-
import java.util.Iterator;
21+
import java.util.LinkedHashMap;
2222
import java.util.List;
23+
import java.util.Map;
2324

2425
import org.eclipse.core.filesystem.IFileInfo;
2526
import org.eclipse.core.filesystem.IFileStore;
2627
import org.eclipse.core.resources.IProject;
2728
import org.eclipse.core.resources.IResource;
28-
import org.eclipse.core.resources.IResourceRuleFactory;
2929
import org.eclipse.core.resources.IWorkspaceRoot;
3030
import org.eclipse.core.resources.ResourcesPlugin;
3131
import org.eclipse.core.resources.WorkspaceJob;
@@ -49,6 +49,7 @@
4949
import org.eclipse.swt.widgets.Shell;
5050
import org.eclipse.ui.PlatformUI;
5151
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
52+
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
5253
import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
5354
import org.eclipse.ui.internal.ide.StatusUtil;
5455
import org.eclipse.ui.internal.ide.dialogs.IDEResourceInfoUtils;
@@ -217,41 +218,110 @@ final public void refreshAll() {
217218

218219
@Override
219220
final protected IRunnableWithProgress createOperation(final IStatus[] errorStatus) {
220-
ISchedulingRule rule = null;
221-
IResourceRuleFactory factory = ResourcesPlugin.getWorkspace().getRuleFactory();
222-
223221
List<? extends IResource> actionResources = new ArrayList<>(getActionResources());
224222
if (shouldPerformResourcePruning()) {
225223
actionResources = pruneResources(actionResources);
226224
}
227225
final List<? extends IResource> resources = actionResources;
228226

229-
Iterator<? extends IResource> res = resources.iterator();
230-
while (res.hasNext()) {
231-
rule = MultiRule.combine(rule, factory.refreshRule(res.next()));
227+
ISchedulingRule rule = null;
228+
for (IResource resource : resources) {
229+
ISchedulingRule newRule = (resource.getType() == IResource.ROOT) ? resource : resource.getProject();
230+
rule = MultiRule.combine(rule, newRule);
232231
}
232+
233233
return new WorkspaceModifyOperation(rule) {
234234
@Override
235235
public void execute(IProgressMonitor mon) {
236236
SubMonitor subMonitor = SubMonitor.convert(mon, resources.size());
237-
MultiStatus errors = null;
238237
subMonitor.setTaskName(getOperationMessage());
239-
Iterator<? extends IResource> resourcesEnum = resources.iterator();
240-
while (resourcesEnum.hasNext()) {
238+
List<IStatus> errors = new ArrayList<>();
239+
for (IResource resource : resources) {
241240
try {
242-
IResource resource = resourcesEnum.next();
243241
refreshResource(resource, subMonitor.split(1));
244242
} catch (CoreException e) {
245-
errors = recordError(errors, e);
243+
errors.add(e.getStatus());
246244
}
247245
}
248-
if (errors != null) {
249-
errorStatus[0] = errors;
246+
if (!errors.isEmpty()) {
247+
MultiStatus multiStatus = new MultiStatus(IDEWorkbenchPlugin.IDE_WORKBENCH, IStatus.ERROR,
248+
getProblemsMessage(), null);
249+
for (IStatus s : errors) {
250+
multiStatus.merge(s);
251+
}
252+
errorStatus[0] = multiStatus;
250253
}
251254
}
252255
};
253256
}
254257

258+
/**
259+
* Creates a {@link WorkspaceJob} that refreshes the given resources under the
260+
* given scheduling rule. The job is not yet scheduled when returned, allowing
261+
* callers to attach listeners before scheduling.
262+
*
263+
* @param resources resources to refresh; must not be <code>null</code>
264+
* @param rule scheduling rule for the job (a project or workspace root)
265+
* @return the created but unscheduled job
266+
* @since 3.23
267+
*/
268+
protected WorkspaceJob createRefreshJob(List<? extends IResource> resources, ISchedulingRule rule) {
269+
final IStatus[] errorStatus = { Status.OK_STATUS };
270+
WorkspaceModifyOperation op = new WorkspaceModifyOperation(rule) {
271+
@Override
272+
public void execute(IProgressMonitor mon) {
273+
SubMonitor subMonitor = SubMonitor.convert(mon, resources.size());
274+
subMonitor.setTaskName(getOperationMessage());
275+
List<IStatus> errors = new ArrayList<>();
276+
for (IResource resource : resources) {
277+
try {
278+
refreshResource(resource, subMonitor.split(1));
279+
} catch (CoreException e) {
280+
errors.add(e.getStatus());
281+
}
282+
}
283+
if (!errors.isEmpty()) {
284+
MultiStatus multiStatus = new MultiStatus(IDEWorkbenchPlugin.IDE_WORKBENCH, IStatus.ERROR,
285+
getProblemsMessage(), null);
286+
for (IStatus s : errors) {
287+
multiStatus.merge(s);
288+
}
289+
errorStatus[0] = multiStatus;
290+
}
291+
}
292+
};
293+
WorkspaceJob job = new WorkspaceJob("refresh") { //$NON-NLS-1$
294+
@Override
295+
public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
296+
try {
297+
op.run(monitor);
298+
} catch (InvocationTargetException e) {
299+
String msg = NLS.bind(IDEWorkbenchMessages.WorkspaceAction_logTitle, getClass().getName(),
300+
e.getTargetException());
301+
return StatusUtil.newStatus(IStatus.ERROR, msg, e.getTargetException());
302+
} catch (InterruptedException e) {
303+
return Status.CANCEL_STATUS;
304+
}
305+
return errorStatus[0];
306+
}
307+
};
308+
job.setRule(op.getRule());
309+
job.setUser(true);
310+
return job;
311+
}
312+
313+
/**
314+
* Creates and schedules a {@link WorkspaceJob} for the given resources.
315+
* Subclasses may override to attach listeners before the job is scheduled.
316+
*
317+
* @param resources resources to refresh; must not be <code>null</code>
318+
* @param rule scheduling rule for the job (a project or workspace root)
319+
* @since 3.23
320+
*/
321+
protected void scheduleRefreshJob(List<? extends IResource> resources, ISchedulingRule rule) {
322+
createRefreshJob(resources, rule).schedule();
323+
}
324+
255325
/**
256326
* Refresh the resource (with a check for deleted projects).
257327
* <p>
@@ -285,32 +355,19 @@ protected void refreshResource(IResource resource, IProgressMonitor monitor) thr
285355

286356
@Override
287357
public void run() {
288-
final IStatus[] errorStatus = new IStatus[1];
289-
errorStatus[0] = Status.OK_STATUS;
290-
final WorkspaceModifyOperation op = (WorkspaceModifyOperation) createOperation(errorStatus);
291-
WorkspaceJob job = new WorkspaceJob("refresh") { //$NON-NLS-1$
292-
293-
@Override
294-
public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
295-
try {
296-
op.run(monitor);
297-
} catch (InvocationTargetException e) {
298-
String msg = NLS.bind(
299-
IDEWorkbenchMessages.WorkspaceAction_logTitle, getClass()
300-
.getName(), e.getTargetException());
301-
throw new CoreException(StatusUtil.newStatus(IStatus.ERROR, msg, e.getTargetException()));
302-
} catch (InterruptedException e) {
303-
return Status.CANCEL_STATUS;
304-
}
305-
return errorStatus[0];
306-
}
307-
308-
};
309-
ISchedulingRule rule = op.getRule();
310-
if (rule != null) {
311-
job.setRule(rule);
358+
List<? extends IResource> actionResources = new ArrayList<>(getActionResources());
359+
if (shouldPerformResourcePruning()) {
360+
actionResources = pruneResources(actionResources);
361+
}
362+
// Group resources by scheduling rule so each project can be refreshed in
363+
// parallel while still holding a project-level rule during its refresh.
364+
Map<ISchedulingRule, List<IResource>> byRule = new LinkedHashMap<>();
365+
for (IResource resource : actionResources) {
366+
ISchedulingRule rule = (resource.getType() == IResource.ROOT) ? resource : resource.getProject();
367+
byRule.computeIfAbsent(rule, r -> new ArrayList<>()).add(resource);
368+
}
369+
for (Map.Entry<ISchedulingRule, List<IResource>> entry : byRule.entrySet()) {
370+
scheduleRefreshJob(entry.getValue(), entry.getKey());
312371
}
313-
job.setUser(true);
314-
job.schedule();
315372
}
316373
}

bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/internal/navigator/resources/actions/ResourceMgmtActionProvider.java

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,27 @@
1515

1616
package org.eclipse.ui.internal.navigator.resources.actions;
1717

18-
import java.lang.reflect.InvocationTargetException;
1918
import java.util.ArrayList;
2019
import java.util.Collections;
2120
import java.util.List;
2221

2322
import org.eclipse.core.resources.ICommand;
2423
import org.eclipse.core.resources.IProject;
2524
import org.eclipse.core.resources.IncrementalProjectBuilder;
25+
import org.eclipse.core.resources.IResource;
2626
import org.eclipse.core.resources.ResourcesPlugin;
2727
import org.eclipse.core.resources.WorkspaceJob;
2828
import org.eclipse.core.runtime.CoreException;
2929
import org.eclipse.core.runtime.IAdaptable;
30-
import org.eclipse.core.runtime.IProgressMonitor;
31-
import org.eclipse.core.runtime.IStatus;
32-
import org.eclipse.core.runtime.Status;
30+
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
3331
import org.eclipse.core.runtime.jobs.ISchedulingRule;
32+
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
3433
import org.eclipse.jface.action.IMenuManager;
3534
import org.eclipse.jface.action.Separator;
3635
import org.eclipse.jface.resource.ImageDescriptor;
3736
import org.eclipse.jface.viewers.IStructuredSelection;
3837
import org.eclipse.jface.viewers.StructuredViewer;
3938
import org.eclipse.jface.window.IShellProvider;
40-
import org.eclipse.osgi.util.NLS;
4139
import org.eclipse.swt.widgets.Shell;
4240
import org.eclipse.ui.IActionBars;
4341
import org.eclipse.ui.IWorkbenchCommandConstants;
@@ -48,11 +46,8 @@
4846
import org.eclipse.ui.actions.CloseUnrelatedProjectsAction;
4947
import org.eclipse.ui.actions.OpenResourceAction;
5048
import org.eclipse.ui.actions.RefreshAction;
51-
import org.eclipse.ui.actions.WorkspaceModifyOperation;
5249
import org.eclipse.ui.ide.IDEActionFactory;
5350
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
54-
import org.eclipse.ui.internal.navigator.NavigatorPlugin;
55-
import org.eclipse.ui.internal.navigator.resources.plugin.WorkbenchNavigatorMessages;
5651
import org.eclipse.ui.navigator.CommonActionProvider;
5752
import org.eclipse.ui.navigator.ICommonActionExtensionSite;
5853
import org.eclipse.ui.navigator.ICommonMenuConstants;
@@ -215,40 +210,22 @@ protected void makeActions() {
215210

216211
refreshAction = new RefreshAction(sp) {
217212
@Override
218-
public void run() {
219-
final IStatus[] errorStatus = new IStatus[1];
220-
errorStatus[0] = Status.OK_STATUS;
221-
final WorkspaceModifyOperation op = (WorkspaceModifyOperation) createOperation(errorStatus);
222-
WorkspaceJob job = new WorkspaceJob("refresh") { //$NON-NLS-1$
223-
213+
protected void scheduleRefreshJob(List<? extends IResource> resources, ISchedulingRule rule) {
214+
WorkspaceJob job = createRefreshJob(resources, rule);
215+
job.addJobChangeListener(new JobChangeAdapter() {
224216
@Override
225-
public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
226-
try {
227-
op.run(monitor);
228-
if (shell != null && !shell.isDisposed()) {
229-
shell.getDisplay().asyncExec(() -> {
230-
StructuredViewer viewer = getActionSite().getStructuredViewer();
231-
if (viewer != null && viewer.getControl() != null
232-
&& !viewer.getControl().isDisposed()) {
233-
viewer.refresh();
234-
}
235-
});
236-
}
237-
} catch (InvocationTargetException e) {
238-
String msg = NLS.bind(WorkbenchNavigatorMessages.ResourceMgmtActionProvider_logTitle, getClass().getName(), e.getTargetException());
239-
throw new CoreException(new Status(IStatus.ERROR, NavigatorPlugin.PLUGIN_ID, IStatus.ERROR, msg, e.getTargetException()));
240-
} catch (InterruptedException e) {
241-
return Status.CANCEL_STATUS;
217+
public void done(IJobChangeEvent event) {
218+
if (shell != null && !shell.isDisposed()) {
219+
shell.getDisplay().asyncExec(() -> {
220+
StructuredViewer viewer = getActionSite().getStructuredViewer();
221+
if (viewer != null && viewer.getControl() != null
222+
&& !viewer.getControl().isDisposed()) {
223+
viewer.refresh();
224+
}
225+
});
242226
}
243-
return errorStatus[0];
244227
}
245-
246-
};
247-
ISchedulingRule rule = op.getRule();
248-
if (rule != null) {
249-
job.setRule(rule);
250-
}
251-
job.setUser(true);
228+
});
252229
job.schedule();
253230
}
254231
};

0 commit comments

Comments
 (0)