1 package org.simantics.workbench.internal;
3 import org.eclipse.core.runtime.IProgressMonitor;
4 import org.eclipse.core.runtime.IStatus;
5 import org.eclipse.core.runtime.Status;
6 import org.eclipse.core.runtime.jobs.Job;
7 import org.eclipse.swt.SWT;
8 import org.eclipse.swt.SWTException;
9 import org.eclipse.swt.widgets.Display;
10 import org.eclipse.swt.widgets.Event;
11 import org.eclipse.swt.widgets.Listener;
12 import org.eclipse.ui.PlatformUI;
13 import org.eclipse.ui.application.IWorkbenchConfigurer;
14 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
15 import org.eclipse.ui.internal.ide.Policy;
18 * The idle helper detects when the system is idle in order to perform garbage
19 * collection in a way that minimizes impact on responsiveness of the UI.
20 * The algorithm for determining when to perform a garbage collection
23 * - Never gc if there is a test harness present
24 * - Don't gc if background jobs are running
25 * - Don't gc if the keyboard or mouse have been active within IDLE_INTERVAL
26 * - Don't gc if there has been a GC within the minimum gc interval (system property PROP_GC_INTERVAL)
27 * - After a gc, don't gc again until (duration * GC_DELAY_MULTIPLIER) has elapsed.
28 * For example, if a GC takes 100ms and the multiplier is 60, don't gc for at least five seconds
29 * - Never gc again if any single gc takes longer than system property PROP_GC_MAX
34 * The default minimum time between garbage collections.
36 private static final int DEFAULT_GC_INTERVAL = 60000;
39 * The default maximum duration for a garbage collection, beyond which
40 * the explicit gc mechanism is automatically disabled.
42 private static final int DEFAULT_GC_MAX = 8000;
45 * The multiple of the last gc duration before we will consider doing
48 private static final int GC_DELAY_MULTIPLIER = 60;
51 * The time interval of no keyboard or mouse events after which the system
54 private static final int IDLE_INTERVAL = 5000;
57 * The name of the boolean system property that specifies whether explicit
58 * garbage collection is enabled.
60 private static final String PROP_GC = "ide.gc"; //$NON-NLS-1$
63 * The name of the integer system property that specifies the minimum time
64 * interval in milliseconds between garbage collections.
66 private static final String PROP_GC_INTERVAL = "ide.gc.interval"; //$NON-NLS-1$
69 * The name of the integer system property that specifies the maximum
70 * duration for a garbage collection. If this duration is ever exceeded, the
71 * explicit gc mechanism is disabled for the remainder of the session.
73 private static final String PROP_GC_MAX = "ide.gc.max"; //$NON-NLS-1$
75 protected IWorkbenchConfigurer configurer;
77 private Listener idleListener;
80 * The last time we garbage collected.
82 private long lastGC = System.currentTimeMillis();
85 * The maximum gc duration. If this value is exceeded, the
86 * entire explicit gc mechanism is disabled.
88 private int maxGC = DEFAULT_GC_MAX;
90 * The minimum time interval until the next garbage collection
92 private int minGCInterval = DEFAULT_GC_INTERVAL;
95 * The time interval until the next garbage collection
97 private int nextGCInterval = DEFAULT_GC_INTERVAL;
101 private Runnable handler;
104 * Creates and initializes the idle handler
105 * @param aConfigurer The workbench configurer.
107 IDEIdleHelper(IWorkbenchConfigurer aConfigurer) {
108 this.configurer = aConfigurer;
109 //don't gc while running tests because performance tests are sensitive to timing (see bug 121562)
110 if (PlatformUI.getTestableObject().getTestHarness() != null) {
113 String enabled = System.getProperty(PROP_GC);
114 //gc is turned on by default if property is missing
115 if (enabled != null && enabled.equalsIgnoreCase(Boolean.FALSE.toString())) {
119 Integer prop = Integer.getInteger(PROP_GC_INTERVAL);
120 if (prop != null && prop.intValue() >= 0) {
121 minGCInterval = nextGCInterval = prop.intValue();
124 //init max gc interval
125 prop = Integer.getInteger(PROP_GC_MAX);
127 maxGC = prop.intValue();
130 createGarbageCollectionJob();
133 final Display display = configurer.getWorkbench().getDisplay();
134 handler = new Runnable() {
136 if (!display.isDisposed() && !configurer.getWorkbench().isClosing()) {
138 final long start = System.currentTimeMillis();
139 //don't garbage collect if background jobs are running
140 if (!Job.getJobManager().isIdle()) {
141 nextInterval = IDLE_INTERVAL;
142 } else if ((start - lastGC) < nextGCInterval) {
143 //don't garbage collect if we have collected within the specific interval
144 nextInterval = nextGCInterval - (int) (start - lastGC);
147 nextInterval = minGCInterval;
149 display.timerExec(nextInterval, this);
153 idleListener = new Listener() {
154 public void handleEvent(Event event) {
155 display.timerExec(IDLE_INTERVAL, handler);
158 display.addFilter(SWT.KeyUp, idleListener);
159 display.addFilter(SWT.MouseUp, idleListener);
163 * Creates the job that performs garbage collection
165 private void createGarbageCollectionJob() {
166 gcJob = new Job(IDEWorkbenchMessages.IDEIdleHelper_backgroundGC) {
167 protected IStatus run(IProgressMonitor monitor) {
168 final Display display = configurer.getWorkbench().getDisplay();
169 if (display != null && !display.isDisposed()) {
170 final long start = System.currentTimeMillis();
172 System.runFinalization();
174 final int duration = (int) (System.currentTimeMillis() - start);
175 if (Policy.DEBUG_GC) {
176 System.out.println("Explicit GC took: " + duration); //$NON-NLS-1$
178 if (duration > maxGC) {
179 if (Policy.DEBUG_GC) {
180 System.out.println("Further explicit GCs disabled due to long GC"); //$NON-NLS-1$
184 //if the gc took a long time, ensure the next gc doesn't happen for awhile
185 nextGCInterval = Math.max(minGCInterval, GC_DELAY_MULTIPLIER * duration);
186 if (Policy.DEBUG_GC) {
187 System.out.println("Next GC to run in: " + nextGCInterval); //$NON-NLS-1$
191 return Status.OK_STATUS;
194 gcJob.setSystem(true);
198 * Shuts down the idle helper, removing any installed listeners, etc.
201 if (idleListener == null) {
204 final Display display = configurer.getWorkbench().getDisplay();
205 if (display != null && !display.isDisposed()) {
207 display.asyncExec(new Runnable() {
209 display.timerExec(-1, handler);
210 display.removeFilter(SWT.KeyUp, idleListener);
211 display.removeFilter(SWT.MouseUp, idleListener);
214 } catch (SWTException ex) {
215 // ignore (display might be disposed)