|
38 | 38 | import java.util.Optional; |
39 | 39 | import java.util.Set; |
40 | 40 | import java.util.concurrent.ConcurrentHashMap; |
| 41 | +import java.util.concurrent.TimeUnit; |
41 | 42 | import java.util.function.Predicate; |
42 | 43 | import org.eclipse.core.runtime.Assert; |
43 | 44 | import org.eclipse.core.runtime.IProgressMonitor; |
@@ -176,6 +177,23 @@ public class ProgressManager extends ProgressProvider implements IProgressServic |
176 | 177 |
|
177 | 178 | private final Throttler uiRefreshThrottler; |
178 | 179 |
|
| 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 | + |
179 | 197 | /** |
180 | 198 | * Returns the progress manager currently in use. |
181 | 199 | * |
@@ -360,6 +378,7 @@ public void setBlocked(IStatus reason) { |
360 | 378 | * Send pending notifications to listeners. |
361 | 379 | */ |
362 | 380 | /* Visible for testing */ public void notifyListeners() { |
| 381 | + promotePendingJobs(); |
363 | 382 | Set<GroupInfo> localPendingGroupUpdates, localPendingGroupRemoval; |
364 | 383 | Map<JobInfo, Set<IJobProgressManagerListener>> localPendingJobUpdates, localPendingJobAddition, |
365 | 384 | localPendingJobRemoval; |
@@ -395,6 +414,33 @@ public void setBlocked(IStatus reason) { |
395 | 414 | localPendingGroupRemoval.forEach(group -> { |
396 | 415 | listeners.forEach(listener -> listener.removeGroup(group)); |
397 | 416 | }); |
| 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 | + } |
398 | 444 | } |
399 | 445 |
|
400 | 446 | private void setUpImages() { |
@@ -508,6 +554,10 @@ public void awake(IJobChangeEvent event) { |
508 | 554 |
|
509 | 555 | @Override |
510 | 556 | 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 | + } |
511 | 561 | if (managedJobs.contains(event.getJob())) { // Are we showing this? |
512 | 562 | sleepJobInfo(progressFor(event.getJob()).getJobInfo()); |
513 | 563 | } |
@@ -629,6 +679,10 @@ void removeListener(IJobProgressManagerListener listener) { |
629 | 679 | */ |
630 | 680 | public void refreshJobInfo(JobInfo info) { |
631 | 681 | 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 | + } |
632 | 686 | synchronized (pendingUpdatesMutex) { |
633 | 687 | Predicate<IJobProgressManagerListener> predicate = listener -> !isNeverDisplaying(info.getJob(), listener.showsDebug()); |
634 | 688 | rememberListenersForJob(info, pendingJobUpdates, predicate); |
@@ -658,6 +712,9 @@ public JobInfo removeJob(Job job) { |
658 | 712 | JobInfo info; |
659 | 713 | synchronized (runnableMonitors) { |
660 | 714 | 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); |
661 | 718 | managedJobs.remove(job); |
662 | 719 | synchronized (pendingUpdatesMutex) { |
663 | 720 | Predicate<IJobProgressManagerListener> predicate = listener -> !isNeverDisplaying(info.getJob(), listener.showsDebug()); |
@@ -704,12 +761,53 @@ public void addJobInfo(JobInfo info) { |
704 | 761 | refreshGroup(group); |
705 | 762 | } |
706 | 763 |
|
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) { |
708 | 781 | synchronized (pendingUpdatesMutex) { |
709 | 782 | Predicate<IJobProgressManagerListener> predicate = listener -> !isCurrentDisplaying(info.getJob(), listener.showsDebug()); |
710 | 783 | rememberListenersForJob(info, pendingJobAddition, predicate); |
711 | 784 | } |
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); |
713 | 811 | } |
714 | 812 |
|
715 | 813 | private void rememberListenersForJob(JobInfo info, Map<JobInfo, Set<IJobProgressManagerListener>> listenersMap, Predicate<IJobProgressManagerListener> predicate) { |
|
0 commit comments