From b1fd98a33e58213d51d114021843943fd822d353 Mon Sep 17 00:00:00 2001 From: Camille Oudot Date: Tue, 9 Oct 2018 11:02:04 +0200 Subject: [PATCH] Implement custom notification on weak reference garbage-collection --- src/main/java/com/eclipsesource/v8/V8.java | 19 ++++- .../java/com/eclipsesource/v8/V8Value.java | 24 +++++- .../v8/WeakReferenceHandler.java | 5 ++ .../v8/V8WeakReferenceHandlerTest.java | 79 +++++++++++++++++++ 4 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/eclipsesource/v8/WeakReferenceHandler.java create mode 100644 src/test/java/com/eclipsesource/v8/V8WeakReferenceHandlerTest.java diff --git a/src/main/java/com/eclipsesource/v8/V8.java b/src/main/java/com/eclipsesource/v8/V8.java index 354012c82..2fa961499 100644 --- a/src/main/java/com/eclipsesource/v8/V8.java +++ b/src/main/java/com/eclipsesource/v8/V8.java @@ -43,7 +43,7 @@ public class V8 extends V8Object { private volatile static int runtimeCounter = 0; private static String v8Flags = null; private static boolean initialized = false; - protected Map v8WeakReferences = new HashMap(); + protected Map v8WeakReferences = new HashMap(); private Map data = null; private final V8Locker locker; @@ -70,6 +70,16 @@ private class MethodDescriptor { boolean includeReceiver; } + static class WeakRefEntry { + V8Value value; + WeakReferenceHandler handler; + + WeakRefEntry(V8Value value, WeakReferenceHandler handler) { + this.value = value; + this.handler = handler; + } + } + private synchronized static void load(final String tmpDirectory) { try { LibraryLoader.loadLibrary(tmpDirectory); @@ -843,8 +853,13 @@ protected void disposeMethodID(final long methodID) { } protected void weakReferenceReleased(final long objectID) { - V8Value v8Value = v8WeakReferences.get(objectID); + WeakRefEntry entry = v8WeakReferences.get(objectID); + V8Value v8Value = entry.value; + WeakReferenceHandler handler = entry.handler; if (v8Value != null) { + if (handler != null) { + handler.v8WeakReferenceCollected(v8Value); + } v8WeakReferences.remove(objectID); try { v8Value.close(); diff --git a/src/main/java/com/eclipsesource/v8/V8Value.java b/src/main/java/com/eclipsesource/v8/V8Value.java index d314bcd9d..f7e2e7e49 100644 --- a/src/main/java/com/eclipsesource/v8/V8Value.java +++ b/src/main/java/com/eclipsesource/v8/V8Value.java @@ -212,16 +212,36 @@ public V8Value twin() { * the object will be closed, so this should only be used if there is no * other way to track object usage. * + * @param handler A handler that will be notified when the value weakly- + * referenced by this value is reclaimed by the V8 + * garbage collector. * @return The receiver. */ - public V8Value setWeak() { + public V8Value setWeak(WeakReferenceHandler handler) { v8.checkThread(); v8.checkReleased(); - v8.v8WeakReferences.put(getHandle(), this); + v8.v8WeakReferences.put(getHandle(), new V8.WeakRefEntry(this, handler)); v8.setWeak(v8.getV8RuntimePtr(), getHandle()); return this; } + /** + * Sets the V8Value as weak reference. A weak reference will eventually + * be closed when no more references exist to this object. Once setWeak + * is called, you should check if {@link V8Value#isReleased()} is true + * before invoking any methods on this object. + * + * If any other references exist to this object, the object will not be + * reclaimed. Even if no reference exist, V8 does not give any guarantee + * the object will be closed, so this should only be used if there is no + * other way to track object usage. + * + * @return The receiver. + */ + public V8Value setWeak() { + return setWeak(null); + } + /** * If {@link V8Value#setWeak()} has been called on this Object, this method * will return true. Otherwise it will return false. diff --git a/src/main/java/com/eclipsesource/v8/WeakReferenceHandler.java b/src/main/java/com/eclipsesource/v8/WeakReferenceHandler.java new file mode 100644 index 000000000..9b1730b74 --- /dev/null +++ b/src/main/java/com/eclipsesource/v8/WeakReferenceHandler.java @@ -0,0 +1,5 @@ +package com.eclipsesource.v8; + +public interface WeakReferenceHandler { + void v8WeakReferenceCollected(V8Value weakRef); +} diff --git a/src/test/java/com/eclipsesource/v8/V8WeakReferenceHandlerTest.java b/src/test/java/com/eclipsesource/v8/V8WeakReferenceHandlerTest.java new file mode 100644 index 000000000..08b364c4f --- /dev/null +++ b/src/test/java/com/eclipsesource/v8/V8WeakReferenceHandlerTest.java @@ -0,0 +1,79 @@ +package com.eclipsesource.v8; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class V8WeakReferenceHandlerTest { + private V8 v8; + + @Before + public void setup() { + V8.setFlags("--expose_gc"); + v8 = V8.createV8Runtime(); + } + + @After + public void tearDown() { + try { + v8.close(); + if (V8.getActiveRuntimes() != 0) { + throw new IllegalStateException("V8Runtimes not properly released"); + } + } catch (IllegalStateException e) { + System.out.println(e.getMessage()); + } + } + + static class WeakReferenceObserver implements WeakReferenceHandler { + boolean collected = false; + V8Value ref; + + WeakReferenceObserver(V8Value ref) { + this.ref = ref; + } + + @Override + public void v8WeakReferenceCollected(V8Value weakRef) { + if (weakRef == ref) { + collected = true; + } else { + throw new IllegalStateException("v8WeakReferenceCollected() called with an unexpected reference"); + } + } + } + + @Test + public void testReferenceCollectedCalled() { + final V8Object o = v8.executeObjectScript( + "let a = [];" + + "a.push({foo: \"bar\"});"+ + "a[0];"); + + WeakReferenceObserver observer = new WeakReferenceObserver(o); + + o.setWeak(observer); + + Assert.assertFalse(observer.collected); + v8.executeVoidScript("a = null"); + v8.executeVoidScript("gc()"); + Assert.assertTrue(observer.collected); + } + + @Test + public void testReferenceCollectedNotCalled() { + final V8Object o = v8.executeObjectScript( + "let a = [];" + + "a.push({foo: \"bar\"});"+ + "a[0];"); + + WeakReferenceObserver observer = new WeakReferenceObserver(o); + + o.setWeak(observer); + + Assert.assertFalse(observer.collected); + v8.executeVoidScript("gc()"); + Assert.assertFalse(observer.collected); + } +}