Giving API users better control over GC aspects of their objects gc
authorJaroslav Tulach <jtulach@netbeans.org>
Fri, 12 Dec 2014 11:22:40 +0100
branchgc
changeset 9002ee22312e414
parent 899 01c8dda1f888
child 902 5c65f811cf55
Giving API users better control over GC aspects of their objects
boot/src/main/java/net/java/html/js/JavaScriptBody.java
boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java
boot/src/main/java/org/netbeans/html/boot/spi/Fn.java
boot/src/test/java/org/netbeans/html/boot/impl/JsMethods.java
boot/src/test/java/org/netbeans/html/boot/impl/KeepAliveTest.java
json-tck/src/main/java/net/java/html/js/tests/Bodies.java
json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java
ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java
     1.1 --- a/boot/src/main/java/net/java/html/js/JavaScriptBody.java	Tue Dec 09 21:32:09 2014 +0100
     1.2 +++ b/boot/src/main/java/net/java/html/js/JavaScriptBody.java	Fri Dec 12 11:22:40 2014 +0100
     1.3 @@ -120,4 +120,33 @@
     1.4       *   of the JavaScript snippet
     1.5       */
     1.6      public boolean wait4js() default true;
     1.7 +    
     1.8 +    /** Controls garbage collection behavior of method parameters.
     1.9 +     * In general JavaScript garbage
    1.10 +     * collection system makes it close to impossible to find out whether
    1.11 +     * an object is supposed to be still used or not. Some systems have
    1.12 +     * an external hooks to find that out (like <em>JavaFX</em> <code>WebView</code>),
    1.13 +     * in some systems this information is not important (like the 
    1.14 +     * <a href="http://bck2brwsr.apidesign.org">Bck2Brwsr</a> VM running
    1.15 +     * all in JavaScript), but other execution systems just can't find that
    1.16 +     * out. To prevent memory leaks on such systems and help them manage
    1.17 +     * memory more effectively, those who define JavaScript interfacing 
    1.18 +     * methods may indicate whether the non-primitive parameters passed
    1.19 +     * in should be hold only for the time of method invocation or 
    1.20 +     * for the whole application lifetime.
    1.21 +     * <p>
    1.22 +     * The default value is <code>true</code> as that is compatible with
    1.23 +     * previous behavior and also prevents unwanted surprises when something
    1.24 +     * garbage collects pre-maturaly. Framework developers are however 
    1.25 +     * encouraged to use <code>keepAlive=false</code> as much as possible.
    1.26 +     * 
    1.27 +     * @return whether Java objects passed as parameters of the method
    1.28 +     *   should be made guaranteed to be available JavaScript
    1.29 +     *   even after the method invocation is over (e.g. prevent them to be
    1.30 +     *   garbage collected in Java until it is known they are not needed
    1.31 +     *   from JavaScript at all).
    1.32 +     * 
    1.33 +     * @since 1.1
    1.34 +     */
    1.35 +    public boolean keepAlive() default true;
    1.36  }
     2.1 --- a/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java	Tue Dec 09 21:32:09 2014 +0100
     2.2 +++ b/boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java	Fri Dec 12 11:22:40 2014 +0100
     2.3 @@ -296,6 +296,7 @@
     2.4                  // init Fn
     2.5                  super.visitInsn(Opcodes.POP);
     2.6                  super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
     2.7 +                super.visitInsn(fia.keepAlive ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
     2.8                  super.visitLdcInsn(body);
     2.9                  super.visitIntInsn(Opcodes.SIPUSH, args.size());
    2.10                  super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
    2.11 @@ -311,7 +312,7 @@
    2.12                  }
    2.13                  super.visitMethodInsn(Opcodes.INVOKESTATIC,
    2.14                          "org/netbeans/html/boot/spi/Fn", "define",
    2.15 -                        "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;"
    2.16 +                        "(Ljava/lang/Class;ZLjava/lang/String;[Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;"
    2.17                  );
    2.18                  Label noPresenter = new Label();
    2.19                  super.visitInsn(Opcodes.DUP);
    2.20 @@ -526,6 +527,7 @@
    2.21                  String body;
    2.22                  boolean javacall = false;
    2.23                  boolean wait4js = true;
    2.24 +                boolean keepAlive = false;
    2.25  
    2.26                  public FindInAnno() {
    2.27                      super(Opcodes.ASM4);
    2.28 @@ -545,7 +547,11 @@
    2.29                          wait4js = (Boolean) value;
    2.30                          return;
    2.31                      }
    2.32 -                    assert name.equals("body");
    2.33 +                    if (name.equals("keepAlive")) { // NOI18N
    2.34 +                        keepAlive = (Boolean) value;
    2.35 +                        return;
    2.36 +                    }
    2.37 +                    assert name.equals("body"); // NOI18N
    2.38                      body = (String) value;
    2.39                  }
    2.40  
     3.1 --- a/boot/src/main/java/org/netbeans/html/boot/spi/Fn.java	Tue Dec 09 21:32:09 2014 +0100
     3.2 +++ b/boot/src/main/java/org/netbeans/html/boot/spi/Fn.java	Fri Dec 12 11:22:40 2014 +0100
     3.3 @@ -115,8 +115,39 @@
     3.4       * @since 0.7
     3.5       */
     3.6      public static Fn define(Class<?> caller, String code, String... names) {
     3.7 +        return define(caller, false, code, names);
     3.8 +    }
     3.9 +
    3.10 +    /** Helper method to find current presenter and ask it to define new
    3.11 +     * function.
    3.12 +     * 
    3.13 +     * @param caller the class who wishes to define the function
    3.14 +     * @param keepParametersAlive whether Java parameters should survive in JavaScript
    3.15 +     *   after the method invocation is over
    3.16 +     * @param code the body of the function (can reference <code>this</code> and <code>names</code> variables)
    3.17 +     * @param names names of individual parameters
    3.18 +     * @return the function object that can be {@link Fn#invoke(java.lang.Object, java.lang.Object...) invoked}
    3.19 +     *    - can return <code>null</code> if there is {@link #activePresenter() no presenter}
    3.20 +     * @since 1.1
    3.21 +     */
    3.22 +    public static Fn define(Class<?> caller, boolean keepParametersAlive, String code, String... names) {
    3.23          final Presenter p = FnContext.currentPresenter(false);
    3.24 -        return p == null ? null : p.defineFn(code, names);
    3.25 +        if (p == null) {
    3.26 +            return null;
    3.27 +        }
    3.28 +        if (p instanceof KeepAlive) {
    3.29 +            boolean[] arr;
    3.30 +            if (!keepParametersAlive) {
    3.31 +                arr = new boolean[names.length];
    3.32 +                for (int i = 0; i < arr.length; i++) {
    3.33 +                    arr[i] = false;
    3.34 +                }
    3.35 +            } else {
    3.36 +                arr = null;
    3.37 +            }
    3.38 +            return ((KeepAlive)p).defineFn(code, names, arr);
    3.39 +        }
    3.40 +        return p.defineFn(code, names);
    3.41      }
    3.42      
    3.43      private static final Map<String,Set<Presenter>> LOADED = new HashMap<String, Set<Presenter>>();
    3.44 @@ -244,7 +275,7 @@
    3.45      protected final Presenter presenter() {
    3.46          return presenter;
    3.47      }
    3.48 -
    3.49 +    
    3.50      /** The representation of a <em>presenter</em> - usually a browser window.
    3.51       * Should be provided by a library included in the application and registered
    3.52       * in <code>META-INF/services</code>, for example with
    3.53 @@ -329,4 +360,29 @@
    3.54           */
    3.55          public Object toJava(Object js);
    3.56      }
    3.57 +
    3.58 +    /** Additional interface to {@link Presenter} to control more precisely
    3.59 +     * garbage collection behavior of individual parameters. See 
    3.60 +     * {@link JavaScriptBody#keepAlive()} attribute for description of the
    3.61 +     * actual behavior of the interface.
    3.62 +     * 
    3.63 +     * @since 1.1
    3.64 +     */
    3.65 +    public interface KeepAlive {
    3.66 +        /** Creates new function with given parameter names and provided body.
    3.67 +         * 
    3.68 +         * @param code the body of the function. Can refer to variables named
    3.69 +         *   as <code>names</code>
    3.70 +         * @param names names of parameters of the function - these will be 
    3.71 +         *   available when the <code>code</code> body executes
    3.72 +         * @param keepAlive array of booleans describing for each parameter
    3.73 +         *   whether it should be kept alive or not. Length of the array
    3.74 +         *   must be the same as length of <code>names</code> array. The
    3.75 +         *   array may be <code>null</code> to signal that all parameters
    3.76 +         *   should be <em>kept alive</em>.
    3.77 +         * 
    3.78 +         * @return function that can be later invoked
    3.79 +         */
    3.80 +        public Fn defineFn(String code, String[] names, boolean[] keepAlive);
    3.81 +    }
    3.82  }
     4.1 --- a/boot/src/test/java/org/netbeans/html/boot/impl/JsMethods.java	Tue Dec 09 21:32:09 2014 +0100
     4.2 +++ b/boot/src/test/java/org/netbeans/html/boot/impl/JsMethods.java	Fri Dec 12 11:22:40 2014 +0100
     4.3 @@ -134,4 +134,7 @@
     4.4      public static int plusOrMul(int x, int y) {
     4.5          return x * y;
     4.6      }
     4.7 +    
     4.8 +    @JavaScriptBody(args = { "x" }, keepAlive = false, body = "throw 'Do not call me!'")
     4.9 +    public static native boolean checkAllowGC(Object x);
    4.10  }
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/boot/src/test/java/org/netbeans/html/boot/impl/KeepAliveTest.java	Fri Dec 12 11:22:40 2014 +0100
     5.3 @@ -0,0 +1,114 @@
     5.4 +/**
     5.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     5.6 + *
     5.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     5.8 + *
     5.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    5.10 + * Other names may be trademarks of their respective owners.
    5.11 + *
    5.12 + * The contents of this file are subject to the terms of either the GNU
    5.13 + * General Public License Version 2 only ("GPL") or the Common
    5.14 + * Development and Distribution License("CDDL") (collectively, the
    5.15 + * "License"). You may not use this file except in compliance with the
    5.16 + * License. You can obtain a copy of the License at
    5.17 + * http://www.netbeans.org/cddl-gplv2.html
    5.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    5.19 + * specific language governing permissions and limitations under the
    5.20 + * License.  When distributing the software, include this License Header
    5.21 + * Notice in each file and include the License file at
    5.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    5.23 + * particular file as subject to the "Classpath" exception as provided
    5.24 + * by Oracle in the GPL Version 2 section of the License file that
    5.25 + * accompanied this code. If applicable, add the following below the
    5.26 + * License Header, with the fields enclosed by brackets [] replaced by
    5.27 + * your own identifying information:
    5.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    5.29 + *
    5.30 + * Contributor(s):
    5.31 + *
    5.32 + * The Original Software is NetBeans. The Initial Developer of the Original
    5.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    5.34 + *
    5.35 + * If you wish your version of this file to be governed by only the CDDL
    5.36 + * or only the GPL Version 2, indicate your decision by adding
    5.37 + * "[Contributor] elects to include this software in this distribution
    5.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    5.39 + * single choice of license, a recipient has the option to distribute
    5.40 + * your version of this file under either the CDDL, the GPL Version 2 or
    5.41 + * to extend the choice of license to its licensees as provided above.
    5.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    5.43 + * Version 2 license, then the option applies only if the new code is
    5.44 + * made subject to such option by the copyright holder.
    5.45 + */
    5.46 +package org.netbeans.html.boot.impl;
    5.47 +
    5.48 +import java.io.Closeable;
    5.49 +import java.io.Reader;
    5.50 +import java.net.URL;
    5.51 +import java.util.Collection;
    5.52 +import org.netbeans.html.boot.spi.Fn;
    5.53 +import static org.testng.Assert.assertFalse;
    5.54 +import static org.testng.Assert.assertTrue;
    5.55 +import org.testng.annotations.BeforeMethod;
    5.56 +import org.testng.annotations.Test;
    5.57 +
    5.58 +public class KeepAliveTest implements Fn.Presenter, Fn.KeepAlive, FindResources {
    5.59 +    private Class<?> jsMethods;
    5.60 +    @Test public void keepAliveIsSetToFalse() throws Exception {
    5.61 +        Closeable c = Fn.activate(this);
    5.62 +        boolean ret = (Boolean)jsMethods.getMethod("checkAllowGC", Object.class).invoke(null, this);
    5.63 +        c.close();
    5.64 +        assertFalse(ret, "keepAlive returns false when the presenter is invoked");
    5.65 +    }    
    5.66 +
    5.67 +    @Test public void keepAliveIsPropagated() throws Exception {
    5.68 +        Closeable c = Fn.activate(this);
    5.69 +        boolean ret = (Boolean)jsMethods.getMethod("truth").invoke(null);
    5.70 +        c.close();
    5.71 +        assertTrue(ret, "keepAlive returns true when the presenter is invoked");
    5.72 +    }    
    5.73 +
    5.74 +    @BeforeMethod
    5.75 +    public void initClass() throws ClassNotFoundException {
    5.76 +        ClassLoader loader = FnUtils.newLoader(this, this, KeepAliveTest.class.getClassLoader().getParent());
    5.77 +        jsMethods = loader.loadClass(JsMethods.class.getName());
    5.78 +    }
    5.79 +
    5.80 +    @Override
    5.81 +    public Fn defineFn(String code, String[] names, final boolean[] keepAlive) {
    5.82 +        return new Fn(this) {
    5.83 +            @Override
    5.84 +            public Object invoke(Object thiz, Object... args) throws Exception {
    5.85 +                boolean res = true;
    5.86 +                if (keepAlive != null) {
    5.87 +                    for (int i = 0; i < keepAlive.length; i++) {
    5.88 +                        res &= keepAlive[i];
    5.89 +                    }
    5.90 +                }
    5.91 +                return res;
    5.92 +            }
    5.93 +        };
    5.94 +    }
    5.95 +    
    5.96 +    @Override
    5.97 +    public Fn defineFn(String code, String... names) {
    5.98 +        throw new UnsupportedOperationException("Never called!");
    5.99 +    }
   5.100 +
   5.101 +    @Override
   5.102 +    public void displayPage(URL page, Runnable onPageLoad) {
   5.103 +        throw new UnsupportedOperationException();
   5.104 +    }
   5.105 +
   5.106 +    @Override
   5.107 +    public void loadScript(Reader code) throws Exception {
   5.108 +    }
   5.109 +
   5.110 +    @Override
   5.111 +    public void findResources(String path, Collection<? super URL> results, boolean oneIsEnough) {
   5.112 +        URL u = ClassLoader.getSystemClassLoader().getResource(path);
   5.113 +        if (u != null) {
   5.114 +            results.add(u);
   5.115 +        }
   5.116 +    }
   5.117 +}
     6.1 --- a/json-tck/src/main/java/net/java/html/js/tests/Bodies.java	Tue Dec 09 21:32:09 2014 +0100
     6.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/Bodies.java	Fri Dec 12 11:22:40 2014 +0100
     6.3 @@ -61,7 +61,7 @@
     6.4      @JavaScriptBody(args = {"r"}, javacall = true, body = "r.@java.lang.Runnable::run()();")
     6.5      static native void callback(Runnable r);
     6.6  
     6.7 -    @JavaScriptBody(args = {"r"}, wait4js = false, javacall = true, body = "r.@java.lang.Runnable::run()();")
     6.8 +    @JavaScriptBody(args = {"r"}, wait4js = false, keepAlive = false, javacall = true, body = "r.@java.lang.Runnable::run()();")
     6.9      static native void asyncCallback(Runnable r);
    6.10      
    6.11      @JavaScriptBody(args = {"c"}, javacall = true, body = "return c.@java.util.concurrent.Callable::call()();")
    6.12 @@ -88,10 +88,10 @@
    6.13      @JavaScriptBody(args = "o", body = "return o.x;")
    6.14      public static native Object readX(Object o);
    6.15      
    6.16 -    @JavaScriptBody(args = { "o", "x" }, body = "o.x = x;")
    6.17 +    @JavaScriptBody(args = { "o", "x" }, keepAlive = false, body = "o.x = x;")
    6.18      public static native Object setX(Object o, Object x);
    6.19  
    6.20 -    @JavaScriptBody(args = { "c" }, javacall = true, body = 
    6.21 +    @JavaScriptBody(args = { "c" }, keepAlive = false, javacall = true, body = 
    6.22          "return c.@net.java.html.js.tests.Sum::sum(II)(40, 2);"
    6.23      )
    6.24      public static native int sumIndirect(Sum c);
     7.1 --- a/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java	Tue Dec 09 21:32:09 2014 +0100
     7.2 +++ b/json-tck/src/main/java/net/java/html/js/tests/GCBodyTest.java	Fri Dec 12 11:22:40 2014 +0100
     7.3 @@ -52,8 +52,9 @@
     7.4   */
     7.5  public class GCBodyTest {
     7.6      Reference<?> ref;
     7.7 +    int[] arr;
     7.8      
     7.9 -    @KOTest public void callbackWithParameters() throws InterruptedException {
    7.10 +    @KOTest public void callbackInterfaceCanDisappear() throws InterruptedException {
    7.11          if (ref != null) {
    7.12              assertGC(ref, "Can disappear!");
    7.13              return;
    7.14 @@ -66,24 +67,60 @@
    7.15          assertGC(ref, "Can disappear!");
    7.16      }
    7.17      
    7.18 +    private Object assignInst() {
    7.19 +        Object obj = Bodies.instance(0);
    7.20 +        Object s = new EmptyInstance();
    7.21 +        Bodies.setX(obj, s);
    7.22 +        assert s == Bodies.readX(obj);
    7.23 +        ref = new WeakReference<Object>(s);
    7.24 +        return obj;
    7.25 +}
    7.26 +    
    7.27      @KOTest public void holdObjectAndReleaseObject() throws InterruptedException {
    7.28          if (ref != null) {
    7.29              assertGC(ref, "Can disappear!");
    7.30              return;
    7.31          }
    7.32 -        Sum s = new Sum();
    7.33 -        Object obj = Bodies.instance(0);
    7.34 -        Bodies.setX(obj, s);
    7.35          
    7.36 -        ref = new WeakReference<Object>(s);
    7.37 -        s = null;
    7.38 -        assertNotGC(ref, "Cannot disappear!");
    7.39 +        Object obj = assignInst();
    7.40 +        assert ref != null;
    7.41          
    7.42          Bodies.setX(obj, null);
    7.43          obj = null;
    7.44          
    7.45          assertGC(ref, "Can disappear!");
    7.46      }
    7.47 +
    7.48 +    private static Reference<?> sendRunnable(final int[] arr) {
    7.49 +        Runnable r = new Runnable() {
    7.50 +            @Override
    7.51 +            public void run() {
    7.52 +                arr[0]++;
    7.53 +            }
    7.54 +        };
    7.55 +        Bodies.asyncCallback(r);
    7.56 +        return new WeakReference<Object>(r);
    7.57 +    }
    7.58 +    
    7.59 +    private static class EmptyInstance {
    7.60 +    }
    7.61 +    
    7.62 +    @KOTest public void parametersNeedToRemainInAsyncMode() throws InterruptedException {
    7.63 +        if (ref != null) {
    7.64 +            if (arr[0] != 1) {
    7.65 +                throw new InterruptedException();
    7.66 +            }
    7.67 +            assertGC(ref, "Now the runnable can disappear");
    7.68 +            return;
    7.69 +        }
    7.70 +        arr = new int[] { 0 };
    7.71 +        ref = sendRunnable(arr);
    7.72 +        if (arr[0] == 1) {
    7.73 +            return;
    7.74 +        }
    7.75 +        assertNotGC(ref, false, "The runnable should not be GCed");
    7.76 +        throw new InterruptedException();
    7.77 +    }
    7.78      
    7.79      private static void assertGC(Reference<?> ref, String msg) throws InterruptedException {
    7.80          for (int i = 0; i < 100; i++) {
    7.81 @@ -109,12 +146,14 @@
    7.82          return ref.get() == null;
    7.83      }
    7.84      
    7.85 -    private static void assertNotGC(Reference<?> ref, String msg) throws InterruptedException {
    7.86 +    private static void assertNotGC(Reference<?> ref, boolean clearJS, String msg) throws InterruptedException {
    7.87          for (int i = 0; i < 10; i++) {
    7.88              if (ref.get() == null) {
    7.89                  throw new IllegalStateException(msg);
    7.90              }
    7.91 -            int size = Bodies.gc(Math.pow(2.0, i));
    7.92 +            if (clearJS) {
    7.93 +                Bodies.gc(Math.pow(2.0, i));
    7.94 +            }
    7.95              try {
    7.96                  System.gc();
    7.97                  System.runFinalization();
     8.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java	Tue Dec 09 21:32:09 2014 +0100
     8.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/Knockout.java	Fri Dec 12 11:22:40 2014 +0100
     8.3 @@ -147,6 +147,7 @@
     8.4      
     8.5      @JavaScriptBody(
     8.6          javacall = true,
     8.7 +        keepAlive = false,
     8.8          wait4js = false,
     8.9          args = { "thiz", "ret", "propNames", "propReadOnly", "propValues", "funcNames" },
    8.10          body =