Skip to content

Commit c402a17

Browse files
fedejeanneCopilot
andcommitted
Add ProgressServiceView demo view class #4202
It can be used to showcase a buggy behavior in IProgressService.run(). Contributes to #4202 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5fe0fc9 commit c402a17

2 files changed

Lines changed: 281 additions & 0 deletions

File tree

examples/org.eclipse.ui.examples.job/plugin.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
class="org.eclipse.ui.examples.jobs.views.JobsView"
2323
id="org.eclipse.ui.examples.jobs.views.JobsView">
2424
</view>
25+
<view
26+
name="Progress Service"
27+
icon="icons/job_view.gif"
28+
category="ProgressExamples"
29+
class="org.eclipse.ui.examples.jobs.views.ProgressServiceView"
30+
id="org.eclipse.ui.examples.jobs.views.ProgressServiceView">
31+
</view>
2532
</extension>
2633
<extension
2734
point="org.eclipse.ui.perspectiveExtensions">
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Eclipse contributors and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
package org.eclipse.ui.examples.jobs.views;
12+
13+
import java.lang.reflect.InvocationTargetException;
14+
import java.util.concurrent.ExecutorService;
15+
import java.util.concurrent.Executors;
16+
import org.eclipse.core.runtime.Platform;
17+
import org.eclipse.jface.operation.IRunnableWithProgress;
18+
import org.eclipse.jface.preference.IPreferenceStore;
19+
import org.eclipse.jface.util.IPropertyChangeListener;
20+
import org.eclipse.swt.SWT;
21+
import org.eclipse.swt.events.SelectionListener;
22+
import org.eclipse.swt.layout.GridData;
23+
import org.eclipse.swt.layout.GridLayout;
24+
import org.eclipse.swt.widgets.Button;
25+
import org.eclipse.swt.widgets.Composite;
26+
import org.eclipse.swt.widgets.Display;
27+
import org.eclipse.swt.widgets.Label;
28+
import org.eclipse.swt.widgets.Text;
29+
import org.eclipse.ui.PlatformUI;
30+
import org.eclipse.ui.internal.IPreferenceConstants;
31+
import org.eclipse.ui.part.ViewPart;
32+
import org.eclipse.ui.progress.IProgressService;
33+
34+
/**
35+
* A view that demonstrates (and lets you reproduce) the documented and
36+
* undocumented behaviors of
37+
* {@link IProgressService#run(boolean, boolean, IRunnableWithProgress)}
38+
* across every combination of the <code>fork</code>/<code>cancelable</code>
39+
* parameters, the "Always run in background" preference, and calling from a
40+
* non-UI thread.
41+
*/
42+
@SuppressWarnings("restriction")
43+
public class ProgressServiceView extends ViewPart {
44+
45+
private static final int SLEEP_STEP_MS = 100;
46+
47+
private final ExecutorService nonUiExecutor = Executors.newSingleThreadExecutor();
48+
49+
private Button backgroundPrefField;
50+
private IPropertyChangeListener prefListener;
51+
52+
private Button forkField;
53+
private Button cancelableField;
54+
private Text durationField;
55+
56+
@Override
57+
public void createPartControl(Composite parent) {
58+
Composite body = new Composite(parent, SWT.NONE);
59+
GridLayout layout = new GridLayout();
60+
layout.numColumns = 1;
61+
body.setLayout(layout);
62+
body.setLayoutData(new GridData(GridData.FILL_BOTH));
63+
64+
createPreferenceGroup(body);
65+
createRunGroup(body);
66+
createForkIllustrationGroup(body);
67+
createNonUiGroup(body);
68+
}
69+
70+
private void createPreferenceGroup(Composite parent) {
71+
Composite group = new Composite(parent, SWT.NONE);
72+
GridLayout layout = new GridLayout();
73+
layout.numColumns = 1;
74+
group.setLayout(layout);
75+
group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
76+
77+
backgroundPrefField = new Button(group, SWT.CHECK);
78+
backgroundPrefField.setText("Always run in background"); //$NON-NLS-1$
79+
backgroundPrefField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
80+
backgroundPrefField.setSelection(getPreferenceStore().getBoolean(IPreferenceConstants.RUN_IN_BACKGROUND));
81+
backgroundPrefField.addSelectionListener(SelectionListener.widgetSelectedAdapter(
82+
e -> getPreferenceStore().setValue(IPreferenceConstants.RUN_IN_BACKGROUND,
83+
backgroundPrefField.getSelection())));
84+
85+
Label hint = new Label(group, SWT.WRAP);
86+
hint.setText("This preference can also be set under Preferences > General."); //$NON-NLS-1$
87+
hint.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
88+
89+
prefListener = event -> {
90+
if (IPreferenceConstants.RUN_IN_BACKGROUND.equals(event.getProperty())) {
91+
Display.getDefault().asyncExec(() -> {
92+
if (!backgroundPrefField.isDisposed()) {
93+
backgroundPrefField
94+
.setSelection(getPreferenceStore().getBoolean(IPreferenceConstants.RUN_IN_BACKGROUND));
95+
}
96+
});
97+
}
98+
};
99+
getPreferenceStore().addPropertyChangeListener(prefListener);
100+
}
101+
102+
private void createRunGroup(Composite parent) {
103+
Composite group = new Composite(parent, SWT.NONE);
104+
GridLayout layout = new GridLayout();
105+
layout.numColumns = 2;
106+
group.setLayout(layout);
107+
group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
108+
109+
Label label = new Label(group, SWT.NONE);
110+
label.setText("Duration (ms):"); //$NON-NLS-1$
111+
durationField = new Text(group, SWT.BORDER);
112+
durationField.setText("3000"); //$NON-NLS-1$
113+
durationField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
114+
115+
forkField = new Button(group, SWT.CHECK);
116+
forkField.setText("fork"); //$NON-NLS-1$
117+
forkField.setSelection(true);
118+
GridData forkData = new GridData(GridData.FILL_HORIZONTAL);
119+
forkData.horizontalSpan = 2;
120+
forkField.setLayoutData(forkData);
121+
122+
cancelableField = new Button(group, SWT.CHECK);
123+
cancelableField.setText("cancelable"); //$NON-NLS-1$
124+
cancelableField.setSelection(true);
125+
GridData cancelableData = new GridData(GridData.FILL_HORIZONTAL);
126+
cancelableData.horizontalSpan = 2;
127+
cancelableField.setLayoutData(cancelableData);
128+
129+
Button run = new Button(group, SWT.PUSH);
130+
run.setText("Run via IProgressService.run(fork, cancelable, ...)"); //$NON-NLS-1$
131+
GridData runData = new GridData(GridData.FILL_HORIZONTAL);
132+
runData.horizontalSpan = 2;
133+
run.setLayoutData(runData);
134+
run.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> runViaProgressService()));
135+
}
136+
137+
private void createForkIllustrationGroup(Composite parent) {
138+
Composite group = new Composite(parent, SWT.NONE);
139+
GridLayout layout = new GridLayout();
140+
layout.numColumns = 1;
141+
group.setLayout(layout);
142+
group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
143+
144+
Button naive = new Button(group, SWT.PUSH);
145+
naive.setText("fork=false (naive - will freeze)"); //$NON-NLS-1$
146+
naive.setToolTipText("Calls run(false, true, ...) with a plain sleep loop. Watch the heartbeat stop."); //$NON-NLS-1$
147+
naive.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
148+
naive.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> runForkFalseNaive()));
149+
150+
Button pumping = new Button(group, SWT.PUSH);
151+
pumping.setText("fork=false (pumping events - correct)"); //$NON-NLS-1$
152+
pumping.setToolTipText(
153+
"Calls run(false, true, ...) with a loop that calls Display.readAndDispatch(). The heartbeat keeps ticking."); //$NON-NLS-1$
154+
pumping.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
155+
pumping.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> runForkFalsePumping()));
156+
}
157+
158+
private void createNonUiGroup(Composite parent) {
159+
Composite group = new Composite(parent, SWT.NONE);
160+
GridLayout layout = new GridLayout();
161+
layout.numColumns = 1;
162+
group.setLayout(layout);
163+
group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
164+
165+
Button nonUiButton = new Button(group, SWT.PUSH);
166+
nonUiButton.setText("Run from non-UI thread (expect exception)"); //$NON-NLS-1$
167+
nonUiButton.setToolTipText(
168+
"IProgressService.run() must be called from the UI thread; this documents the resulting exception."); //$NON-NLS-1$
169+
nonUiButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
170+
nonUiButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> runFromNonUIThread()));
171+
}
172+
173+
@SuppressWarnings("deprecation")
174+
private IPreferenceStore getPreferenceStore() {
175+
return PlatformUI.getWorkbench().getPreferenceStore();
176+
}
177+
178+
private long getDuration() {
179+
try {
180+
return Long.parseLong(durationField.getText().trim());
181+
} catch (NumberFormatException e) {
182+
Platform.getLog(ProgressServiceView.class).error(e.getMessage(), e);
183+
return 3000;
184+
}
185+
}
186+
187+
private IRunnableWithProgress createSleepRunnable(long durationMillis, boolean pumpEvents) {
188+
return monitor -> {
189+
int ticks = (int) Math.max(1, durationMillis / SLEEP_STEP_MS);
190+
monitor.beginTask("Simulated long-running operation", ticks); //$NON-NLS-1$
191+
for (int i = 0; i < ticks; i++) {
192+
if (monitor.isCanceled()) {
193+
return;
194+
}
195+
try {
196+
Thread.sleep(SLEEP_STEP_MS);
197+
} catch (InterruptedException e) {
198+
Thread.currentThread().interrupt();
199+
return;
200+
}
201+
if (pumpEvents) {
202+
Display display = Display.getCurrent();
203+
if (display != null) {
204+
while (display.readAndDispatch()) {
205+
// drain pending UI events so the heartbeat keeps ticking
206+
}
207+
}
208+
}
209+
monitor.worked(1);
210+
}
211+
};
212+
}
213+
214+
private void runViaProgressService() {
215+
boolean fork = forkField.getSelection();
216+
boolean cancelable = cancelableField.getSelection();
217+
long duration = getDuration();
218+
IProgressService service = PlatformUI.getWorkbench().getProgressService();
219+
try {
220+
service.run(fork, cancelable, createSleepRunnable(duration, false));
221+
} catch (InvocationTargetException | InterruptedException e) {
222+
Platform.getLog(ProgressServiceView.class).error(e.getMessage(), e);
223+
}
224+
}
225+
226+
private void runForkFalseNaive() {
227+
long duration = getDuration();
228+
boolean cancelable = cancelableField.getSelection();
229+
try {
230+
PlatformUI.getWorkbench().getProgressService().run(false, cancelable, createSleepRunnable(duration, false));
231+
} catch (InvocationTargetException | InterruptedException e) {
232+
Platform.getLog(ProgressServiceView.class).error(e.getMessage(), e);
233+
}
234+
}
235+
236+
private void runForkFalsePumping() {
237+
long duration = getDuration();
238+
boolean cancelable = cancelableField.getSelection();
239+
try {
240+
PlatformUI.getWorkbench().getProgressService().run(false, cancelable, createSleepRunnable(duration, true));
241+
} catch (InvocationTargetException | InterruptedException e) {
242+
Platform.getLog(ProgressServiceView.class).error(e.getMessage(), e);
243+
}
244+
}
245+
246+
private void runFromNonUIThread() {
247+
boolean fork = forkField.getSelection();
248+
boolean cancelable = cancelableField.getSelection();
249+
long duration = getDuration();
250+
nonUiExecutor.execute(() -> {
251+
try {
252+
PlatformUI.getWorkbench().getProgressService().run(fork, cancelable,
253+
createSleepRunnable(duration, false));
254+
} catch (Throwable t) {
255+
Platform.getLog(ProgressServiceView.class).error(t.getMessage(), t);
256+
}
257+
});
258+
}
259+
260+
@Override
261+
public void dispose() {
262+
if (prefListener != null) {
263+
getPreferenceStore().removePropertyChangeListener(prefListener);
264+
}
265+
nonUiExecutor.shutdownNow();
266+
super.dispose();
267+
}
268+
269+
@Override
270+
public void setFocus() {
271+
// do nothing
272+
}
273+
274+
}

0 commit comments

Comments
 (0)