X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.workbench%2Fsrc%2Forg%2Fsimantics%2Fworkbench%2Finternal%2FIDEIdleHelper.java;fp=bundles%2Forg.simantics.workbench%2Fsrc%2Forg%2Fsimantics%2Fworkbench%2Finternal%2FIDEIdleHelper.java;h=814820f4a1d20793391912c787a344cba2079526;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.workbench/src/org/simantics/workbench/internal/IDEIdleHelper.java b/bundles/org.simantics.workbench/src/org/simantics/workbench/internal/IDEIdleHelper.java new file mode 100644 index 000000000..814820f4a --- /dev/null +++ b/bundles/org.simantics.workbench/src/org/simantics/workbench/internal/IDEIdleHelper.java @@ -0,0 +1,219 @@ +package org.simantics.workbench.internal; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +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; +import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; +import org.eclipse.ui.internal.ide.Policy; + +/** + * 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; + + private Job gcJob; + + private Runnable handler; + + /** + * 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(); + } + + createGarbageCollectionJob(); + + //hook idle handler + final Display display = configurer.getWorkbench().getDisplay(); + handler = new Runnable() { + public void run() { + if (!display.isDisposed() && !configurer.getWorkbench().isClosing()) { + int nextInterval; + final long start = System.currentTimeMillis(); + //don't garbage collect if background jobs are running + if (!Job.getJobManager().isIdle()) { + nextInterval = IDLE_INTERVAL; + } else if ((start - lastGC) < nextGCInterval) { + //don't garbage collect if we have collected within the specific interval + nextInterval = nextGCInterval - (int) (start - lastGC); + } else { + gcJob.schedule(); + nextInterval = minGCInterval; + } + display.timerExec(nextInterval, this); + } + } + }; + idleListener = new Listener() { + public void handleEvent(Event event) { + display.timerExec(IDLE_INTERVAL, handler); + } + }; + display.addFilter(SWT.KeyUp, idleListener); + display.addFilter(SWT.MouseUp, idleListener); + } + + /** + * Creates the job that performs garbage collection + */ + private void createGarbageCollectionJob() { + gcJob = new Job(IDEWorkbenchMessages.IDEIdleHelper_backgroundGC) { + protected IStatus run(IProgressMonitor monitor) { + final Display display = configurer.getWorkbench().getDisplay(); + if (display != null && !display.isDisposed()) { + final long start = System.currentTimeMillis(); + 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(); + } else { + //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 Status.OK_STATUS; + } + }; + gcJob.setSystem(true); + } + + /** + * Shuts down the idle helper, removing any installed listeners, etc. + */ + void shutdown() { + if (idleListener == null) { + return; + } + final Display display = configurer.getWorkbench().getDisplay(); + if (display != null && !display.isDisposed()) { + try { + display.asyncExec(new Runnable() { + public void run() { + display.timerExec(-1, handler); + display.removeFilter(SWT.KeyUp, idleListener); + display.removeFilter(SWT.MouseUp, idleListener); + } + }); + } catch (SWTException ex) { + // ignore (display might be disposed) + } + } + } +}