Skip to content

Commit 8603a98

Browse files
committed
Defer short-running jobs from the progress view
Non-system jobs were shown in the progress view the moment they were scheduled and removed when they finished, so jobs that complete within a few hundred milliseconds flashed in and out and could not be read. Add a per-job grace period before a job is announced to the progress listeners. Jobs that finish, sleep or are cancelled before the grace period elapses are never shown, which removes the flicker for refresh, validation and decoration bursts. The grace period defaults to half of getLongOperationTime() with a lower bound of 200ms and can be overridden through the org.eclipse.ui.progress.viewGracePeriod system property. Jobs kept after completion (KEEP_PROPERTY or an error result) are still captured. Fixes #4126
1 parent ef8e9f8 commit 8603a98

2 files changed

Lines changed: 148 additions & 2 deletions

File tree

bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/progress/ProgressManager.java

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.Optional;
3939
import java.util.Set;
4040
import java.util.concurrent.ConcurrentHashMap;
41+
import java.util.concurrent.TimeUnit;
4142
import java.util.function.Predicate;
4243
import org.eclipse.core.runtime.Assert;
4344
import org.eclipse.core.runtime.IProgressMonitor;
@@ -176,6 +177,23 @@ public class ProgressManager extends ProgressProvider implements IProgressServic
176177

177178
private final Throttler uiRefreshThrottler;
178179

180+
/**
181+
* System property overriding, in milliseconds, the grace period a job must run
182+
* before it is shown in the progress view; {@code 0} disables it and restores
183+
* the immediate display of jobs.
184+
*/
185+
public static final String GRACE_PERIOD_PROPERTY = "org.eclipse.ui.progress.viewGracePeriod"; //$NON-NLS-1$
186+
187+
private static final int MINIMUM_GRACE_PERIOD = 200;
188+
189+
/**
190+
* Scheduled jobs not yet announced, mapped to the {@link System#nanoTime()} at
191+
* which their grace period elapses. Jobs that finish, sleep or are cancelled
192+
* before then are removed here and never appear, which avoids flicker from very
193+
* short running jobs.
194+
*/
195+
private final Map<Job, Long> pendingJobAdditions = new ConcurrentHashMap<>();
196+
179197
/**
180198
* Returns the progress manager currently in use.
181199
*
@@ -360,6 +378,7 @@ public void setBlocked(IStatus reason) {
360378
* Send pending notifications to listeners.
361379
*/
362380
/* Visible for testing */ public void notifyListeners() {
381+
promotePendingJobs();
363382
Set<GroupInfo> localPendingGroupUpdates, localPendingGroupRemoval;
364383
Map<JobInfo, Set<IJobProgressManagerListener>> localPendingJobUpdates, localPendingJobAddition,
365384
localPendingJobRemoval;
@@ -395,6 +414,33 @@ public void setBlocked(IStatus reason) {
395414
localPendingGroupRemoval.forEach(group -> {
396415
listeners.forEach(listener -> listener.removeGroup(group));
397416
});
417+
418+
// Keep ticking so pending jobs are announced even when nothing else updates.
419+
if (!pendingJobAdditions.isEmpty()) {
420+
uiRefreshThrottler.throttledAsyncExec();
421+
}
422+
}
423+
424+
/**
425+
* Announces the jobs whose grace period has elapsed. Jobs removed from
426+
* {@link #pendingJobAdditions} meanwhile are never announced.
427+
*/
428+
private void promotePendingJobs() {
429+
if (pendingJobAdditions.isEmpty()) {
430+
return;
431+
}
432+
long now = System.nanoTime();
433+
for (Iterator<Entry<Job, Long>> it = pendingJobAdditions.entrySet().iterator(); it.hasNext();) {
434+
Entry<Job, Long> entry = it.next();
435+
if (now - entry.getValue() < 0) {
436+
continue; // still within its grace period
437+
}
438+
it.remove();
439+
Job job = entry.getKey();
440+
if (managedJobs.contains(job)) {
441+
rememberJobAddition(progressFor(job).getJobInfo());
442+
}
443+
}
398444
}
399445

400446
private void setUpImages() {
@@ -508,6 +554,10 @@ public void awake(IJobChangeEvent event) {
508554

509555
@Override
510556
public void sleeping(IJobChangeEvent event) {
557+
if (cancelPendingAdd(event.getJob())) {
558+
// Slept before its grace period elapsed; never announced, nothing to sleep.
559+
return;
560+
}
511561
if (managedJobs.contains(event.getJob())) { // Are we showing this?
512562
sleepJobInfo(progressFor(event.getJob()).getJobInfo());
513563
}
@@ -629,6 +679,10 @@ void removeListener(IJobProgressManagerListener listener) {
629679
*/
630680
public void refreshJobInfo(JobInfo info) {
631681
checkForStaleness(info.getJob());
682+
if (pendingJobAdditions.containsKey(info.getJob())) {
683+
// Still within its grace period; state is read when it is announced.
684+
return;
685+
}
632686
synchronized (pendingUpdatesMutex) {
633687
Predicate<IJobProgressManagerListener> predicate = listener -> !isNeverDisplaying(info.getJob(), listener.showsDebug());
634688
rememberListenersForJob(info, pendingJobUpdates, predicate);
@@ -658,6 +712,9 @@ public JobInfo removeJob(Job job) {
658712
JobInfo info;
659713
synchronized (runnableMonitors) {
660714
info = progressFor(job).getJobInfo();
715+
// Drop a pending add so the job is never shown; the removal below still
716+
// captures jobs that must be kept (KEEP_PROPERTY or an error result).
717+
cancelPendingAdd(job);
661718
managedJobs.remove(job);
662719
synchronized (pendingUpdatesMutex) {
663720
Predicate<IJobProgressManagerListener> predicate = listener -> !isNeverDisplaying(info.getJob(), listener.showsDebug());
@@ -704,12 +761,53 @@ public void addJobInfo(JobInfo info) {
704761
refreshGroup(group);
705762
}
706763

707-
managedJobs.add(info.getJob());
764+
Job job = info.getJob();
765+
managedJobs.add(job);
766+
767+
int gracePeriod = getViewGracePeriod();
768+
if (gracePeriod <= 0) {
769+
rememberJobAddition(info);
770+
} else {
771+
// Defer the announcement; promotePendingJobs picks it up once the grace period elapses.
772+
pendingJobAdditions.put(job, System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(gracePeriod));
773+
}
774+
uiRefreshThrottler.throttledExec();
775+
}
776+
777+
/**
778+
* Remembers a job addition for the registered listeners.
779+
*/
780+
private void rememberJobAddition(JobInfo info) {
708781
synchronized (pendingUpdatesMutex) {
709782
Predicate<IJobProgressManagerListener> predicate = listener -> !isCurrentDisplaying(info.getJob(), listener.showsDebug());
710783
rememberListenersForJob(info, pendingJobAddition, predicate);
711784
}
712-
uiRefreshThrottler.throttledExec();
785+
}
786+
787+
/**
788+
* Cancels a still pending grace-period add so the job is never announced.
789+
*
790+
* @return {@code true} if an add was pending and got cancelled
791+
*/
792+
private boolean cancelPendingAdd(Job job) {
793+
return pendingJobAdditions.remove(job) != null;
794+
}
795+
796+
/**
797+
* Returns the grace period in milliseconds a job must run before it is shown,
798+
* overridable through the {@link #GRACE_PERIOD_PROPERTY} system property.
799+
* {@code 0} disables it.
800+
*/
801+
private int getViewGracePeriod() {
802+
String override = System.getProperty(GRACE_PERIOD_PROPERTY);
803+
if (override != null) {
804+
try {
805+
return Math.max(0, Integer.parseInt(override));
806+
} catch (NumberFormatException e) {
807+
// Ignore and fall back to the computed default.
808+
}
809+
}
810+
return Math.max(MINIMUM_GRACE_PERIOD, getLongOperationTime() / 2);
713811
}
714812

715813
private void rememberListenersForJob(JobInfo info, Map<JobInfo, Set<IJobProgressManagerListener>> listenersMap, Predicate<IJobProgressManagerListener> predicate) {

tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/progress/ProgressViewTests.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.eclipse.ui.internal.progress.JobInfo;
3535
import org.eclipse.ui.internal.progress.JobTreeElement;
3636
import org.eclipse.ui.internal.progress.ProgressInfoItem;
37+
import org.eclipse.ui.internal.progress.ProgressManager;
3738
import org.eclipse.ui.internal.progress.TaskInfo;
3839
import org.eclipse.ui.progress.IProgressConstants;
3940
import org.junit.After;
@@ -206,6 +207,53 @@ public void testItemOrder() throws Exception {
206207
}
207208
}
208209

210+
@Test
211+
public void testShortJobIsSuppressedFromView() throws Exception {
212+
// A grace period far longer than the job's lifetime: the job must never
213+
// be shown as running and therefore never flickers in the view.
214+
System.setProperty(ProgressManager.GRACE_PERIOD_PROPERTY, Long.toString(TimeUnit.MINUTES.toMillis(1)));
215+
DummyJob job = new DummyJob("Short job", Status.OK_STATUS);
216+
try {
217+
openProgressView();
218+
job.schedule();
219+
job.join();
220+
// Flush any pending throttled updates.
221+
processEventsUntil(() -> false, 500);
222+
assertEquals("Short job should not appear in the progress view", 0, countJobs(job));
223+
} finally {
224+
System.clearProperty(ProgressManager.GRACE_PERIOD_PROPERTY);
225+
processEvents();
226+
}
227+
}
228+
229+
@Test
230+
public void testLongRunningJobAppearsAfterGracePeriod() throws Exception {
231+
long grace = TimeUnit.SECONDS.toMillis(2);
232+
System.setProperty(ProgressManager.GRACE_PERIOD_PROPERTY, Long.toString(grace));
233+
DummyJob job = new DummyJob("Grace period job", Status.OK_STATUS);
234+
job.shouldFinish = false;
235+
try {
236+
openProgressView();
237+
long start = System.currentTimeMillis();
238+
job.schedule();
239+
// Wait until the job is actually running.
240+
processEventsUntil(() -> job.inProgress, TimeUnit.SECONDS.toMillis(3));
241+
// Within the grace period the job must not be shown yet.
242+
if (System.currentTimeMillis() - start < grace) {
243+
assertEquals("Job appeared before its grace period elapsed", 0, countJobs(job));
244+
}
245+
// Once the grace period elapses, the job appears.
246+
processEventsUntil(() -> countJobs(job) == 1, grace + TimeUnit.SECONDS.toMillis(5));
247+
assertEquals("Job did not appear after its grace period", 1, countJobs(job));
248+
} finally {
249+
job.shouldFinish = true;
250+
job.cancel();
251+
job.join();
252+
processEvents();
253+
System.clearProperty(ProgressManager.GRACE_PERIOD_PROPERTY);
254+
}
255+
}
256+
209257
private int countJobs(Job job) {
210258
int count = 0;
211259
ProgressInfoItem[] progressInfoItems = progressView.getViewer().getProgressInfoItems();

0 commit comments

Comments
 (0)