]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.workbench/src/org/simantics/workbench/internal/IDEIdleHelper.java
Improve startup time for fresh or rollback'd session in index writing
[simantics/platform.git] / bundles / org.simantics.workbench / src / org / simantics / workbench / internal / IDEIdleHelper.java
1 package org.simantics.workbench.internal;
2
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;
16
17 /**
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
21  * is as follows:
22  * 
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
30  */
31 class IDEIdleHelper {
32
33         /**
34          * The default minimum time between garbage collections.
35          */
36         private static final int DEFAULT_GC_INTERVAL = 60000;
37
38         /**
39          * The default maximum duration for a garbage collection, beyond which
40          * the explicit gc mechanism is automatically disabled.
41          */
42         private static final int DEFAULT_GC_MAX = 8000;
43
44         /**
45          * The multiple of the last gc duration before we will consider doing
46          * another one.
47          */
48         private static final int GC_DELAY_MULTIPLIER = 60;
49
50         /**
51          * The time interval of no keyboard or mouse events after which the system 
52          * is considered idle.
53          */
54         private static final int IDLE_INTERVAL = 5000;
55
56         /**
57          * The name of the boolean system property that specifies whether explicit
58          * garbage collection is enabled.
59          */
60         private static final String PROP_GC = "ide.gc"; //$NON-NLS-1$
61
62         /**
63          * The name of the integer system property that specifies the minimum time 
64          * interval in milliseconds between garbage collections.
65          */
66         private static final String PROP_GC_INTERVAL = "ide.gc.interval"; //$NON-NLS-1$
67
68         /**
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.
72          */
73         private static final String PROP_GC_MAX = "ide.gc.max"; //$NON-NLS-1$
74
75         protected IWorkbenchConfigurer configurer;
76
77         private Listener idleListener;
78
79         /**
80          * The last time we garbage collected.
81          */
82         private long lastGC = System.currentTimeMillis();
83
84         /**
85          * The maximum gc duration. If this value is exceeded, the
86          * entire explicit gc mechanism is disabled.
87          */
88         private int maxGC = DEFAULT_GC_MAX;
89         /**
90          * The minimum time interval until the next garbage collection
91          */
92         private int minGCInterval = DEFAULT_GC_INTERVAL;
93
94         /**
95          * The time interval until the next garbage collection
96          */
97         private int nextGCInterval = DEFAULT_GC_INTERVAL;
98         
99         private Job gcJob;
100
101         private Runnable handler;       
102         
103         /**
104          * Creates and initializes the idle handler
105          * @param aConfigurer The workbench configurer.
106          */
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) {
111                         return;
112                 }
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())) {
116                         return;
117                 }
118                 //init gc interval
119                 Integer prop = Integer.getInteger(PROP_GC_INTERVAL);
120                 if (prop != null && prop.intValue() >= 0) {
121                         minGCInterval = nextGCInterval = prop.intValue();
122                 }
123
124                 //init max gc interval
125                 prop = Integer.getInteger(PROP_GC_MAX);
126                 if (prop != null) {
127                         maxGC = prop.intValue();
128                 }
129                 
130                 createGarbageCollectionJob();
131
132                 //hook idle handler
133                 final Display display = configurer.getWorkbench().getDisplay();
134                 handler = new Runnable() {
135                                         public void run() {
136                                                 if (!display.isDisposed() && !configurer.getWorkbench().isClosing()) {
137                                                         int nextInterval;
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);
145                                                         } else {
146                                                                 gcJob.schedule();
147                                                                 nextInterval = minGCInterval;
148                                                         }
149                                                         display.timerExec(nextInterval, this);
150                                                 }
151                                         }
152                                 };
153                 idleListener = new Listener() {
154                         public void handleEvent(Event event) {
155                                 display.timerExec(IDLE_INTERVAL, handler);
156                         }
157                 };
158                 display.addFilter(SWT.KeyUp, idleListener);
159                 display.addFilter(SWT.MouseUp, idleListener);
160         }
161
162         /**
163          * Creates the job that performs garbage collection
164          */
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();
171                                         System.gc();
172                                         System.runFinalization();
173                                         lastGC = start;
174                                         final int duration = (int) (System.currentTimeMillis() - start);
175                                         if (Policy.DEBUG_GC) {
176                                                 System.out.println("Explicit GC took: " + duration); //$NON-NLS-1$
177                                         }
178                                         if (duration > maxGC) {
179                                                 if (Policy.DEBUG_GC) {
180                                                         System.out.println("Further explicit GCs disabled due to long GC"); //$NON-NLS-1$
181                                                 }
182                                                 shutdown();
183                                         } else {
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$
188                                                 }
189                                         }
190                                 }
191                                 return Status.OK_STATUS;
192                         }
193                 };
194                 gcJob.setSystem(true);
195         }
196
197         /**
198          * Shuts down the idle helper, removing any installed listeners, etc.
199          */
200         void shutdown() {
201                 if (idleListener == null) {
202                         return;
203                 }
204                 final Display display = configurer.getWorkbench().getDisplay();
205                 if (display != null && !display.isDisposed()) {
206                         try {
207                                 display.asyncExec(new Runnable() {
208                                         public void run() {
209                                                 display.timerExec(-1, handler);
210                                                 display.removeFilter(SWT.KeyUp, idleListener);
211                                                 display.removeFilter(SWT.MouseUp, idleListener);
212                                         }
213                                 });
214                         } catch (SWTException ex) {
215                                 // ignore (display might be disposed)
216                         }                       
217                 }
218         }
219 }