Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
Expand Down Expand Up @@ -176,6 +177,23 @@ public class ProgressManager extends ProgressProvider implements IProgressServic

private final Throttler uiRefreshThrottler;

/**
* System property overriding, in milliseconds, the grace period a job must run
* before it is shown in the progress view; {@code 0} disables it and restores
* the immediate display of jobs.
*/
public static final String GRACE_PERIOD_PROPERTY = "org.eclipse.ui.progress.viewGracePeriod"; //$NON-NLS-1$

private static final int MINIMUM_GRACE_PERIOD = 200;

/**
* Scheduled jobs not yet announced, mapped to the {@link System#nanoTime()} at
* which their grace period elapses. Jobs that finish, sleep or are cancelled
* before then are removed here and never appear, which avoids flicker from very
* short running jobs.
*/
private final Map<Job, Long> pendingJobAdditions = new ConcurrentHashMap<>();

/**
* Returns the progress manager currently in use.
*
Expand Down Expand Up @@ -360,6 +378,7 @@ public void setBlocked(IStatus reason) {
* Send pending notifications to listeners.
*/
/* Visible for testing */ public void notifyListeners() {
promotePendingJobs();
Set<GroupInfo> localPendingGroupUpdates, localPendingGroupRemoval;
Map<JobInfo, Set<IJobProgressManagerListener>> localPendingJobUpdates, localPendingJobAddition,
localPendingJobRemoval;
Expand Down Expand Up @@ -395,6 +414,33 @@ public void setBlocked(IStatus reason) {
localPendingGroupRemoval.forEach(group -> {
listeners.forEach(listener -> listener.removeGroup(group));
});

// Keep ticking so pending jobs are announced even when nothing else updates.
if (!pendingJobAdditions.isEmpty()) {
uiRefreshThrottler.throttledAsyncExec();
}
}

/**
* Announces the jobs whose grace period has elapsed. Jobs removed from
* {@link #pendingJobAdditions} meanwhile are never announced.
*/
private void promotePendingJobs() {
if (pendingJobAdditions.isEmpty()) {
return;
}
long now = System.nanoTime();
for (Iterator<Entry<Job, Long>> it = pendingJobAdditions.entrySet().iterator(); it.hasNext();) {
Entry<Job, Long> entry = it.next();
if (now - entry.getValue() < 0) {
continue; // still within its grace period
}
it.remove();
Job job = entry.getKey();
if (managedJobs.contains(job)) {
rememberJobAddition(progressFor(job).getJobInfo());
}
}
}

private void setUpImages() {
Expand Down Expand Up @@ -508,6 +554,10 @@ public void awake(IJobChangeEvent event) {

@Override
public void sleeping(IJobChangeEvent event) {
if (cancelPendingAdd(event.getJob())) {
// Slept before its grace period elapsed; never announced, nothing to sleep.
return;
}
if (managedJobs.contains(event.getJob())) { // Are we showing this?
sleepJobInfo(progressFor(event.getJob()).getJobInfo());
}
Expand Down Expand Up @@ -629,6 +679,10 @@ void removeListener(IJobProgressManagerListener listener) {
*/
public void refreshJobInfo(JobInfo info) {
checkForStaleness(info.getJob());
if (pendingJobAdditions.containsKey(info.getJob())) {
// Still within its grace period; state is read when it is announced.
return;
}
synchronized (pendingUpdatesMutex) {
Predicate<IJobProgressManagerListener> predicate = listener -> !isNeverDisplaying(info.getJob(), listener.showsDebug());
rememberListenersForJob(info, pendingJobUpdates, predicate);
Expand Down Expand Up @@ -658,6 +712,9 @@ public JobInfo removeJob(Job job) {
JobInfo info;
synchronized (runnableMonitors) {
info = progressFor(job).getJobInfo();
// Drop a pending add so the job is never shown; the removal below still
// captures jobs that must be kept (KEEP_PROPERTY or an error result).
cancelPendingAdd(job);
managedJobs.remove(job);
synchronized (pendingUpdatesMutex) {
Predicate<IJobProgressManagerListener> predicate = listener -> !isNeverDisplaying(info.getJob(), listener.showsDebug());
Expand Down Expand Up @@ -704,12 +761,53 @@ public void addJobInfo(JobInfo info) {
refreshGroup(group);
}

managedJobs.add(info.getJob());
Job job = info.getJob();
managedJobs.add(job);

int gracePeriod = getViewGracePeriod();
if (gracePeriod <= 0) {
rememberJobAddition(info);
} else {
// Defer the announcement; promotePendingJobs picks it up once the grace period elapses.
pendingJobAdditions.put(job, System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(gracePeriod));
}
uiRefreshThrottler.throttledExec();
}

/**
* Remembers a job addition for the registered listeners.
*/
private void rememberJobAddition(JobInfo info) {
synchronized (pendingUpdatesMutex) {
Predicate<IJobProgressManagerListener> predicate = listener -> !isCurrentDisplaying(info.getJob(), listener.showsDebug());
rememberListenersForJob(info, pendingJobAddition, predicate);
}
uiRefreshThrottler.throttledExec();
}

/**
* Cancels a still pending grace-period add so the job is never announced.
*
* @return {@code true} if an add was pending and got cancelled
*/
private boolean cancelPendingAdd(Job job) {
return pendingJobAdditions.remove(job) != null;
}

/**
* Returns the grace period in milliseconds a job must run before it is shown,
* overridable through the {@link #GRACE_PERIOD_PROPERTY} system property.
* {@code 0} disables it.
*/
private int getViewGracePeriod() {
String override = System.getProperty(GRACE_PERIOD_PROPERTY);
if (override != null) {
try {
return Math.max(0, Integer.parseInt(override));
} catch (NumberFormatException e) {
// Ignore and fall back to the computed default.
}
}
return Math.max(MINIMUM_GRACE_PERIOD, getLongOperationTime() / 2);
}

private void rememberListenersForJob(JobInfo info, Map<JobInfo, Set<IJobProgressManagerListener>> listenersMap, Predicate<IJobProgressManagerListener> predicate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.eclipse.ui.internal.progress.JobInfo;
import org.eclipse.ui.internal.progress.JobTreeElement;
import org.eclipse.ui.internal.progress.ProgressInfoItem;
import org.eclipse.ui.internal.progress.ProgressManager;
import org.eclipse.ui.internal.progress.TaskInfo;
import org.eclipse.ui.progress.IProgressConstants;
import org.junit.After;
Expand Down Expand Up @@ -206,6 +207,53 @@ public void testItemOrder() throws Exception {
}
}

@Test
public void testShortJobIsSuppressedFromView() throws Exception {
// A grace period far longer than the job's lifetime: the job must never
// be shown as running and therefore never flickers in the view.
System.setProperty(ProgressManager.GRACE_PERIOD_PROPERTY, Long.toString(TimeUnit.MINUTES.toMillis(1)));
DummyJob job = new DummyJob("Short job", Status.OK_STATUS);
try {
openProgressView();
job.schedule();
job.join();
// Flush any pending throttled updates.
processEventsUntil(() -> false, 500);
assertEquals("Short job should not appear in the progress view", 0, countJobs(job));
} finally {
System.clearProperty(ProgressManager.GRACE_PERIOD_PROPERTY);
processEvents();
}
}

@Test
public void testLongRunningJobAppearsAfterGracePeriod() throws Exception {
long grace = TimeUnit.SECONDS.toMillis(2);
System.setProperty(ProgressManager.GRACE_PERIOD_PROPERTY, Long.toString(grace));
DummyJob job = new DummyJob("Grace period job", Status.OK_STATUS);
job.shouldFinish = false;
try {
openProgressView();
long start = System.currentTimeMillis();
job.schedule();
// Wait until the job is actually running.
processEventsUntil(() -> job.inProgress, TimeUnit.SECONDS.toMillis(3));
// Within the grace period the job must not be shown yet.
if (System.currentTimeMillis() - start < grace) {
assertEquals("Job appeared before its grace period elapsed", 0, countJobs(job));
}
// Once the grace period elapses, the job appears.
processEventsUntil(() -> countJobs(job) == 1, grace + TimeUnit.SECONDS.toMillis(5));
assertEquals("Job did not appear after its grace period", 1, countJobs(job));
} finally {
job.shouldFinish = true;
job.cancel();
job.join();
processEvents();
System.clearProperty(ProgressManager.GRACE_PERIOD_PROPERTY);
}
}

private int countJobs(Job job) {
int count = 0;
ProgressInfoItem[] progressInfoItems = progressView.getViewer().getProgressInfoItems();
Expand Down
Loading