Skip to content

Commit e16108b

Browse files
Add experimental GWT.unloadModule() function.
Enable via <set-property name="gwt.unloadEnabled" value="true"/> Invoke via window.__gwt_activeModules[moduleName].unloadModule(). Attempts to do the following: 1) Cleanup all outstanding non-primitive properties from $wnd 2) Remove all registered event listeners 3) detach all widgets from rootpanel 4) Cancel any continuously running timers, I/Os in progress, etc 5) Delete all code from the iframe scope (window != $wnd) 6) erase registration from __gwt_activeModules and null out nocache.js module function 7) Remove iframe hosting the module (if present) Even with all of this cleanup, Chrome's heap profiler still shows leaks on the simplest modules. This is a work in progress to support portal-like services that want to load/unload many GWT modules within a long running page. Currently, IE paths which use attachEvent() have not been patched. By default, support is disabled and there should only be a negligible impact on codesize. Review at http://gwt-code-reviews.appspot.com/1827804 Review by: [email protected] git-svn-id: http://google-web-toolkit.googlecode.com/svn/trunk@11513 8db76d5a-ed1c-0410-87a9-c151d255dfc7
1 parent 8f81f0a commit e16108b

24 files changed

+670
-112
lines changed

user/src/com/google/gwt/core/Core.gwt.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@
3535
<inherits name="com.google.gwt.core.XSLinker" />
3636
<inherits name="com.google.gwt.core.CrossSiteIframeLinker" />
3737

38+
<!-- When true, compiles in support for GWT.unloadModule(), otherwise it is a no-op. -->
39+
<define-property name="gwt.unloadEnabled" values="false, true"/>
40+
<set-property name="gwt.unloadEnabled" value="false"/>
41+
42+
<replace-with class="com.google.gwt.core.client.impl.UnloadSupportEnabled">
43+
<when-property-is name="gwt.unloadEnabled" value="true"/>
44+
<when-type-is class="com.google.gwt.core.client.impl.UnloadSupport"/>
45+
</replace-with>
46+
3847
<define-linker name="soycReport" class="com.google.gwt.core.linker.SoycReportLinker" />
3948
<define-linker name="symbolMaps" class="com.google.gwt.core.linker.SymbolMapsLinker" />
4049

user/src/com/google/gwt/core/client/GWT.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ public static <T> T create(Class<?> classLiteral) {
8585
return com.google.gwt.core.shared.GWT.<T>create(classLiteral);
8686
}
8787

88+
public static void exportUnloadModule() {
89+
Impl.exportUnloadModule();
90+
}
91+
8892
/**
8993
* Gets the URL prefix of the hosting page, useful for prepending to relative
9094
* paths of resources which may be relative to the host page. Typically, you
@@ -291,4 +295,15 @@ static void setBridge(GWTBridge bridge) {
291295
private static native String getVersion0() /*-{
292296
return $gwt_version;
293297
}-*/;
298+
299+
/**
300+
* If enabled via &lt;set-property name="gwt.unloadEnabled" value="true"/> invoking this method causes the module
301+
* to be removed from memory and all {@link com.google.gwt.core.client.impl.Disposable} instances to be
302+
* cleaned up. This method is not typically called by the GWT module itself, but exported so that another module
303+
* may call it.
304+
* @see com.google.gwt.core.client.GWT#exportUnloadModule()
305+
*/
306+
private static void unloadModule() {
307+
Impl.unloadModule();
308+
}
294309
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2012 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.google.gwt.core.client.impl;
17+
18+
/**
19+
* Disposable objects are registered to be called when GWT.unloadModule() is invoked allowing a GWT module to clean
20+
* up danging DOM references and other exported or leaking references.
21+
* @see Impl#scheduleDispose
22+
*/
23+
public interface Disposable {
24+
void dispose();
25+
}

user/src/com/google/gwt/core/client/impl/Impl.java

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
*/
2727
public final class Impl {
2828

29+
public static boolean moduleUnloaded = false;
30+
2931
private static final int WATCHDOG_ENTRY_DEPTH_CHECK_INTERVAL_MS = 2000;
3032

3133
/**
@@ -44,6 +46,25 @@ public final class Impl {
4446
*/
4547
private static int watchdogEntryDepthTimerId = -1;
4648

49+
private static UnloadSupport unloadSupport = GWT.isScript() ?
50+
(UnloadSupport) GWT.create(UnloadSupport.class) : new UnloadSupport();
51+
52+
static {
53+
exportUnloadModule();
54+
}
55+
56+
public static void clearInterval(int timerId) {
57+
unloadSupport.clearInterval(timerId);
58+
}
59+
60+
public static void clearTimeout(int timerId) {
61+
unloadSupport.clearTimeout(timerId);
62+
}
63+
64+
public static void dispose(Disposable d) {
65+
unloadSupport.dispose(d);
66+
}
67+
4768
/**
4869
* This method should be used whenever GWT code is entered from a JS context
4970
* and there is no GWT code in the same module on the call stack. Examples
@@ -58,7 +79,7 @@ public final class Impl {
5879
* The function passed to this method will be invoked via
5980
* <code>Function.apply()</code> with the current <code>this</code> value and
6081
* the invocation arguments passed to <code>$entry</code>.
61-
*
82+
*
6283
* @param jsFunction a JS function to invoke, which is typically a JSNI
6384
* reference to a static Java method
6485
* @return the value returned when <code>jsFunction</code> is invoked, or
@@ -79,6 +100,10 @@ public static native JavaScriptObject entry(JavaScriptObject jsFunction) /*-{
79100
};
80101
}-*/;
81102

103+
public static void exportUnloadModule() {
104+
unloadSupport.exportUnloadModule();
105+
}
106+
82107
/**
83108
* Gets an identity-based hash code on the passed-in Object by adding an
84109
* expando. This method should not be used with <code>null</code> or any
@@ -134,7 +159,7 @@ public static native String getModuleName() /*-{
134159
* Returns the obfuscated name of members in the compiled output. This is a
135160
* thin wrapper around JNameOf AST nodes and is therefore meaningless to
136161
* implement in Development Mode.
137-
*
162+
*
138163
* @param jsniIdent a string literal specifying a type, field, or method. Raw
139164
* type names may also be used to obtain the name of the type's seed
140165
* function.
@@ -193,6 +218,10 @@ public static boolean isEntryOnStack() {
193218
return entryDepth > 0;
194219
}
195220

221+
public static boolean isModuleUnloaded() {
222+
return moduleUnloaded;
223+
}
224+
196225
/**
197226
* Indicates if <code>$entry</code> is present on the stack more than once.
198227
*/
@@ -213,6 +242,25 @@ public static native JavaScriptObject registerEntry() /*-{
213242
}
214243
}-*/;
215244

245+
public static void scheduleDispose(Disposable d) {
246+
unloadSupport.scheduleDispose(d);
247+
}
248+
249+
public static int setInterval(JavaScriptObject func, int time) {
250+
return unloadSupport.setInterval(func, time);
251+
}
252+
253+
public static int setTimeout(JavaScriptObject func, int time) {
254+
return unloadSupport.setTimeout(func, time);
255+
}
256+
257+
public static void unloadModule() {
258+
if (unloadSupport.isUnloadSupported()) {
259+
moduleUnloaded = true;
260+
unloadSupport.disposeAll();
261+
}
262+
}
263+
216264
private static native Object apply(Object jsFunction, Object thisObj,
217265
Object args) /*-{
218266
if (@com.google.gwt.core.client.GWT::isScript()()) {
@@ -254,6 +302,10 @@ private static boolean enter() {
254302
*/
255303
private static Object entry0(Object jsFunction, Object thisObj,
256304
Object arguments) throws Throwable {
305+
// if module is unloaded, don't run anything
306+
if (unloadSupport.isUnloadSupported() && Impl.isModuleUnloaded()) {
307+
return null;
308+
}
257309
boolean initialEntry = enter();
258310

259311
try {
@@ -324,7 +376,7 @@ private static native Object undefined() /*-{
324376
}-*/;
325377

326378
private static native void watchdogEntryDepthCancel(int timerId) /*-{
327-
$wnd.clearTimeout(timerId);
379+
@com.google.gwt.core.client.impl.Impl::clearTimeout(I)(timerId);
328380
}-*/;
329381

330382
private static void watchdogEntryDepthRun() {
@@ -337,7 +389,7 @@ private static void watchdogEntryDepthRun() {
337389
}
338390

339391
private static native int watchdogEntryDepthSchedule() /*-{
340-
return $wnd.setTimeout(function() {
392+
return @com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)(function() {
341393
@com.google.gwt.core.client.impl.Impl::watchdogEntryDepthRun()();
342394
}, 10);
343395
}-*/;

user/src/com/google/gwt/core/client/impl/SchedulerImpl.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,15 +189,15 @@ private static JsArray<Task> runScheduledTasks(JsArray<Task> tasks,
189189

190190
private static native void scheduleFixedDelayImpl(RepeatingCommand cmd,
191191
int delayMs) /*-{
192-
$wnd.setTimeout(function() {
192+
@com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)(function() {
193193
// $entry takes care of uncaught exception handling
194194
var ret = $entry(@com.google.gwt.core.client.impl.SchedulerImpl::execute(Lcom/google/gwt/core/client/Scheduler$RepeatingCommand;))(cmd);
195195
if ([email protected]::isScript()()) {
196196
// Unwrap from Development Mode
197197
ret = ret == true;
198198
}
199199
if (ret) {
200-
$wnd.setTimeout(arguments.callee, delayMs);
200+
@com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)(arguments.callee, delayMs);
201201
}
202202
}, delayMs);
203203
}-*/;
@@ -213,10 +213,10 @@ private static native void scheduleFixedPeriodImpl(RepeatingCommand cmd,
213213
}
214214
if (!ret) {
215215
// Either canceled or threw an exception
216-
$wnd.clearInterval(arguments.callee.token);
216+
@com.google.gwt.core.client.impl.Impl::clearInterval(I)(arguments.callee.token);
217217
}
218218
};
219-
fn.token = $wnd.setInterval(fn, delayMs);
219+
fn.token = @com.google.gwt.core.client.impl.Impl::setInterval(Lcom/google/gwt/core/client/JavaScriptObject;I)(fn, delayMs);
220220
}-*/;
221221

222222
/**
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2012 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.google.gwt.core.client.impl;
17+
18+
import com.google.gwt.core.client.JavaScriptObject;
19+
20+
/**
21+
* Rebound class to enable/disable support for {@link com.google.gwt.core.client.GWT#unloadModule()}
22+
*/
23+
public class UnloadSupport {
24+
25+
static native void clearInterval0(int timerId) /*-{
26+
window.clearInterval(timerId);
27+
}-*/;
28+
29+
static native void clearTimeout0(int timerId) /*-{
30+
window.clearTimeout(timerId);
31+
}-*/;
32+
33+
static native int setInterval0(JavaScriptObject func, int time) /*-{
34+
var timerId = window.setInterval(function () {
35+
func();
36+
}, time);
37+
return timerId;
38+
}-*/;
39+
40+
static native int setTimeout0(JavaScriptObject func, int time, Disposable disposeable) /*-{
41+
var timerId = window.setTimeout(function () {
42+
func();
43+
if (disposeable != null) {
44+
@com.google.gwt.core.client.impl.Impl::dispose(Lcom/google/gwt/core/client/impl/Disposable;)(disposeable);
45+
}
46+
}, time);
47+
return timerId;
48+
}-*/;
49+
50+
public void exportUnloadModule() {
51+
}
52+
53+
/**
54+
* Return true if {@link com.google.gwt.core.client.GWT#unloadModule()} is enabled. Default is false.
55+
*/
56+
public boolean isUnloadSupported() {
57+
return false;
58+
}
59+
60+
void clearInterval(int timerId) {
61+
clearInterval0(timerId);
62+
}
63+
64+
void clearTimeout(int timerId) {
65+
clearTimeout0(timerId);
66+
}
67+
68+
void dispose(Disposable d) {
69+
if (d != null) {
70+
d.dispose();
71+
}
72+
}
73+
74+
void disposeAll() {
75+
}
76+
77+
void scheduleDispose(Disposable d) {
78+
}
79+
80+
81+
int setInterval(JavaScriptObject func, int time) {
82+
return setInterval0(func, time);
83+
}
84+
85+
int setTimeout(JavaScriptObject func, int time) {
86+
return setTimeout0(func, time, null);
87+
}
88+
}

0 commit comments

Comments
 (0)