]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.workbench/src/org/simantics/workbench/internal/IDEIdleHelper.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.workbench / src / org / simantics / workbench / internal / IDEIdleHelper.java
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 (file)
index 0000000..814820f
--- /dev/null
@@ -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)
+                       }                       
+               }
+       }
+}