blob: 0622d30352ff4f5c2ced2e84422231871c39c97b [file] [log] [blame]
package org.eclipse.ui.internal.ide;
import org.eclipse.core.runtime.Platform;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.application.IWorkbenchConfigurer;
/**
* The idle helper detects when the system is idle in order to perform garbage
* collection in a way that minimizes impact on responsiveness of the UI.
* The algorithm for determining when to perform a garbage collection
* is as follows:
*
* - Never gc if there is a test harness present
* - Don't gc if background jobs are running
* - Don't gc if the keyboard or mouse have been active within IDLE_INTERVAL
* - Don't gc if there has been a GC within the minimum gc interval (system property PROP_GC_INTERVAL)
* - After a gc, don't gc again until (duration * GC_DELAY_MULTIPLIER) has elapsed.
* For example, if a GC takes 100ms and the multiplier is 60, don't gc for at least five seconds
* - Never gc again if any single gc takes longer than system property PROP_GC_MAX
*/
class IDEIdleHelper {
/**
* The default minimum time between garbage collections.
*/
private static final int DEFAULT_GC_INTERVAL = 60000;
/**
* The default maximum duration for a garbage collection, beyond which
* the explicit gc mechanism is automatically disabled.
*/
private static final int DEFAULT_GC_MAX = 8000;
/**
* The multiple of the last gc duration before we will consider doing
* another one.
*/
private static final int GC_DELAY_MULTIPLIER = 60;
/**
* The time interval of no keyboard or mouse events after which the system
* is considered idle.
*/
private static final int IDLE_INTERVAL = 5000;
/**
* The name of the boolean system property that specifies whether explicit
* garbage collection is enabled.
*/
private static final String PROP_GC = "ide.gc"; //$NON-NLS-1$
/**
* The name of the integer system property that specifies the minimum time
* interval in milliseconds between garbage collections.
*/
private static final String PROP_GC_INTERVAL = "ide.gc.interval"; //$NON-NLS-1$
/**
* The name of the integer system property that specifies the maximum
* duration for a garbage collection. If this duration is ever exceeded, the
* explicit gc mechanism is disabled for the remainder of the session.
*/
private static final String PROP_GC_MAX = "ide.gc.max"; //$NON-NLS-1$
protected IWorkbenchConfigurer configurer;
private Listener idleListener;
/**
* The last time we garbage collected.
*/
private long lastGC = System.currentTimeMillis();
/**
* The maximum gc duration. If this value is exceeded, the
* entire explicit gc mechanism is disabled.
*/
private int maxGC = DEFAULT_GC_MAX;
/**
* The minimum time interval until the next garbage collection
*/
private int minGCInterval = DEFAULT_GC_INTERVAL;
/**
* The time interval until the next garbage collection
*/
private int nextGCInterval = DEFAULT_GC_INTERVAL;
/**
* Creates and initializes the idle handler
* @param aConfigurer The workbench configurer.
*/
IDEIdleHelper(IWorkbenchConfigurer aConfigurer) {
this.configurer = aConfigurer;
//don't gc while running tests because performance tests are sensitive to timing (see bug 121562)
if (PlatformUI.getTestableObject().getTestHarness() != null)
return;
String enabled = System.getProperty(PROP_GC);
//gc is turned on by default if property is missing
if (enabled != null && enabled.equalsIgnoreCase(Boolean.FALSE.toString()))
return;
//init gc interval
Integer prop = Integer.getInteger(PROP_GC_INTERVAL);
if (prop != null && prop.intValue() >= 0)
minGCInterval = nextGCInterval = prop.intValue();
//init max gc interval
prop = Integer.getInteger(PROP_GC_MAX);
if (prop != null)
maxGC = prop.intValue();
//hook idle handler
final Display display = configurer.getWorkbench().getDisplay();
final Runnable handler = new Runnable() {
public void run() {
if (!configurer.getWorkbench().isClosing())
display.timerExec(performGC(), this);
}
};
idleListener = new Listener() {
public void handleEvent(Event event) {
display.timerExec(IDLE_INTERVAL, handler);
}
};
display.addFilter(SWT.KeyUp, idleListener);
display.addFilter(SWT.MouseUp, idleListener);
}
/**
* The idle handler has detected that the system is idle. Perform
* idle processing. Returns the amount of time that should pass before
* the next GC.
*/
protected int performGC() {
//don't garbage collect if background jobs are running
if (!Platform.getJobManager().isIdle())
return IDLE_INTERVAL;
final long start = System.currentTimeMillis();
//don't garbage collect if we have collected within the specific interval
if ((start - lastGC) < nextGCInterval)
return nextGCInterval - (int) (start - lastGC);
System.gc();
System.runFinalization();
lastGC = start;
final int duration = (int) (System.currentTimeMillis() - start);
if (Policy.DEBUG_GC)
System.out.println("Explicit GC took: " + duration); //$NON-NLS-1$
if (duration > maxGC) {
if (Policy.DEBUG_GC)
System.out.println("Further explicit GCs disabled due to long GC"); //$NON-NLS-1$
shutdown();
return -1;
}
//if the gc took a long time, ensure the next gc doesn't happen for awhile
nextGCInterval = Math.max(minGCInterval, GC_DELAY_MULTIPLIER * duration);
if (Policy.DEBUG_GC)
System.out.println("Next GC to run in: " + nextGCInterval); //$NON-NLS-1$
return nextGCInterval;
}
/**
* Shuts down the idle helper, removing any installed listeners, etc.
*/
void shutdown() {
if (idleListener == null)
return;
Display display = configurer.getWorkbench().getDisplay();
if (display != null && !display.isDisposed()) {
display.removeFilter(SWT.KeyUp, idleListener);
display.removeFilter(SWT.MouseUp, idleListener);
}
}
}