From 5266a4b75d89c8756499162ef29316ef46e3c8e7 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 18 Nov 2024 08:35:47 +0100 Subject: [PATCH 01/50] Using Ref.new allow_gc=True to implement in-memory caches --- .../Base/0.0.0-dev/src/Runtime/Ref.enso | 18 ++- .../org/enso/interpreter/runtime/RefTest.java | 124 ++++++++++++++++++ .../builtin/special/NewRefNode.java | 2 +- .../enso/interpreter/runtime/EnsoContext.java | 9 ++ .../runtime/ReferencesManager.java | 58 ++++++++ .../enso/interpreter/runtime/data/Ref.java | 39 +++++- .../Base/0.0.0-dev/src/Runtime/Ref.enso | 4 +- 7 files changed, 244 insertions(+), 10 deletions(-) create mode 100644 engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/runtime/ReferencesManager.java diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso index 8e8edcde1255..ef266204ae43 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso @@ -1,4 +1,5 @@ import project.Any.Any +import project.Data.Boolean.Boolean ## PRIVATE ADVANCED @@ -8,16 +9,27 @@ type Ref ## PRIVATE ADVANCED Creates a new reference containing the provided value. + If `allow_gc` is set to `True` than the management + of the reference is controlled by the runtime which + may _set the reference to `Nothing` any time_. The + definition of _"any time"_ is intentionally vague, but + the known implementations reset the value when running + out of memory or when a user asks for _"clean re-execution"_ + of a project. Arguments: - value: The value to be contained in the ref. + - allow_gc: Is the value eligible for being cleared by the system at any time? > Example Creating a new reference containing the value 7. - Ref.new 7 - new : Any -> Ref - new value = @Builtin_Method "Ref.new" + > Example + Cache a value, but allow the system to reclaim it. + Ref.new huge_data_downloaded_from_internet True + new value:Any allow_gc:Boolean=Boolean.False -> Ref = Ref.new_impl value (if allow_gc then 2 else 0) + + private new_impl value typ = @Builtin_Method "Ref.new" ## GROUP Metadata ICON metadata diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java new file mode 100644 index 000000000000..96c566b52dfd --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java @@ -0,0 +1,124 @@ +package org.enso.interpreter.runtime; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import org.enso.common.MethodNames; +import org.enso.test.utils.ContextUtils; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class RefTest { + private static Context ctx; + private static EnsoContext ensoCtx; + private static Value newRef; + private static Value createRef; + private static Value getRef; + + @BeforeClass + public static void initCtx() throws Exception { + ctx = ContextUtils.createDefaultContext(); + ensoCtx = ContextUtils.leakContext(ctx); + var code = + """ + import Standard.Base.Runtime.Ref.Ref + + new_ref obj = + Ref.new obj + + create_ref obj allow_gc = + Ref.new obj allow_gc + + get_ref ref = ref.get + """; + var src = Source.newBuilder("enso", code, "gc.enso").build(); + var gcEnso = ctx.eval(src); + newRef = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "new_ref"); + createRef = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create_ref"); + getRef = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "get_ref"); + } + + @AfterClass + public static void closeCtx() throws Exception { + ctx.close(); + ctx = null; + } + + @Test + public void regularReference() throws Exception { + var obj = new Object(); + var ref = newRef.execute(obj); + + assertFalse("Value returned", ref.isNull()); + assertEquals("Standard.Base.Runtime.Ref.Ref", ref.getMetaObject().getMetaQualifiedName()); + + var weakRef = new WeakReference<>(obj); + obj = null; + + assertEquals("We get the object", weakRef.get(), getRef.execute(ref).asHostObject()); + + assertGC("Weak wasn't released", false, weakRef); + assertFalse("Value was not GCed", getRef.execute(ref).isNull()); + assertEquals("We get the object", weakRef.get(), getRef.execute(ref).asHostObject()); + + ensoCtx.getReferencesManager().releaseAll(); + assertEquals( + "releaseAll has no effect on regular reference", + weakRef.get(), + getRef.execute(ref).asHostObject()); + } + + @Test + public void garbageCollectableReference() throws Exception { + var obj = new Object(); + var ref = createRef.execute(obj, true); + + assertFalse("Value returned", ref.isNull()); + assertEquals("Standard.Base.Runtime.Ref.Ref", ref.getMetaObject().getMetaQualifiedName()); + assertEquals("We get the object", obj, getRef.execute(ref).asHostObject()); + + var weakRef = new WeakReference<>(obj); + obj = null; + assertGC("Weak reference can be GCed", true, weakRef); + + assertTrue("Value was GCed", getRef.execute(ref).isNull()); + } + + @Test + public void explicitlyReclaimableReference() throws Exception { + var obj = new Object(); + var ref = createRef.execute(obj, true); + + assertFalse("Value returned", ref.isNull()); + assertEquals("Standard.Base.Runtime.Ref.Ref", ref.getMetaObject().getMetaQualifiedName()); + assertEquals("We get the object", obj, getRef.execute(ref).asHostObject()); + + ensoCtx.getReferencesManager().releaseAll(); + + assertTrue("Value was GCed", getRef.execute(ref).isNull()); + } + + private static void assertGC(String msg, boolean expectGC, Reference ref) { + for (var i = 1; i < Integer.MAX_VALUE / 2; i *= 2) { + if (ref.get() == null) { + break; + } + System.gc(); + } + var obj = ref.get(); + if (expectGC) { + assertNull(msg + " ref still alive", obj); + } else { + assertNotNull(msg + " ref has been cleaned", obj); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/NewRefNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/NewRefNode.java index ca6898cc793b..235d131a1cac 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/NewRefNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/NewRefNode.java @@ -7,6 +7,6 @@ @BuiltinMethod(type = "Special", name = "") public class NewRefNode extends Node { public Ref execute() { - return new Ref(null); + return new Ref(null, 0); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java index 531a116cc317..58bceb834447 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java @@ -96,6 +96,7 @@ public final class EnsoContext { private final ThreadManager threadManager; private final ThreadExecutors threadExecutors; private final ResourceManager resourceManager; + private final ReferencesManager referencesManager; private final boolean isInlineCachingDisabled; private final boolean isIrCachingDisabled; private final boolean shouldWaitForPendingSerializationJobs; @@ -139,6 +140,7 @@ public EnsoContext( this.threadManager = new ThreadManager(environment); this.threadExecutors = new ThreadExecutors(this); this.resourceManager = new ResourceManager(this); + this.referencesManager = new ReferencesManager(this); this.isInlineCachingDisabled = getOption(RuntimeOptions.DISABLE_INLINE_CACHES_KEY); var isParallelismEnabled = getOption(RuntimeOptions.ENABLE_AUTO_PARALLELISM_KEY); this.isIrCachingDisabled = @@ -801,6 +803,13 @@ public ResourceManager getResourceManager() { return resourceManager; } + /** + * @return the references manager for this context + */ + public ReferencesManager getReferencesManager() { + return referencesManager; + } + /** * @return whether inline caches should be disabled for this context. */ diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ReferencesManager.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ReferencesManager.java new file mode 100644 index 000000000000..80f003dcb51d --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ReferencesManager.java @@ -0,0 +1,58 @@ +package org.enso.interpreter.runtime; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** Tracks soft and weak references and allow their cleanup. */ +public final class ReferencesManager { + private final EnsoContext ctx; + private final Collection refs = new ConcurrentLinkedQueue<>(); + private final ReferenceQueue queue = new ReferenceQueue<>(); + + ReferencesManager(EnsoContext ctx) { + this.ctx = ctx; + } + + /** + * Creates new reference to provided object and registers it in the manager. + * + * @param class of the object to reference + * @param obj the object to reference + * @param type ({@code 1} use {@link SoftReference} or {@code 2} to use {@link WeakReference} + * @return newly created reference to the provided object + */ + public Reference create(T obj, int type) { + clearPendingReferences(); + var r = + switch (type) { + case 1 -> new SoftReference<>(obj, queue); + case 2 -> new WeakReference<>(obj, queue); + default -> throw new IllegalStateException(); + }; + refs.add(r); + return r; + } + + /** Releases all the references. E.g. cleans all the cached values. */ + public void releaseAll() { + var arr = refs.toArray(Reference[]::new); + for (var r : arr) { + r.clear(); + refs.remove(r); + } + } + + private void clearPendingReferences() { + for (; ; ) { + var r = queue.poll(); + if (r == null) { + break; + } + refs.remove(r); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java index 8bf74270d09f..c13ec8b9bb73 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java @@ -5,6 +5,9 @@ import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.Node; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; import org.enso.interpreter.dsl.Builtin; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; @@ -14,16 +17,24 @@ @ExportLibrary(TypesLibrary.class) @Builtin(pkg = "mutable", stdlibName = "Standard.Base.Runtime.Ref.Ref") public final class Ref implements EnsoObject { + /** + * {@code 0} - regular reference to an object {@code 1} - reference via {@link SoftReference} + * {@code 2} - reference via {@link WeakReference} + */ + private final byte type; + private volatile Object value; /** * Creates a new reference. * * @param value the initial value to store in the reference. + * @param referenceType type of reference to use */ @Builtin.Method(description = "Creates a new Ref", autoRegister = false) - public Ref(Object value) { - this.value = value; + public Ref(Object value, long referenceType) { + this.type = (byte) (referenceType & 0x03); + this.value = wrapValue(value); } /** @@ -32,7 +43,7 @@ public Ref(Object value) { @Builtin.Method(name = "get", description = "Gets the value stored in the reference") @SuppressWarnings("generic-enso-builtin-type") public Object getValue() { - return value; + return unwrapValue(value); } /** @@ -45,8 +56,8 @@ public Object getValue() { @SuppressWarnings("generic-enso-builtin-type") public Object setValue(Object value) { Object old = this.value; - this.value = value; - return old; + this.value = wrapValue(value); + return unwrapValue(old); } @ExportMessage @@ -68,4 +79,22 @@ boolean hasType() { Type getType(@Bind("$node") Node node) { return EnsoContext.get(node).getBuiltins().ref(); } + + private final Object wrapValue(Object v) { + if (type == 0) { + return v; + } + assert !(v instanceof Reference) : "Ref[" + type + ", " + v + "]"; + var ctx = EnsoContext.get(null); + return ctx.getReferencesManager().create(v, type); + } + + private final Object unwrapValue(Object v) { + if (v instanceof Reference ref) { + var ret = ref.get(); + return ret == null ? EnsoContext.get(null).getNothing() : ret; + } else { + return value; + } + } } diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso index 82c679a4f92d..1f78498a8319 100644 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso @@ -1,4 +1,6 @@ @Builtin_Type type Ref -new value = @Builtin_Method "Ref.new" +new value allow_gc=0 = new_impl value allow_gc + +private new_impl value allow_gc = @Builtin_Method "Ref.new" From d803b28559296c7da0c5eeb761990af0acbb8a78 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 18 Nov 2024 09:39:36 +0100 Subject: [PATCH 02/50] Hide operations with References behind @TruffleBoundary --- .../java/org/enso/interpreter/runtime/ReferencesManager.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ReferencesManager.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ReferencesManager.java index 80f003dcb51d..397253a71a68 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ReferencesManager.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ReferencesManager.java @@ -1,5 +1,6 @@ package org.enso.interpreter.runtime; +import com.oracle.truffle.api.CompilerDirectives; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; @@ -25,6 +26,7 @@ public final class ReferencesManager { * @param type ({@code 1} use {@link SoftReference} or {@code 2} to use {@link WeakReference} * @return newly created reference to the provided object */ + @CompilerDirectives.TruffleBoundary public Reference create(T obj, int type) { clearPendingReferences(); var r = @@ -38,6 +40,7 @@ public Reference create(T obj, int type) { } /** Releases all the references. E.g. cleans all the cached values. */ + @CompilerDirectives.TruffleBoundary public void releaseAll() { var arr = refs.toArray(Reference[]::new); for (var r : arr) { @@ -46,6 +49,7 @@ public void releaseAll() { } } + @CompilerDirectives.TruffleBoundary private void clearPendingReferences() { for (; ; ) { var r = queue.poll(); From 38a20b47bc263c95d320b39ecd7c824fba6c3145 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 18 Nov 2024 12:15:13 +0100 Subject: [PATCH 03/50] Work with v and not this.value --- .../src/main/java/org/enso/interpreter/runtime/data/Ref.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java index c13ec8b9bb73..d63d1a2a5f94 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java @@ -94,7 +94,7 @@ private final Object unwrapValue(Object v) { var ret = ref.get(); return ret == null ? EnsoContext.get(null).getNothing() : ret; } else { - return value; + return v; } } } From 404e3ccf95c36690d545b4b50ee5626b78e41d4f Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 20 Nov 2024 15:12:10 +0100 Subject: [PATCH 04/50] Fixing typo --- test/Table_Tests/src/IO/Fetch_Spec.enso | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Table_Tests/src/IO/Fetch_Spec.enso b/test/Table_Tests/src/IO/Fetch_Spec.enso index bb809ffb379a..24dbbb181110 100644 --- a/test/Table_Tests/src/IO/Fetch_Spec.enso +++ b/test/Table_Tests/src/IO/Fetch_Spec.enso @@ -141,14 +141,14 @@ add_specs suite_builder = get_cache_file_sizes -> Vector Integer = Vector.from_polyglot_array EnsoSecretHelper.getCache.getLRUCache.getFileSizes . sort Sort_Direction.Ascending - # Craetes a new cache each time, then resets it at the end + # Creates a new cache each time, then resets it at the end with_lru_cache lru_cache ~action = reset = EnsoSecretHelper.getCache.setLRUCache LRUCache.new Panic.with_finalizer reset <| EnsoSecretHelper.getCache.setLRUCache lru_cache action - # Craetes a new cache each time, then resets it at the end + # Creates a new cache each time, then resets it at the end with_config max_file_size total_cache_size ~action = now_getter = NowGetter.new disk_space_getter = DiskSpaceGetter.new @@ -156,14 +156,14 @@ add_specs suite_builder = lru_cache = LRUCache.new lru_cache_settings now_getter disk_space_getter with_lru_cache lru_cache (action now_getter disk_space_getter) - # Craetes a new cache each time, then resets it at the end + # Creates a new cache each time, then resets it at the end with_mocks ~action = now_getter = NowGetter.new disk_space_getter = DiskSpaceGetter.new lru_cache = LRUCache.new LRUCacheSettings.getDefault now_getter disk_space_getter with_lru_cache lru_cache (action now_getter disk_space_getter) - # Craetes a new cache each time, then resets it at the end + # Creates a new cache each time, then resets it at the end with_default_cache ~action = lru_cache = LRUCache.new with_lru_cache lru_cache action From 5629e20b9f23dbee9c61a8a8a5b7f9f9ce09ee93 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 20 Nov 2024 16:56:53 +0100 Subject: [PATCH 05/50] Using Ref.new to cache reference to EnsoHTTPResponseCache --- .../base/enso_cloud/EnsoSecretHelper.java | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java index 9859ab54cc7e..659f7697ffe4 100644 --- a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java +++ b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java @@ -19,10 +19,12 @@ import org.enso.base.net.URISchematic; import org.enso.base.net.URIWithSecrets; import org.graalvm.collections.Pair; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; /** Makes HTTP requests with secrets in either header or query string. */ public final class EnsoSecretHelper extends SecretValueResolver { - private static EnsoHTTPResponseCache cache; + private static Value cache; /** Gets a JDBC connection resolving EnsoKeyValuePair into the properties. */ public static Connection getJDBCConnection( @@ -177,14 +179,42 @@ public EnsoHttpResponse reconstructResponseFromCachedStream( } private static EnsoHTTPResponseCache getOrCreateCache() { - if (cache == null) { - cache = new EnsoHTTPResponseCache(); + if (getCache() instanceof EnsoHTTPResponseCache httpCache) { + return httpCache; + } else { + var module = + Context.getCurrent() + .eval( + "enso", + """ + import Standard.Base.Runtime.Ref.Ref + import Standard.Base.Data.Boolean.Boolean + + type Cache + private Value ref:Ref + + new obj -> Cache = + ref = Ref.new obj Boolean.False + Cache.Value ref + + get self = self.ref.get + """); + var cacheNew = module.invokeMember("eval_expression", "Cache.new"); + var httpCache = new EnsoHTTPResponseCache(); + cache = cacheNew.execute(httpCache); + return httpCache; } - return cache; } public static EnsoHTTPResponseCache getCache() { - return cache; + var c = cache instanceof Value v ? v.invokeMember("get") : null; + if (c != null + && c.isHostObject() + && c.asHostObject() instanceof EnsoHTTPResponseCache httpCache) { + return httpCache; + } else { + return null; + } } private static final Comparator> headerNameComparator = From b1d4b371cd29da838fd0227859bac7af12ce99b5 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 20 Nov 2024 17:43:32 +0100 Subject: [PATCH 06/50] Removing (unused) support for Name.Special --- .../builtin/special/NewRefNode.java | 12 ------ .../builtin/special/ReadRefNode.java | 12 ------ .../builtin/special/WriteRefNode.java | 13 ------ .../interpreter/runtime/builtin/Builtins.java | 6 --- .../interpreter/runtime/builtin/Special.java | 41 ------------------- .../interpreter/runtime/IrToTruffle.scala | 11 ----- 6 files changed, 95 deletions(-) delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/NewRefNode.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/ReadRefNode.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/WriteRefNode.java delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Special.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/NewRefNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/NewRefNode.java deleted file mode 100644 index 235d131a1cac..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/NewRefNode.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.special; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Ref; - -@BuiltinMethod(type = "Special", name = "") -public class NewRefNode extends Node { - public Ref execute() { - return new Ref(null, 0); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/ReadRefNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/ReadRefNode.java deleted file mode 100644 index eea49b884d4a..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/ReadRefNode.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.special; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Ref; - -@BuiltinMethod(type = "Special", name = "") -public class ReadRefNode extends Node { - public Object execute(Ref self) { - return self.getValue(); - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/WriteRefNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/WriteRefNode.java deleted file mode 100644 index 906f83084403..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/WriteRefNode.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.special; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.data.Ref; - -@BuiltinMethod(type = "Special", name = "") -public class WriteRefNode extends Node { - public Object execute(Ref self, Object value) { - self.setValue(value); - return null; - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java index 6698287f16da..306c3758d07f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java @@ -97,7 +97,6 @@ public static class Debug { private final Comparable comparable; private final DefaultComparator defaultComparator; private final System system; - private final Special special; // Builtin types private final Builtin any; @@ -181,7 +180,6 @@ public Builtins(EnsoContext context) { error = new Error(this, context); system = new System(this); number = new Number(this); - special = new Special(language); scope = scopeBuilder.build(); } @@ -769,10 +767,6 @@ public Type dataflowError() { return dataflowError.getType(); } - public Special special() { - return special; - } - /** * Returns the builtin module scope. * diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Special.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Special.java deleted file mode 100644 index 7c2ef271c4e0..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Special.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.enso.interpreter.runtime.builtin; - -import org.enso.interpreter.EnsoLanguage; -import org.enso.interpreter.node.expression.builtin.special.*; -import org.enso.interpreter.runtime.callable.function.Function; - -public class Special { - private final Function newRef; - private final Function readRef; - private final Function writeRef; - private final Function runThread; - private final Function joinThread; - - public Special(EnsoLanguage language) { - newRef = NewRefMethodGen.makeFunction(language); - readRef = ReadRefMethodGen.makeFunction(language); - writeRef = WriteRefMethodGen.makeFunction(language); - runThread = RunThreadMethodGen.makeFunction(language); - joinThread = JoinThreadMethodGen.makeFunction(language); - } - - public Function getNewRef() { - return newRef; - } - - public Function getReadRef() { - return readRef; - } - - public Function getWriteRef() { - return writeRef; - } - - public Function getRunThread() { - return runThread; - } - - public Function getJoinThread() { - return joinThread; - } -} diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala index d071f6e07c94..5f4c57d82c28 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala @@ -32,7 +32,6 @@ import org.enso.compiler.core.ir.module.scope.Definition import org.enso.compiler.core.ir.module.scope.definition import org.enso.compiler.core.ir.module.scope.Import import org.enso.compiler.core.ir.module.scope.imports -import org.enso.compiler.core.ir.Name.Special import org.enso.compiler.core.ir.expression.{ errors, Application, @@ -1963,16 +1962,6 @@ class IrToTruffle( "a Self occurence must be resolved" ).target ) - case Name.Special(name, _, _) => - val fun = name match { - case Special.NewRef => context.getBuiltins.special().getNewRef - case Special.ReadRef => context.getBuiltins.special().getReadRef - case Special.WriteRef => context.getBuiltins.special().getWriteRef - case Special.RunThread => context.getBuiltins.special().getRunThread - case Special.JoinThread => - context.getBuiltins.special().getJoinThread - } - ConstantObjectNode.build(fun) case _: Name.Annotation => throw new CompilerError( "Annotation should not be present at codegen time." From 0e42e9dd34b8ac6d0bda3801195180db6c4583a2 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 20 Nov 2024 17:43:48 +0100 Subject: [PATCH 07/50] Fixing moved import --- .../src/test/java/org/enso/interpreter/test/WarningsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/WarningsTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/WarningsTest.java index f29576cdb676..a6ff9aafd3ed 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/WarningsTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/WarningsTest.java @@ -13,7 +13,7 @@ import java.util.List; import org.enso.common.LanguageInfo; import org.enso.common.MethodNames; -import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; +import org.enso.interpreter.node.expression.foreign.HostValueToEnsoNode; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.data.hash.EnsoHashMap; import org.enso.interpreter.runtime.data.hash.HashMapGetNode; From 0b4e1f84c7985d86dec761f4d2e5a5ce6c8c1494 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 20 Nov 2024 17:45:38 +0100 Subject: [PATCH 08/50] Providing access to bodyNode in the builtin methods --- .../enso/interpreter/runtime/data/Ref.java | 24 ++++++++------- .../enso/interpreter/dsl/MethodProcessor.java | 30 +++++++++++-------- .../dsl/model/MethodDefinition.java | 22 +++++++++++++- 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java index a29e9f7197c9..878162d91c31 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java @@ -34,9 +34,10 @@ public final class Ref extends EnsoObject { * @param referenceType type of reference to use */ @Builtin.Method(description = "Creates a new Ref", autoRegister = false) - public Ref(Object value, long referenceType) { + public Ref(Node node, Object value, long referenceType) { + var ctx = EnsoContext.get(node); this.type = (byte) (referenceType & 0x03); - this.value = wrapValue(value); + this.value = wrapValue(ctx, value); } /** @@ -44,8 +45,9 @@ public Ref(Object value, long referenceType) { */ @Builtin.Method(name = "get", description = "Gets the value stored in the reference") @SuppressWarnings("generic-enso-builtin-type") - public Object getValue() { - return unwrapValue(value); + public Object getValue(Node node) { + var ctx = EnsoContext.get(node); + return unwrapValue(ctx, value); } /** @@ -56,10 +58,11 @@ public Object getValue() { */ @Builtin.Method(name = "put", description = "Stores a new value in the reference") @SuppressWarnings("generic-enso-builtin-type") - public Object setValue(Object value) { + public Object setValue(Node node, Object value) { + var ctx = EnsoContext.get(node); Object old = this.value; - this.value = wrapValue(value); - return unwrapValue(old); + this.value = wrapValue(ctx, value); + return unwrapValue(ctx, old); } @ExportMessage @@ -82,19 +85,18 @@ Type getType(@Bind("$node") Node node) { return EnsoContext.get(node).getBuiltins().ref(); } - private final Object wrapValue(Object v) { + private final Object wrapValue(EnsoContext ctx, Object v) { if (type == 0) { return v; } assert !(v instanceof Reference) : "Ref[" + type + ", " + v + "]"; - var ctx = EnsoContext.get(null); return ctx.getReferencesManager().create(v, type); } - private final Object unwrapValue(Object v) { + private final Object unwrapValue(EnsoContext ctx, Object v) { if (v instanceof Reference ref) { var ret = ref.get(); - return ret == null ? EnsoContext.get(null).getNothing() : ret; + return ret == null ? ctx.getNothing() : ret; } else { return v; } diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java index 891a5ab0fa6e..a4458a2268dd 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/MethodProcessor.java @@ -206,10 +206,10 @@ private void generateCode(MethodDefinition methodDefinition) throws IOException out.println(); out.println(" private static final class Internals {"); out.println(" Internals(boolean s) {"); - out.println(" this.staticOfInstanceMethod = s;"); + out.println(" this.staticOrInstanceMethod = s;"); out.println(" }"); out.println(); - out.println(" private final boolean staticOfInstanceMethod;"); + out.println(" private final boolean staticOrInstanceMethod;"); for (MethodDefinition.ArgumentDefinition arg : methodDefinition.getArguments()) { if (arg.shouldCheckErrors()) { @@ -239,10 +239,10 @@ private void generateCode(MethodDefinition methodDefinition) throws IOException out.println( " private " + methodDefinition.getClassName() - + "(EnsoLanguage language, boolean staticOfInstanceMethod) {"); + + "(EnsoLanguage language, boolean staticOrInstanceMethod) {"); out.println(" super(language);"); out.println(" this.bodyNode = " + methodDefinition.getConstructorExpression() + ";"); - out.println(" this.internals = new Internals(staticOfInstanceMethod);"); + out.println(" this.internals = new Internals(staticOrInstanceMethod);"); out.println(" }"); out.println(); @@ -258,11 +258,11 @@ private void generateCode(MethodDefinition methodDefinition) throws IOException out.println(); out.println( " public static Function makeFunction(EnsoLanguage language, boolean" - + " staticOfInstanceMethod) {"); - out.println(" if (staticOfInstanceMethod) {"); + + " staticOrInstanceMethod) {"); + out.println(" if (staticOrInstanceMethod) {"); out.println(" return Function." + functionBuilderMethod + "("); out.print( - " new " + methodDefinition.getClassName() + "(language, staticOfInstanceMethod)"); + " new " + methodDefinition.getClassName() + "(language, staticOrInstanceMethod)"); List argsStaticInstace = generateMakeFunctionArgs(true, methodDefinition.getArguments()); if (!argsStaticInstace.isEmpty()) { @@ -272,7 +272,7 @@ private void generateCode(MethodDefinition methodDefinition) throws IOException out.println(" } else {"); out.println(" return Function." + functionBuilderMethod + "("); out.print( - " new " + methodDefinition.getClassName() + "(language, staticOfInstanceMethod)"); + " new " + methodDefinition.getClassName() + "(language, staticOrInstanceMethod)"); List argsInstance = generateMakeFunctionArgs(false, methodDefinition.getArguments()); if (!argsInstance.isEmpty()) { out.println(","); @@ -288,7 +288,7 @@ private void generateCode(MethodDefinition methodDefinition) throws IOException out.println(" class Inlineable extends InlineableNode {"); out.println( " private final Internals extra = new" - + " Internals(internals.staticOfInstanceMethod);"); + + " Internals(internals.staticOrInstanceMethod);"); out.println( " private @Child " + methodDefinition.getOriginalClassName() @@ -334,7 +334,7 @@ private void generateCode(MethodDefinition methodDefinition) throws IOException + " bodyNode, AppendWarningNode appendWarningNode, WarningsLibrary warnLib," + " HashMapInsertAllNode mapInsertAllNode, Object[] args) {"); } - out.println(" var prefix = internals.staticOfInstanceMethod ? 1 : 0;"); + out.println(" var prefix = internals.staticOrInstanceMethod ? 1 : 0;"); out.println(" State state = Function.ArgumentsHelper.getState(args);"); if (methodDefinition.needsCallerInfo()) { out.println(" CallerInfo callerInfo = Function.ArgumentsHelper.getCallerInfo(args);"); @@ -343,7 +343,11 @@ private void generateCode(MethodDefinition methodDefinition) throws IOException " Object[] arguments = Function.ArgumentsHelper.getPositionalArguments(args);"); List callArgNames = new ArrayList<>(); for (MethodDefinition.ArgumentDefinition arg : methodDefinition.getArguments()) { - if (!(arg.isImplicit() || arg.isFrame() || arg.isState() || arg.isCallerInfo())) { + if (!(arg.isImplicit() + || arg.isFrame() + || arg.isState() + || arg.isCallerInfo() + || arg.isNode())) { out.println( " int arg" + arg.getPosition() + "Idx = " + arg.getPosition() + " + prefix;"); } @@ -361,6 +365,8 @@ private void generateCode(MethodDefinition methodDefinition) throws IOException callArgNames.add("state"); } else if (argumentDefinition.isFrame()) { callArgNames.add("frame"); + } else if (argumentDefinition.isNode()) { + callArgNames.add("bodyNode"); } else if (argumentDefinition.isCallerInfo()) { callArgNames.add("callerInfo"); } else { @@ -414,7 +420,7 @@ private void generateCode(MethodDefinition methodDefinition) throws IOException out.println( " return new " + methodDefinition.getClassName() - + "(EnsoLanguage.get(this), internals.staticOfInstanceMethod);"); + + "(EnsoLanguage.get(this), internals.staticOrInstanceMethod);"); out.println(" }"); out.println(); diff --git a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java index 6c778902e7bc..8933dfd061f1 100644 --- a/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java +++ b/lib/scala/interpreter-dsl/src/main/java/org/enso/interpreter/dsl/model/MethodDefinition.java @@ -288,6 +288,11 @@ public interface ArgumentDefinition { */ boolean isFrame(); + /** + * @return whether this argument should be passed the node implementing the method. + */ + boolean isNode(); + /** * @return whether this argument should be passed the caller info. */ @@ -371,6 +376,11 @@ public boolean isFrame() { return false; } + @Override + public boolean isNode() { + return false; + } + @Override public boolean isCallerInfo() { return false; @@ -460,6 +470,7 @@ public boolean isImplicit() { /** A domain specific representation of a method argument. */ public static class ArgumentDefinitionFromParameter implements ArgumentDefinition { private static final String VIRTUAL_FRAME = "com.oracle.truffle.api.frame.VirtualFrame"; + private static final String NODE = "com.oracle.truffle.api.nodes.Node"; private static final String OBJECT = "java.lang.Object"; private static final String THUNK = "org.enso.interpreter.runtime.callable.argument.Thunk"; private static final String CALLER_INFO = "org.enso.interpreter.runtime.callable.CallerInfo"; @@ -471,6 +482,7 @@ public static class ArgumentDefinitionFromParameter implements ArgumentDefinitio private final TypeMirror type; private final String name; private final boolean isState; + private final boolean isNode; private final boolean isFrame; private final boolean isCallerInfo; private final boolean isSuspended; @@ -498,6 +510,7 @@ public ArgumentDefinitionFromParameter(VariableElement element, int position) { || type.toString().equals(DATAFLOW_ERROR); acceptsWarning = element.getAnnotation(AcceptsWarning.class) != null; isFrame = type.toString().equals(VIRTUAL_FRAME); + isNode = type.toString().equals(NODE); isCallerInfo = type.toString().equals(CALLER_INFO); this.position = position; } @@ -560,6 +573,13 @@ public boolean isFrame() { return isFrame; } + /** + * @return whether this argument should be passed the node. + */ + public boolean isNode() { + return isNode; + } + /** * @return whether this argument should be passed the caller info. */ @@ -571,7 +591,7 @@ public boolean isCallerInfo() { * @return whether this argument should be passed the next positional function argument. */ public boolean isPositional() { - return !isFrame() && !isState() && !isCallerInfo(); + return !isFrame() && !isState() && !isCallerInfo() && !isNode(); } /** From f5f1614ad7c7ecb72f180dda5da85aa2243bbeab Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 20 Nov 2024 17:56:33 +0100 Subject: [PATCH 09/50] Enabling allow_gc in the caches --- .../main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java index 659f7697ffe4..9bcd1b97f98d 100644 --- a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java +++ b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java @@ -194,7 +194,7 @@ private static EnsoHTTPResponseCache getOrCreateCache() { private Value ref:Ref new obj -> Cache = - ref = Ref.new obj Boolean.False + ref = Ref.new obj Boolean.True Cache.Value ref get self = self.ref.get From dad2bb657c2b88f080e6829946436d2a966516ad Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 20 Nov 2024 17:58:27 +0100 Subject: [PATCH 10/50] Note in changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e608d050a646..5c077420edf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ operation.][11490] - [Added `Table.input` allowing creation of typed tables from vectors of data, including auto parsing text columns.][11562] +- [Ref.new with allow_gc][11577] [11235]: https://github.com/enso-org/enso/pull/11235 [11255]: https://github.com/enso-org/enso/pull/11255 @@ -68,6 +69,7 @@ [11373]: https://github.com/enso-org/enso/pull/11373 [11490]: https://github.com/enso-org/enso/pull/11490 [11562]: https://github.com/enso-org/enso/pull/11562 +[11577]: https://github.com/enso-org/enso/pull/11577 #### Enso Language & Runtime From e26a8688546be693bf071ca867aa6da5e9f4853e Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Mon, 25 Nov 2024 17:00:11 -0500 Subject: [PATCH 11/50] wip --- .../Standard/Base/0.0.0-dev/src/Runtime.enso | 3 ++ .../runtime/ReleaseReferencesNode.java | 35 +++++++++++++++++ .../base/enso_cloud/EnsoSecretHelper.java | 38 +++++++++++++++++++ test/Table_Tests/src/IO/Fetch_Spec.enso | 9 +++++ 4 files changed, 85 insertions(+) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ReleaseReferencesNode.java diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso index 5d44fad3de4e..8cb7d4d976da 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso @@ -54,6 +54,9 @@ get_stack_trace = gc : Nothing gc = @Builtin_Method "Runtime.gc" +release_references : Nothing +release_references = @Builtin_Method "Runtime.release_references" + ## PRIVATE ADVANCED diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ReleaseReferencesNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ReleaseReferencesNode.java new file mode 100644 index 000000000000..8faa902a4c1c --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ReleaseReferencesNode.java @@ -0,0 +1,35 @@ +package org.enso.interpreter.node.expression.builtin.runtime; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.EnsoContext; + +@BuiltinMethod( + type = "Runtime", + name = "release_references", + description = "Releases registered references", + autoRegister = false) +public abstract class ReleaseReferencesNode extends Node { + + public abstract Object execute(); + + /** + * @return A new ReleaseReferencesNode. + */ + public static ReleaseReferencesNode build() { + return ReleaseReferencesNodeGen.create(); + } + + @Specialization + Object doReleaseReferences() { + releaseReferences(); + return EnsoContext.get(this).getBuiltins().nothing(); + } + + @CompilerDirectives.TruffleBoundary + private void releaseReferences() { + EnsoContext.get(this).getReferencesManager().releaseAll(); + } +} diff --git a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java index 9bcd1b97f98d..f66a10b4723d 100644 --- a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java +++ b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java @@ -25,6 +25,7 @@ /** Makes HTTP requests with secrets in either header or query string. */ public final class EnsoSecretHelper extends SecretValueResolver { private static Value cache; + private static Value triggerReference; /** Gets a JDBC connection resolving EnsoKeyValuePair into the properties. */ public static Connection getJDBCConnection( @@ -189,11 +190,14 @@ private static EnsoHTTPResponseCache getOrCreateCache() { """ import Standard.Base.Runtime.Ref.Ref import Standard.Base.Data.Boolean.Boolean + import Standard.Base.Runtime.Managed_Resource.Managed_Resource + polyglot java import org.enso.base.enso_cloud.EnsoSecretHelper type Cache private Value ref:Ref new obj -> Cache = + #EnsoSecretHelper.hello 10 ref = Ref.new obj Boolean.True Cache.Value ref @@ -206,6 +210,10 @@ private static EnsoHTTPResponseCache getOrCreateCache() { } } + public static void hello(long x) { + System.out.println("HELLO " + x); + } + public static EnsoHTTPResponseCache getCache() { var c = cache instanceof Value v ? v.invokeMember("get") : null; if (c != null @@ -217,6 +225,36 @@ public static EnsoHTTPResponseCache getCache() { } } + public static void installTriggerReference() { + var module = + Context.getCurrent() + .eval( + "enso", + """ + import Standard.Base.IO + import Standard.Base.Runtime.Ref.Ref + import Standard.Base.Data.Boolean.Boolean + import Standard.Base.Runtime.Managed_Resource.Managed_Resource + polyglot java import org.enso.base.enso_cloud.EnsoSecretHelper + + type Cache2 + private Value ref:Ref + + new -> Cache2 = + do_clear _ = + IO.println "Hi from clearer" + EnsoSecretHelper.hello 11 + mr = Managed_Resource.register 0 do_clear + ref = Ref.new mr Boolean.True + IO.println "Hi from TR" + Cache2.Value ref + + fin self = self.ref.get.finalize + get self = self.ref.get + """); + triggerReference = module.invokeMember("eval_expression", "Cache2.new"); + } + private static final Comparator> headerNameComparator = Comparator.comparing((Pair pair) -> pair.getLeft()) .thenComparing(Comparator.comparing(pair -> pair.getRight())); diff --git a/test/Table_Tests/src/IO/Fetch_Spec.enso b/test/Table_Tests/src/IO/Fetch_Spec.enso index 24dbbb181110..51b3fcbd956c 100644 --- a/test/Table_Tests/src/IO/Fetch_Spec.enso +++ b/test/Table_Tests/src/IO/Fetch_Spec.enso @@ -512,3 +512,12 @@ add_specs suite_builder = LRUCache.new . getSettings . getTotalCacheLimit . should_equal (TotalCacheLimit.Percentage.new 0.2) Test_Environment.unsafe_with_environment_override "ENSO_LIB_HTTP_CACHE_MAX_TOTAL_CACHE_LIMIT" "101%" <| LRUCache.new . getSettings . getTotalCacheLimit . should_equal (TotalCacheLimit.Percentage.new 0.2) + + group_builder.specify "Cache should be cleared when the trigger reference is garbage collected" <| + #HTTP.fetch url0 . decode_as_text + #cache = EnsoSecretHelper.getCache + #_ = cache + EnsoSecretHelper.installTriggerReference + Runtime.release_references + Runtime.gc + _ = 11 From e003b8ac90682306d2e474a9ab805e628f73d59b Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Tue, 26 Nov 2024 14:02:15 -0500 Subject: [PATCH 12/50] revert ESH changes --- .../base/enso_cloud/EnsoSecretHelper.java | 74 +------------------ 1 file changed, 4 insertions(+), 70 deletions(-) diff --git a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java index f66a10b4723d..87bbe87e9e84 100644 --- a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java +++ b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java @@ -25,7 +25,6 @@ /** Makes HTTP requests with secrets in either header or query string. */ public final class EnsoSecretHelper extends SecretValueResolver { private static Value cache; - private static Value triggerReference; /** Gets a JDBC connection resolving EnsoKeyValuePair into the properties. */ public static Connection getJDBCConnection( @@ -180,79 +179,14 @@ public EnsoHttpResponse reconstructResponseFromCachedStream( } private static EnsoHTTPResponseCache getOrCreateCache() { - if (getCache() instanceof EnsoHTTPResponseCache httpCache) { - return httpCache; - } else { - var module = - Context.getCurrent() - .eval( - "enso", - """ - import Standard.Base.Runtime.Ref.Ref - import Standard.Base.Data.Boolean.Boolean - import Standard.Base.Runtime.Managed_Resource.Managed_Resource - polyglot java import org.enso.base.enso_cloud.EnsoSecretHelper - - type Cache - private Value ref:Ref - - new obj -> Cache = - #EnsoSecretHelper.hello 10 - ref = Ref.new obj Boolean.True - Cache.Value ref - - get self = self.ref.get - """); - var cacheNew = module.invokeMember("eval_expression", "Cache.new"); - var httpCache = new EnsoHTTPResponseCache(); - cache = cacheNew.execute(httpCache); - return httpCache; + if (cache == null) { + cache = new EnsoHTTPResponseCache(); } - } - - public static void hello(long x) { - System.out.println("HELLO " + x); + return cache; } public static EnsoHTTPResponseCache getCache() { - var c = cache instanceof Value v ? v.invokeMember("get") : null; - if (c != null - && c.isHostObject() - && c.asHostObject() instanceof EnsoHTTPResponseCache httpCache) { - return httpCache; - } else { - return null; - } - } - - public static void installTriggerReference() { - var module = - Context.getCurrent() - .eval( - "enso", - """ - import Standard.Base.IO - import Standard.Base.Runtime.Ref.Ref - import Standard.Base.Data.Boolean.Boolean - import Standard.Base.Runtime.Managed_Resource.Managed_Resource - polyglot java import org.enso.base.enso_cloud.EnsoSecretHelper - - type Cache2 - private Value ref:Ref - - new -> Cache2 = - do_clear _ = - IO.println "Hi from clearer" - EnsoSecretHelper.hello 11 - mr = Managed_Resource.register 0 do_clear - ref = Ref.new mr Boolean.True - IO.println "Hi from TR" - Cache2.Value ref - - fin self = self.ref.get.finalize - get self = self.ref.get - """); - triggerReference = module.invokeMember("eval_expression", "Cache2.new"); + return cache; } private static final Comparator> headerNameComparator = From 5d97a2aa8d514fc3273b8b00e1e6bd47c54c1baf Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Tue, 26 Nov 2024 14:33:01 -0500 Subject: [PATCH 13/50] 1 test --- .../java/org/enso/base/cache/LRUCache.java | 11 +++++ .../org/enso/base/cache/ReloadDetector.java | 49 +++++++++++++++++++ .../base/enso_cloud/EnsoSecretHelper.java | 4 +- test/Table_Tests/src/IO/Fetch_Spec.enso | 31 +++++++++--- 4 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java diff --git a/std-bits/base/src/main/java/org/enso/base/cache/LRUCache.java b/std-bits/base/src/main/java/org/enso/base/cache/LRUCache.java index 9368cfe74e23..655231993868 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/LRUCache.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/LRUCache.java @@ -73,6 +73,9 @@ public class LRUCache { /** Used to get the current free disk space; mockable. */ private final DiskSpaceGetter diskSpaceGetter; + /** Used to clear the cache on reload. */ + private final ReloadDetector reloadDetector = new ReloadDetector(); + public LRUCache() { this(LRUCacheSettings.getDefault(), new NowGetter(), new DiskSpaceGetter()); } @@ -85,6 +88,8 @@ public LRUCache(LRUCacheSettings settings, NowGetter nowGetter, DiskSpaceGetter public CacheResult getResult(ItemBuilder itemBuilder) throws IOException, InterruptedException, ResponseTooLargeException { + clearOnReload(); + String cacheKey = itemBuilder.makeCacheKey(); if (cache.containsKey(cacheKey)) { return getResultForCacheEntry(cacheKey); @@ -199,6 +204,12 @@ public void clear() { removeCacheEntriesByPredicate(e -> true); } + private void clearOnReload() { + if (reloadDetector.hasReloadOccurred()) { + clear(); + } + } + /** Remove all cache entries (and their cache files) that match the predicate. */ private void removeCacheEntriesByPredicate(Predicate> predicate) { List>> toRemove = diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java new file mode 100644 index 000000000000..6f622b3c30fe --- /dev/null +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -0,0 +1,49 @@ +package org.enso.base.cache; + +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; + +/** + * Detects that the reload button has been pressed. + * + * .hasReloadOccurred() returns true if the reload button was pressed since the + * last call to .hasReloadOccurred(). + * + */ + +public class ReloadDetector { + private Value triggerRef; + + public ReloadDetector() { + resetTriggerRef(); + } + + public boolean hasReloadOccurred() { + var reloadHasOccurred = triggerRef.invokeMember("get").isNull(); + if (reloadHasOccurred) { + resetTriggerRef(); + } + return reloadHasOccurred; + } + + private void resetTriggerRef() { + var module = + Context.getCurrent() + .eval( + "enso", + """ + import Standard.Base.Runtime.Ref.Ref + import Standard.Base.Data.Boolean.Boolean + + type Trigger + private Value ref:Ref + + new -> Trigger = + ref = Ref.new 0 Boolean.True + Trigger.Value ref + + get self = self.ref.get + """); + triggerRef = module.invokeMember("eval_expression", "Trigger.new"); + } +} \ No newline at end of file diff --git a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java index 87bbe87e9e84..9859ab54cc7e 100644 --- a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java +++ b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java @@ -19,12 +19,10 @@ import org.enso.base.net.URISchematic; import org.enso.base.net.URIWithSecrets; import org.graalvm.collections.Pair; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Value; /** Makes HTTP requests with secrets in either header or query string. */ public final class EnsoSecretHelper extends SecretValueResolver { - private static Value cache; + private static EnsoHTTPResponseCache cache; /** Gets a JDBC connection resolving EnsoKeyValuePair into the properties. */ public static Connection getJDBCConnection( diff --git a/test/Table_Tests/src/IO/Fetch_Spec.enso b/test/Table_Tests/src/IO/Fetch_Spec.enso index 51b3fcbd956c..af5650512cd5 100644 --- a/test/Table_Tests/src/IO/Fetch_Spec.enso +++ b/test/Table_Tests/src/IO/Fetch_Spec.enso @@ -168,6 +168,9 @@ add_specs suite_builder = lru_cache = LRUCache.new with_lru_cache lru_cache action + fake_reload = + Runtime.release_references + url0 = base_url_with_slash+'test_download?max-age=16&length=10' url1 = base_url_with_slash+'test_download?max-age=16&length=20' url_post = base_url_with_slash + "post" @@ -513,11 +516,23 @@ add_specs suite_builder = Test_Environment.unsafe_with_environment_override "ENSO_LIB_HTTP_CACHE_MAX_TOTAL_CACHE_LIMIT" "101%" <| LRUCache.new . getSettings . getTotalCacheLimit . should_equal (TotalCacheLimit.Percentage.new 0.2) - group_builder.specify "Cache should be cleared when the trigger reference is garbage collected" <| - #HTTP.fetch url0 . decode_as_text - #cache = EnsoSecretHelper.getCache - #_ = cache - EnsoSecretHelper.installTriggerReference - Runtime.release_references - Runtime.gc - _ = 11 + group_builder.specify "Cache should be cleared when the trigger reference is garbage collected because of a reload" <| + HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=10' + HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=11' + HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=12' + get_num_response_cache_entries . should_equal 3 + + fake_reload + + get_num_response_cache_entries . should_equal 3 # Cleaning is not triggered until the next request + HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=13' + get_num_response_cache_entries . should_equal 1 + HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=14' + HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=15' + get_num_response_cache_entries . should_equal 3 + + fake_reload + + get_num_response_cache_entries . should_equal 3 # Cleaning is not triggered until the next request + HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=16' + get_num_response_cache_entries . should_equal 1 From 6a1e89fe4b68e19725a571adc15e13585f44a9e8 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Tue, 26 Nov 2024 14:37:43 -0500 Subject: [PATCH 14/50] docs --- .../src/main/java/org/enso/base/cache/ReloadDetector.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java index 6f622b3c30fe..2f5378c004fd 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -5,13 +5,17 @@ /** * Detects that the reload button has been pressed. - * + * * .hasReloadOccurred() returns true if the reload button was pressed since the * last call to .hasReloadOccurred(). * + * This uses a weak reference (created in eval'd Enso code) that is set to null + * on reload. + * */ public class ReloadDetector { + // Weak reference that is set to null on reload. private Value triggerRef; public ReloadDetector() { From c493ec0a1544d0e204f7f0f011c5dac9171e8c5f Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Tue, 26 Nov 2024 14:54:05 -0500 Subject: [PATCH 15/50] Clear references on reload --- .../interpreter/instrument/command/RecomputeContextCmd.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala index 43b6df2bab42..dba5e325edc7 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala @@ -11,6 +11,7 @@ import org.enso.interpreter.instrument.{ } import org.enso.interpreter.instrument.execution.RuntimeContext import org.enso.interpreter.instrument.job.{EnsureCompiledJob, ExecuteJob} +import org.enso.interpreter.runtime.EnsoContext import org.enso.polyglot.runtime.Runtime.Api import org.enso.polyglot.runtime.Runtime.Api.RequestId @@ -42,6 +43,7 @@ class RecomputeContextCmd( ec: ExecutionContext ): Future[Boolean] = { Future { + EnsoContext.get(null).getReferencesManager().releaseAll() ctx.jobControlPlane.abortJobs( request.contextId, "recompute context", From 131c52e13963f18313b8f4ef2ca319eddd8896fa Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Wed, 27 Nov 2024 10:26:43 -0500 Subject: [PATCH 16/50] doc --- .../base/src/main/java/org/enso/base/cache/ReloadDetector.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java index 2f5378c004fd..0126df1f61d4 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -31,6 +31,8 @@ public boolean hasReloadOccurred() { } private void resetTriggerRef() { + // The `0` value stored in the reference is not used; it just has to + // something other than null. var module = Context.getCurrent() .eval( From 5b563d41f101a6356a698ece67547f3447a487e6 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 27 Nov 2024 17:29:34 +0100 Subject: [PATCH 17/50] Can no longer invoke Managed_Resource.with when Managed_Resource.finalize was called --- .../src/Runtime/Managed_Resource.enso | 6 ++- .../expression/builtin/resource/WithNode.java | 18 ++++--- .../interpreter/runtime/ResourceManager.java | 1 + .../runtime/data/ManagedResource.java | 35 ++++++++++-- .../src/Runtime/Managed_Resource_Spec.enso | 53 +++++++++++++++++++ 5 files changed, 102 insertions(+), 11 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso index 35701ebfdc74..9312b0a121b6 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso @@ -58,11 +58,15 @@ type Managed_Resource ADVANCED Executes the provided action on the resource managed by the managed - resource object. + resource object. The action is only invoked if the managed resource + has not yet been finalized. Arguments: - action: The action that will be applied to the resource managed by resource. + Returns: + Value returned from the `action` or `Nothing` if the managed resource + was already finalized with : (Any -> Any) -> Any with self ~action = @Builtin_Method "Managed_Resource.with" diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java index 2dd940690f4c..1f61fab30186 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java @@ -6,7 +6,6 @@ import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.runtime.EnsoContext; -import org.enso.interpreter.runtime.ResourceManager; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.data.ManagedResource; import org.enso.interpreter.runtime.state.State; @@ -33,12 +32,17 @@ static WithNode build() { @Specialization Object doWith(State state, VirtualFrame frame, ManagedResource self, Object action) { - ResourceManager resourceManager = EnsoContext.get(this).getResourceManager(); - resourceManager.park(self); - try { - return invokeCallableNode.execute(action, frame, state, new Object[] {self.getResource()}); - } finally { - resourceManager.unpark(self); + var ctx = EnsoContext.get(this); + var resourceManager = ctx.getResourceManager(); + if (self.getPhantomReference().refersTo(self)) { + resourceManager.park(self); + try { + return invokeCallableNode.execute(action, frame, state, new Object[] {self.getResource()}); + } finally { + resourceManager.unpark(self); + } + } else { + return ctx.getBuiltins().nothing(); } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java index b8295c6b9457..78c8bb26937f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java @@ -408,6 +408,7 @@ private Item( @CompilerDirectives.TruffleBoundary private void finalizeNow(EnsoContext context) { try { + clear(); InteropLibrary.getUncached(finalizer).execute(finalizer, underlying); } catch (Exception e) { context.getErr().println("Exception in finalizer: " + e.getMessage()); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java index 424eac477f3c..826e5554e788 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java @@ -1,5 +1,6 @@ package org.enso.interpreter.runtime.data; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.interop.InteropLibrary; @@ -12,7 +13,27 @@ import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; -/** A runtime representation of a managed resource. */ +/** + * An Enso runtime representation of a managed resource. + * + *

Instance of this class is convoluted with instances playing various roles: + * + *

    + *
  • this {@link ManagedResource} points to {@code resource} + *
  • this {@link ManagedResource} points to {@link PhantomReference} that is "phantom + * referencing" {@code this} + *
  • the implementation of {@link PhantomReference} is {@code Item} in {@link ResourceManager} + *
  • the {@code Item} "phantom referencing" {@code this} {@link ManagedResource} is + * stored in {@link ResourceManager} {@code pendingItems} collection. + *
  • the {@code Item} has a pointer to the {@code resource} as well + *
  • the {@code Item} has a pointer to the {@code finalizer} function + *
+ * + * Once all this braided chunk of objects is eligible for GC because nobody holds pointer to + * {@link ManagedResource}, the {@code Item} is put into {@link ResourceManager} {@code + * referenceQueue} and process by the intricate machinery of {@link ResourceManager} and its {@code + * ProcessItems} processor. + */ @ExportLibrary(InteropLibrary.class) @ExportLibrary(TypesLibrary.class) @Builtin(pkg = "resource", stdlibName = "Standard.Base.Runtime.Managed_Resource.Managed_Resource") @@ -97,8 +118,16 @@ Type getType(@Bind("$node") Node node) { @ExportMessage @TruffleBoundary + public String toDisplayString(boolean allowSideEffects, @Bind("$node") Node node) { + var type = getType(node); + return type.getName() + + " " + + InteropLibrary.getUncached().toDisplayString(resource, allowSideEffects); + } + + @ExportMessage.Ignore @Override - public String toDisplayString(boolean allowSideEffects) { - return resource.toString(); + public Object toDisplayString(boolean allowSideEffects) { + throw CompilerDirectives.shouldNotReachHere(); } } diff --git a/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso b/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso index 14f3cd6730cc..69ddd4252397 100644 --- a/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso +++ b/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso @@ -2,6 +2,7 @@ from Standard.Base import all import Standard.Base.Data.Vector.Builder import Standard.Base.Errors.Illegal_State.Illegal_State import Standard.Base.Runtime.Managed_Resource.Managed_Resource +import Standard.Base.Runtime.Ref.Ref import project.Runtime.GC_Example from Standard.Test import all @@ -68,6 +69,58 @@ add_specs suite_builder = suite_builder.group "Managed_Resource" group_builder-> if messages.last != "OK" then Test.fail (messages.join '\n') + group_builder.specify "register_with_finalize" <| + messages = Vector.build builder-> + builder.append "" + + create_resource value = + # registers new resource + Managed_Resource.register (Ref.new value) v-> + v.put -1 + builder.append " finalizing:"+v.to_text + + mr = create_resource 42 + + builder.append "Allocated: "+mr.to_text + + # operates with its value + out = mr.with v-> + builder.append " with :"+v.to_text + v.put 7 + v.modify n-> + builder.append " modify:"+n.to_text + 6 + v + builder.append "With finished:"+out.to_text + + # finalizes the resource + mr.finalize + builder.append "Finalized:"+mr.to_text + + # operation on finalized resource + none = mr.with v-> + # should never be called + builder.append " empty :"+v.to_text + "Don't call me!" + + builder.append none.to_text + none.is_nothing . should_be_true + + exp_text = """ + + Allocated: Managed_Resource 42 + with :42 + modify:7 + With finished:6 + finalizing:-1 + Finalized:Managed_Resource -1 + Nothing + + msg_text = messages.join '\n' + + if msg_text != exp_text then + Test.fail (msg_text) + main filter=Nothing = suite = Test.build suite_builder-> add_specs suite_builder From dc2decd9fffee1e0d0c2abce13f04ab698207be3 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Wed, 27 Nov 2024 11:30:49 -0500 Subject: [PATCH 18/50] remove reference release builtin and simulate reload --- .../Standard/Base/0.0.0-dev/src/Runtime.enso | 3 -- .../runtime/ReleaseReferencesNode.java | 35 ------------------- .../java/org/enso/base/cache/LRUCache.java | 5 +++ .../org/enso/base/cache/ReloadDetector.java | 7 ++++ test/Table_Tests/src/IO/Fetch_Spec.enso | 2 +- 5 files changed, 13 insertions(+), 39 deletions(-) delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ReleaseReferencesNode.java diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso index 8cb7d4d976da..5d44fad3de4e 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso @@ -54,9 +54,6 @@ get_stack_trace = gc : Nothing gc = @Builtin_Method "Runtime.gc" -release_references : Nothing -release_references = @Builtin_Method "Runtime.release_references" - ## PRIVATE ADVANCED diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ReleaseReferencesNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ReleaseReferencesNode.java deleted file mode 100644 index 8faa902a4c1c..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ReleaseReferencesNode.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.runtime; - -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.EnsoContext; - -@BuiltinMethod( - type = "Runtime", - name = "release_references", - description = "Releases registered references", - autoRegister = false) -public abstract class ReleaseReferencesNode extends Node { - - public abstract Object execute(); - - /** - * @return A new ReleaseReferencesNode. - */ - public static ReleaseReferencesNode build() { - return ReleaseReferencesNodeGen.create(); - } - - @Specialization - Object doReleaseReferences() { - releaseReferences(); - return EnsoContext.get(this).getBuiltins().nothing(); - } - - @CompilerDirectives.TruffleBoundary - private void releaseReferences() { - EnsoContext.get(this).getReferencesManager().releaseAll(); - } -} diff --git a/std-bits/base/src/main/java/org/enso/base/cache/LRUCache.java b/std-bits/base/src/main/java/org/enso/base/cache/LRUCache.java index 655231993868..f658da4b7efe 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/LRUCache.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/LRUCache.java @@ -321,6 +321,11 @@ public LRUCacheSettings getSettings() { return settings; } + /** Public for testing. */ + public void simulateReloadTestOnly() { + reloadDetector.simulateReloadTestOnly(); + } + private record CacheEntry(File responseData, M metadata, long size, ZonedDateTime expiry) {} /** diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java index 0126df1f61d4..e86909fffb3d 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -40,6 +40,7 @@ private void resetTriggerRef() { """ import Standard.Base.Runtime.Ref.Ref import Standard.Base.Data.Boolean.Boolean + import Standard.Base.Nothing.Nothing type Trigger private Value ref:Ref @@ -49,7 +50,13 @@ private void resetTriggerRef() { Trigger.Value ref get self = self.ref.get + + clear self = self.ref.put Nothing """); triggerRef = module.invokeMember("eval_expression", "Trigger.new"); } + + void simulateReloadTestOnly() { + triggerRef.invokeMember("clear"); + } } \ No newline at end of file diff --git a/test/Table_Tests/src/IO/Fetch_Spec.enso b/test/Table_Tests/src/IO/Fetch_Spec.enso index af5650512cd5..fb5b438c6df3 100644 --- a/test/Table_Tests/src/IO/Fetch_Spec.enso +++ b/test/Table_Tests/src/IO/Fetch_Spec.enso @@ -169,7 +169,7 @@ add_specs suite_builder = with_lru_cache lru_cache action fake_reload = - Runtime.release_references + EnsoSecretHelper.getCache.getLRUCache.simulateReloadTestOnly url0 = base_url_with_slash+'test_download?max-age=16&length=10' url1 = base_url_with_slash+'test_download?max-age=16&length=20' From ef15d9051cfb5e2f69bf3c1e1c9a0fd7f5f397f7 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 27 Nov 2024 17:33:38 +0100 Subject: [PATCH 19/50] Can no longer invoke Managed_Resource.with when Managed_Resource.finalize was called --- .../src/Runtime/Managed_Resource.enso | 6 ++- .../expression/builtin/resource/WithNode.java | 18 ++++--- .../interpreter/runtime/ResourceManager.java | 1 + .../runtime/data/ManagedResource.java | 35 ++++++++++-- .../src/Runtime/Managed_Resource_Spec.enso | 53 +++++++++++++++++++ 5 files changed, 102 insertions(+), 11 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso index 35701ebfdc74..9312b0a121b6 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso @@ -58,11 +58,15 @@ type Managed_Resource ADVANCED Executes the provided action on the resource managed by the managed - resource object. + resource object. The action is only invoked if the managed resource + has not yet been finalized. Arguments: - action: The action that will be applied to the resource managed by resource. + Returns: + Value returned from the `action` or `Nothing` if the managed resource + was already finalized with : (Any -> Any) -> Any with self ~action = @Builtin_Method "Managed_Resource.with" diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java index 2dd940690f4c..1f61fab30186 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java @@ -6,7 +6,6 @@ import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.runtime.EnsoContext; -import org.enso.interpreter.runtime.ResourceManager; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.data.ManagedResource; import org.enso.interpreter.runtime.state.State; @@ -33,12 +32,17 @@ static WithNode build() { @Specialization Object doWith(State state, VirtualFrame frame, ManagedResource self, Object action) { - ResourceManager resourceManager = EnsoContext.get(this).getResourceManager(); - resourceManager.park(self); - try { - return invokeCallableNode.execute(action, frame, state, new Object[] {self.getResource()}); - } finally { - resourceManager.unpark(self); + var ctx = EnsoContext.get(this); + var resourceManager = ctx.getResourceManager(); + if (self.getPhantomReference().refersTo(self)) { + resourceManager.park(self); + try { + return invokeCallableNode.execute(action, frame, state, new Object[] {self.getResource()}); + } finally { + resourceManager.unpark(self); + } + } else { + return ctx.getBuiltins().nothing(); } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java index b8295c6b9457..78c8bb26937f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java @@ -408,6 +408,7 @@ private Item( @CompilerDirectives.TruffleBoundary private void finalizeNow(EnsoContext context) { try { + clear(); InteropLibrary.getUncached(finalizer).execute(finalizer, underlying); } catch (Exception e) { context.getErr().println("Exception in finalizer: " + e.getMessage()); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java index 424eac477f3c..826e5554e788 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java @@ -1,5 +1,6 @@ package org.enso.interpreter.runtime.data; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.interop.InteropLibrary; @@ -12,7 +13,27 @@ import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; -/** A runtime representation of a managed resource. */ +/** + * An Enso runtime representation of a managed resource. + * + *

Instance of this class is convoluted with instances playing various roles: + * + *

    + *
  • this {@link ManagedResource} points to {@code resource} + *
  • this {@link ManagedResource} points to {@link PhantomReference} that is "phantom + * referencing" {@code this} + *
  • the implementation of {@link PhantomReference} is {@code Item} in {@link ResourceManager} + *
  • the {@code Item} "phantom referencing" {@code this} {@link ManagedResource} is + * stored in {@link ResourceManager} {@code pendingItems} collection. + *
  • the {@code Item} has a pointer to the {@code resource} as well + *
  • the {@code Item} has a pointer to the {@code finalizer} function + *
+ * + * Once all this braided chunk of objects is eligible for GC because nobody holds pointer to + * {@link ManagedResource}, the {@code Item} is put into {@link ResourceManager} {@code + * referenceQueue} and process by the intricate machinery of {@link ResourceManager} and its {@code + * ProcessItems} processor. + */ @ExportLibrary(InteropLibrary.class) @ExportLibrary(TypesLibrary.class) @Builtin(pkg = "resource", stdlibName = "Standard.Base.Runtime.Managed_Resource.Managed_Resource") @@ -97,8 +118,16 @@ Type getType(@Bind("$node") Node node) { @ExportMessage @TruffleBoundary + public String toDisplayString(boolean allowSideEffects, @Bind("$node") Node node) { + var type = getType(node); + return type.getName() + + " " + + InteropLibrary.getUncached().toDisplayString(resource, allowSideEffects); + } + + @ExportMessage.Ignore @Override - public String toDisplayString(boolean allowSideEffects) { - return resource.toString(); + public Object toDisplayString(boolean allowSideEffects) { + throw CompilerDirectives.shouldNotReachHere(); } } diff --git a/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso b/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso index 14f3cd6730cc..69ddd4252397 100644 --- a/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso +++ b/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso @@ -2,6 +2,7 @@ from Standard.Base import all import Standard.Base.Data.Vector.Builder import Standard.Base.Errors.Illegal_State.Illegal_State import Standard.Base.Runtime.Managed_Resource.Managed_Resource +import Standard.Base.Runtime.Ref.Ref import project.Runtime.GC_Example from Standard.Test import all @@ -68,6 +69,58 @@ add_specs suite_builder = suite_builder.group "Managed_Resource" group_builder-> if messages.last != "OK" then Test.fail (messages.join '\n') + group_builder.specify "register_with_finalize" <| + messages = Vector.build builder-> + builder.append "" + + create_resource value = + # registers new resource + Managed_Resource.register (Ref.new value) v-> + v.put -1 + builder.append " finalizing:"+v.to_text + + mr = create_resource 42 + + builder.append "Allocated: "+mr.to_text + + # operates with its value + out = mr.with v-> + builder.append " with :"+v.to_text + v.put 7 + v.modify n-> + builder.append " modify:"+n.to_text + 6 + v + builder.append "With finished:"+out.to_text + + # finalizes the resource + mr.finalize + builder.append "Finalized:"+mr.to_text + + # operation on finalized resource + none = mr.with v-> + # should never be called + builder.append " empty :"+v.to_text + "Don't call me!" + + builder.append none.to_text + none.is_nothing . should_be_true + + exp_text = """ + + Allocated: Managed_Resource 42 + with :42 + modify:7 + With finished:6 + finalizing:-1 + Finalized:Managed_Resource -1 + Nothing + + msg_text = messages.join '\n' + + if msg_text != exp_text then + Test.fail (msg_text) + main filter=Nothing = suite = Test.build suite_builder-> add_specs suite_builder From 5f176e7a2fcf2208f31c1ac6cfadd37dd9b6c436 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Wed, 27 Nov 2024 11:37:14 -0500 Subject: [PATCH 20/50] fmt --- .../org/enso/base/cache/ReloadDetector.java | 61 +++++++++---------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java index e86909fffb3d..fdafc1ca3bee 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -6,38 +6,35 @@ /** * Detects that the reload button has been pressed. * - * .hasReloadOccurred() returns true if the reload button was pressed since the - * last call to .hasReloadOccurred(). - * - * This uses a weak reference (created in eval'd Enso code) that is set to null - * on reload. + *

.hasReloadOccurred() returns true if the reload button was pressed since the last call to + * .hasReloadOccurred(). * + *

This uses a weak reference (created in eval'd Enso code) that is set to null on reload. */ - public class ReloadDetector { - // Weak reference that is set to null on reload. - private Value triggerRef; + // Weak reference that is set to null on reload. + private Value triggerRef; - public ReloadDetector() { - resetTriggerRef(); - } + public ReloadDetector() { + resetTriggerRef(); + } - public boolean hasReloadOccurred() { - var reloadHasOccurred = triggerRef.invokeMember("get").isNull(); - if (reloadHasOccurred) { - resetTriggerRef(); - } - return reloadHasOccurred; + public boolean hasReloadOccurred() { + var reloadHasOccurred = triggerRef.invokeMember("get").isNull(); + if (reloadHasOccurred) { + resetTriggerRef(); } - - private void resetTriggerRef() { - // The `0` value stored in the reference is not used; it just has to - // something other than null. - var module = - Context.getCurrent() - .eval( - "enso", - """ + return reloadHasOccurred; + } + + private void resetTriggerRef() { + // The `0` value stored in the reference is not used; it just has to + // something other than null. + var module = + Context.getCurrent() + .eval( + "enso", + """ import Standard.Base.Runtime.Ref.Ref import Standard.Base.Data.Boolean.Boolean import Standard.Base.Nothing.Nothing @@ -53,10 +50,10 @@ private void resetTriggerRef() { clear self = self.ref.put Nothing """); - triggerRef = module.invokeMember("eval_expression", "Trigger.new"); - } + triggerRef = module.invokeMember("eval_expression", "Trigger.new"); + } - void simulateReloadTestOnly() { - triggerRef.invokeMember("clear"); - } -} \ No newline at end of file + void simulateReloadTestOnly() { + triggerRef.invokeMember("clear"); + } +} From 85e8b6d9e19c78c685f1928c4ce93c5f6d63a25e Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 27 Nov 2024 17:37:46 +0100 Subject: [PATCH 21/50] Can no longer invoke Managed_Resource.with when Managed_Resource.finalize was called --- .../src/Runtime/Managed_Resource.enso | 6 ++- .../expression/builtin/resource/WithNode.java | 18 ++++--- .../interpreter/runtime/ResourceManager.java | 1 + .../runtime/data/ManagedResource.java | 35 ++++++++++-- .../src/Runtime/Managed_Resource_Spec.enso | 53 +++++++++++++++++++ 5 files changed, 102 insertions(+), 11 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso index 35701ebfdc74..9312b0a121b6 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso @@ -58,11 +58,15 @@ type Managed_Resource ADVANCED Executes the provided action on the resource managed by the managed - resource object. + resource object. The action is only invoked if the managed resource + has not yet been finalized. Arguments: - action: The action that will be applied to the resource managed by resource. + Returns: + Value returned from the `action` or `Nothing` if the managed resource + was already finalized with : (Any -> Any) -> Any with self ~action = @Builtin_Method "Managed_Resource.with" diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java index 2dd940690f4c..1f61fab30186 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java @@ -6,7 +6,6 @@ import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.runtime.EnsoContext; -import org.enso.interpreter.runtime.ResourceManager; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.data.ManagedResource; import org.enso.interpreter.runtime.state.State; @@ -33,12 +32,17 @@ static WithNode build() { @Specialization Object doWith(State state, VirtualFrame frame, ManagedResource self, Object action) { - ResourceManager resourceManager = EnsoContext.get(this).getResourceManager(); - resourceManager.park(self); - try { - return invokeCallableNode.execute(action, frame, state, new Object[] {self.getResource()}); - } finally { - resourceManager.unpark(self); + var ctx = EnsoContext.get(this); + var resourceManager = ctx.getResourceManager(); + if (self.getPhantomReference().refersTo(self)) { + resourceManager.park(self); + try { + return invokeCallableNode.execute(action, frame, state, new Object[] {self.getResource()}); + } finally { + resourceManager.unpark(self); + } + } else { + return ctx.getBuiltins().nothing(); } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java index b8295c6b9457..78c8bb26937f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java @@ -408,6 +408,7 @@ private Item( @CompilerDirectives.TruffleBoundary private void finalizeNow(EnsoContext context) { try { + clear(); InteropLibrary.getUncached(finalizer).execute(finalizer, underlying); } catch (Exception e) { context.getErr().println("Exception in finalizer: " + e.getMessage()); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java index 424eac477f3c..826e5554e788 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java @@ -1,5 +1,6 @@ package org.enso.interpreter.runtime.data; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.interop.InteropLibrary; @@ -12,7 +13,27 @@ import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; -/** A runtime representation of a managed resource. */ +/** + * An Enso runtime representation of a managed resource. + * + *

Instance of this class is convoluted with instances playing various roles: + * + *

    + *
  • this {@link ManagedResource} points to {@code resource} + *
  • this {@link ManagedResource} points to {@link PhantomReference} that is "phantom + * referencing" {@code this} + *
  • the implementation of {@link PhantomReference} is {@code Item} in {@link ResourceManager} + *
  • the {@code Item} "phantom referencing" {@code this} {@link ManagedResource} is + * stored in {@link ResourceManager} {@code pendingItems} collection. + *
  • the {@code Item} has a pointer to the {@code resource} as well + *
  • the {@code Item} has a pointer to the {@code finalizer} function + *
+ * + * Once all this braided chunk of objects is eligible for GC because nobody holds pointer to + * {@link ManagedResource}, the {@code Item} is put into {@link ResourceManager} {@code + * referenceQueue} and process by the intricate machinery of {@link ResourceManager} and its {@code + * ProcessItems} processor. + */ @ExportLibrary(InteropLibrary.class) @ExportLibrary(TypesLibrary.class) @Builtin(pkg = "resource", stdlibName = "Standard.Base.Runtime.Managed_Resource.Managed_Resource") @@ -97,8 +118,16 @@ Type getType(@Bind("$node") Node node) { @ExportMessage @TruffleBoundary + public String toDisplayString(boolean allowSideEffects, @Bind("$node") Node node) { + var type = getType(node); + return type.getName() + + " " + + InteropLibrary.getUncached().toDisplayString(resource, allowSideEffects); + } + + @ExportMessage.Ignore @Override - public String toDisplayString(boolean allowSideEffects) { - return resource.toString(); + public Object toDisplayString(boolean allowSideEffects) { + throw CompilerDirectives.shouldNotReachHere(); } } diff --git a/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso b/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso index 14f3cd6730cc..69ddd4252397 100644 --- a/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso +++ b/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso @@ -2,6 +2,7 @@ from Standard.Base import all import Standard.Base.Data.Vector.Builder import Standard.Base.Errors.Illegal_State.Illegal_State import Standard.Base.Runtime.Managed_Resource.Managed_Resource +import Standard.Base.Runtime.Ref.Ref import project.Runtime.GC_Example from Standard.Test import all @@ -68,6 +69,58 @@ add_specs suite_builder = suite_builder.group "Managed_Resource" group_builder-> if messages.last != "OK" then Test.fail (messages.join '\n') + group_builder.specify "register_with_finalize" <| + messages = Vector.build builder-> + builder.append "" + + create_resource value = + # registers new resource + Managed_Resource.register (Ref.new value) v-> + v.put -1 + builder.append " finalizing:"+v.to_text + + mr = create_resource 42 + + builder.append "Allocated: "+mr.to_text + + # operates with its value + out = mr.with v-> + builder.append " with :"+v.to_text + v.put 7 + v.modify n-> + builder.append " modify:"+n.to_text + 6 + v + builder.append "With finished:"+out.to_text + + # finalizes the resource + mr.finalize + builder.append "Finalized:"+mr.to_text + + # operation on finalized resource + none = mr.with v-> + # should never be called + builder.append " empty :"+v.to_text + "Don't call me!" + + builder.append none.to_text + none.is_nothing . should_be_true + + exp_text = """ + + Allocated: Managed_Resource 42 + with :42 + modify:7 + With finished:6 + finalizing:-1 + Finalized:Managed_Resource -1 + Nothing + + msg_text = messages.join '\n' + + if msg_text != exp_text then + Test.fail (msg_text) + main filter=Nothing = suite = Test.build suite_builder-> add_specs suite_builder From 46ef5fb503f3964fcae3fce55ae195f271e22054 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Wed, 27 Nov 2024 11:47:46 -0500 Subject: [PATCH 22/50] revert --- .../src/Runtime/Managed_Resource.enso | 6 +-- .../expression/builtin/resource/WithNode.java | 18 +++---- .../interpreter/runtime/ResourceManager.java | 1 - .../runtime/data/ManagedResource.java | 35 ++---------- .../src/Runtime/Managed_Resource_Spec.enso | 53 ------------------- 5 files changed, 11 insertions(+), 102 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso index 9312b0a121b6..35701ebfdc74 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso @@ -58,15 +58,11 @@ type Managed_Resource ADVANCED Executes the provided action on the resource managed by the managed - resource object. The action is only invoked if the managed resource - has not yet been finalized. + resource object. Arguments: - action: The action that will be applied to the resource managed by resource. - Returns: - Value returned from the `action` or `Nothing` if the managed resource - was already finalized with : (Any -> Any) -> Any with self ~action = @Builtin_Method "Managed_Resource.with" diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java index 1f61fab30186..2dd940690f4c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java @@ -6,6 +6,7 @@ import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.node.callable.InvokeCallableNode; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.ResourceManager; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.data.ManagedResource; import org.enso.interpreter.runtime.state.State; @@ -32,17 +33,12 @@ static WithNode build() { @Specialization Object doWith(State state, VirtualFrame frame, ManagedResource self, Object action) { - var ctx = EnsoContext.get(this); - var resourceManager = ctx.getResourceManager(); - if (self.getPhantomReference().refersTo(self)) { - resourceManager.park(self); - try { - return invokeCallableNode.execute(action, frame, state, new Object[] {self.getResource()}); - } finally { - resourceManager.unpark(self); - } - } else { - return ctx.getBuiltins().nothing(); + ResourceManager resourceManager = EnsoContext.get(this).getResourceManager(); + resourceManager.park(self); + try { + return invokeCallableNode.execute(action, frame, state, new Object[] {self.getResource()}); + } finally { + resourceManager.unpark(self); } } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java index 78c8bb26937f..b8295c6b9457 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java @@ -408,7 +408,6 @@ private Item( @CompilerDirectives.TruffleBoundary private void finalizeNow(EnsoContext context) { try { - clear(); InteropLibrary.getUncached(finalizer).execute(finalizer, underlying); } catch (Exception e) { context.getErr().println("Exception in finalizer: " + e.getMessage()); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java index 826e5554e788..424eac477f3c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java @@ -1,6 +1,5 @@ package org.enso.interpreter.runtime.data; -import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.interop.InteropLibrary; @@ -13,27 +12,7 @@ import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; -/** - * An Enso runtime representation of a managed resource. - * - *

Instance of this class is convoluted with instances playing various roles: - * - *

    - *
  • this {@link ManagedResource} points to {@code resource} - *
  • this {@link ManagedResource} points to {@link PhantomReference} that is "phantom - * referencing" {@code this} - *
  • the implementation of {@link PhantomReference} is {@code Item} in {@link ResourceManager} - *
  • the {@code Item} "phantom referencing" {@code this} {@link ManagedResource} is - * stored in {@link ResourceManager} {@code pendingItems} collection. - *
  • the {@code Item} has a pointer to the {@code resource} as well - *
  • the {@code Item} has a pointer to the {@code finalizer} function - *
- * - * Once all this braided chunk of objects is eligible for GC because nobody holds pointer to - * {@link ManagedResource}, the {@code Item} is put into {@link ResourceManager} {@code - * referenceQueue} and process by the intricate machinery of {@link ResourceManager} and its {@code - * ProcessItems} processor. - */ +/** A runtime representation of a managed resource. */ @ExportLibrary(InteropLibrary.class) @ExportLibrary(TypesLibrary.class) @Builtin(pkg = "resource", stdlibName = "Standard.Base.Runtime.Managed_Resource.Managed_Resource") @@ -118,16 +97,8 @@ Type getType(@Bind("$node") Node node) { @ExportMessage @TruffleBoundary - public String toDisplayString(boolean allowSideEffects, @Bind("$node") Node node) { - var type = getType(node); - return type.getName() - + " " - + InteropLibrary.getUncached().toDisplayString(resource, allowSideEffects); - } - - @ExportMessage.Ignore @Override - public Object toDisplayString(boolean allowSideEffects) { - throw CompilerDirectives.shouldNotReachHere(); + public String toDisplayString(boolean allowSideEffects) { + return resource.toString(); } } diff --git a/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso b/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso index 69ddd4252397..14f3cd6730cc 100644 --- a/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso +++ b/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso @@ -2,7 +2,6 @@ from Standard.Base import all import Standard.Base.Data.Vector.Builder import Standard.Base.Errors.Illegal_State.Illegal_State import Standard.Base.Runtime.Managed_Resource.Managed_Resource -import Standard.Base.Runtime.Ref.Ref import project.Runtime.GC_Example from Standard.Test import all @@ -69,58 +68,6 @@ add_specs suite_builder = suite_builder.group "Managed_Resource" group_builder-> if messages.last != "OK" then Test.fail (messages.join '\n') - group_builder.specify "register_with_finalize" <| - messages = Vector.build builder-> - builder.append "" - - create_resource value = - # registers new resource - Managed_Resource.register (Ref.new value) v-> - v.put -1 - builder.append " finalizing:"+v.to_text - - mr = create_resource 42 - - builder.append "Allocated: "+mr.to_text - - # operates with its value - out = mr.with v-> - builder.append " with :"+v.to_text - v.put 7 - v.modify n-> - builder.append " modify:"+n.to_text - 6 - v - builder.append "With finished:"+out.to_text - - # finalizes the resource - mr.finalize - builder.append "Finalized:"+mr.to_text - - # operation on finalized resource - none = mr.with v-> - # should never be called - builder.append " empty :"+v.to_text - "Don't call me!" - - builder.append none.to_text - none.is_nothing . should_be_true - - exp_text = """ - - Allocated: Managed_Resource 42 - with :42 - modify:7 - With finished:6 - finalizing:-1 - Finalized:Managed_Resource -1 - Nothing - - msg_text = messages.join '\n' - - if msg_text != exp_text then - Test.fail (msg_text) - main filter=Nothing = suite = Test.build suite_builder-> add_specs suite_builder From e56b86703be389f78e0f05d092ef4c276f5900ef Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 28 Nov 2024 07:46:11 +0100 Subject: [PATCH 23/50] Backing out the Ref changes --- .../Base/0.0.0-dev/src/Runtime/Ref.enso | 18 +----- .../org/enso/interpreter/runtime/RefTest.java | 4 +- .../enso/interpreter/runtime/EnsoContext.java | 9 --- .../runtime/ReferencesManager.java | 62 ------------------- .../enso/interpreter/runtime/data/Ref.java | 45 +++----------- .../Base/0.0.0-dev/src/Runtime/Ref.enso | 4 +- 6 files changed, 13 insertions(+), 129 deletions(-) delete mode 100644 engine/runtime/src/main/java/org/enso/interpreter/runtime/ReferencesManager.java diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso index ef266204ae43..8e8edcde1255 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso @@ -1,5 +1,4 @@ import project.Any.Any -import project.Data.Boolean.Boolean ## PRIVATE ADVANCED @@ -9,27 +8,16 @@ type Ref ## PRIVATE ADVANCED Creates a new reference containing the provided value. - If `allow_gc` is set to `True` than the management - of the reference is controlled by the runtime which - may _set the reference to `Nothing` any time_. The - definition of _"any time"_ is intentionally vague, but - the known implementations reset the value when running - out of memory or when a user asks for _"clean re-execution"_ - of a project. Arguments: - value: The value to be contained in the ref. - - allow_gc: Is the value eligible for being cleared by the system at any time? > Example Creating a new reference containing the value 7. - > Example - Cache a value, but allow the system to reclaim it. - Ref.new huge_data_downloaded_from_internet True - new value:Any allow_gc:Boolean=Boolean.False -> Ref = Ref.new_impl value (if allow_gc then 2 else 0) - - private new_impl value typ = @Builtin_Method "Ref.new" + Ref.new 7 + new : Any -> Ref + new value = @Builtin_Method "Ref.new" ## GROUP Metadata ICON metadata diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java index 96c566b52dfd..f7efa9cc3dda 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java @@ -70,7 +70,7 @@ public void regularReference() throws Exception { assertFalse("Value was not GCed", getRef.execute(ref).isNull()); assertEquals("We get the object", weakRef.get(), getRef.execute(ref).asHostObject()); - ensoCtx.getReferencesManager().releaseAll(); +// ensoCtx.getReferencesManager().releaseAll(); assertEquals( "releaseAll has no effect on regular reference", weakRef.get(), @@ -102,7 +102,7 @@ public void explicitlyReclaimableReference() throws Exception { assertEquals("Standard.Base.Runtime.Ref.Ref", ref.getMetaObject().getMetaQualifiedName()); assertEquals("We get the object", obj, getRef.execute(ref).asHostObject()); - ensoCtx.getReferencesManager().releaseAll(); + // ensoCtx.getReferencesManager().releaseAll(); assertTrue("Value was GCed", getRef.execute(ref).isNull()); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java index 58bceb834447..531a116cc317 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java @@ -96,7 +96,6 @@ public final class EnsoContext { private final ThreadManager threadManager; private final ThreadExecutors threadExecutors; private final ResourceManager resourceManager; - private final ReferencesManager referencesManager; private final boolean isInlineCachingDisabled; private final boolean isIrCachingDisabled; private final boolean shouldWaitForPendingSerializationJobs; @@ -140,7 +139,6 @@ public EnsoContext( this.threadManager = new ThreadManager(environment); this.threadExecutors = new ThreadExecutors(this); this.resourceManager = new ResourceManager(this); - this.referencesManager = new ReferencesManager(this); this.isInlineCachingDisabled = getOption(RuntimeOptions.DISABLE_INLINE_CACHES_KEY); var isParallelismEnabled = getOption(RuntimeOptions.ENABLE_AUTO_PARALLELISM_KEY); this.isIrCachingDisabled = @@ -803,13 +801,6 @@ public ResourceManager getResourceManager() { return resourceManager; } - /** - * @return the references manager for this context - */ - public ReferencesManager getReferencesManager() { - return referencesManager; - } - /** * @return whether inline caches should be disabled for this context. */ diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ReferencesManager.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ReferencesManager.java deleted file mode 100644 index 397253a71a68..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ReferencesManager.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.enso.interpreter.runtime; - -import com.oracle.truffle.api.CompilerDirectives; -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.SoftReference; -import java.lang.ref.WeakReference; -import java.util.Collection; -import java.util.concurrent.ConcurrentLinkedQueue; - -/** Tracks soft and weak references and allow their cleanup. */ -public final class ReferencesManager { - private final EnsoContext ctx; - private final Collection refs = new ConcurrentLinkedQueue<>(); - private final ReferenceQueue queue = new ReferenceQueue<>(); - - ReferencesManager(EnsoContext ctx) { - this.ctx = ctx; - } - - /** - * Creates new reference to provided object and registers it in the manager. - * - * @param class of the object to reference - * @param obj the object to reference - * @param type ({@code 1} use {@link SoftReference} or {@code 2} to use {@link WeakReference} - * @return newly created reference to the provided object - */ - @CompilerDirectives.TruffleBoundary - public Reference create(T obj, int type) { - clearPendingReferences(); - var r = - switch (type) { - case 1 -> new SoftReference<>(obj, queue); - case 2 -> new WeakReference<>(obj, queue); - default -> throw new IllegalStateException(); - }; - refs.add(r); - return r; - } - - /** Releases all the references. E.g. cleans all the cached values. */ - @CompilerDirectives.TruffleBoundary - public void releaseAll() { - var arr = refs.toArray(Reference[]::new); - for (var r : arr) { - r.clear(); - refs.remove(r); - } - } - - @CompilerDirectives.TruffleBoundary - private void clearPendingReferences() { - for (; ; ) { - var r = queue.poll(); - if (r == null) { - break; - } - refs.remove(r); - } - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java index 878162d91c31..e9e1728cacc0 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Ref.java @@ -7,9 +7,6 @@ import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.Node; -import java.lang.ref.Reference; -import java.lang.ref.SoftReference; -import java.lang.ref.WeakReference; import org.enso.interpreter.dsl.Builtin; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; @@ -19,25 +16,16 @@ @ExportLibrary(TypesLibrary.class) @Builtin(pkg = "mutable", stdlibName = "Standard.Base.Runtime.Ref.Ref") public final class Ref extends EnsoObject { - /** - * {@code 0} - regular reference to an object {@code 1} - reference via {@link SoftReference} - * {@code 2} - reference via {@link WeakReference} - */ - private final byte type; - private volatile Object value; /** * Creates a new reference. * * @param value the initial value to store in the reference. - * @param referenceType type of reference to use */ @Builtin.Method(description = "Creates a new Ref", autoRegister = false) - public Ref(Node node, Object value, long referenceType) { - var ctx = EnsoContext.get(node); - this.type = (byte) (referenceType & 0x03); - this.value = wrapValue(ctx, value); + public Ref(Object value) { + this.value = value; } /** @@ -45,9 +33,8 @@ public Ref(Node node, Object value, long referenceType) { */ @Builtin.Method(name = "get", description = "Gets the value stored in the reference") @SuppressWarnings("generic-enso-builtin-type") - public Object getValue(Node node) { - var ctx = EnsoContext.get(node); - return unwrapValue(ctx, value); + public Object getValue() { + return value; } /** @@ -58,11 +45,10 @@ public Object getValue(Node node) { */ @Builtin.Method(name = "put", description = "Stores a new value in the reference") @SuppressWarnings("generic-enso-builtin-type") - public Object setValue(Node node, Object value) { - var ctx = EnsoContext.get(node); + public Object setValue(Object value) { Object old = this.value; - this.value = wrapValue(ctx, value); - return unwrapValue(ctx, old); + this.value = value; + return old; } @ExportMessage @@ -85,23 +71,6 @@ Type getType(@Bind("$node") Node node) { return EnsoContext.get(node).getBuiltins().ref(); } - private final Object wrapValue(EnsoContext ctx, Object v) { - if (type == 0) { - return v; - } - assert !(v instanceof Reference) : "Ref[" + type + ", " + v + "]"; - return ctx.getReferencesManager().create(v, type); - } - - private final Object unwrapValue(EnsoContext ctx, Object v) { - if (v instanceof Reference ref) { - var ret = ref.get(); - return ret == null ? ctx.getNothing() : ret; - } else { - return v; - } - } - @ExportMessage Object toDisplayString( boolean allowSideEffects, @CachedLibrary(limit = "3") InteropLibrary interop) { diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso index 1f78498a8319..82c679a4f92d 100644 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso @@ -1,6 +1,4 @@ @Builtin_Type type Ref -new value allow_gc=0 = new_impl value allow_gc - -private new_impl value allow_gc = @Builtin_Method "Ref.new" +new value = @Builtin_Method "Ref.new" From da2d4388a7a97b274f3130e12b53e2a49f03c214 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 28 Nov 2024 08:02:29 +0100 Subject: [PATCH 24/50] Ref.new takes only one argument (again) --- .../main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java index 9bcd1b97f98d..fee3f1f5b85e 100644 --- a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java +++ b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java @@ -194,7 +194,7 @@ private static EnsoHTTPResponseCache getOrCreateCache() { private Value ref:Ref new obj -> Cache = - ref = Ref.new obj Boolean.True + ref = Ref.new obj Cache.Value ref get self = self.ref.get From ae8300844ccedeae244c156a2aa022d16193fef5 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 28 Nov 2024 08:02:50 +0100 Subject: [PATCH 25/50] Commenting out releaseAll call for now --- .../src/test/java/org/enso/interpreter/runtime/RefTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java index f7efa9cc3dda..44edbc24f93a 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java @@ -70,7 +70,7 @@ public void regularReference() throws Exception { assertFalse("Value was not GCed", getRef.execute(ref).isNull()); assertEquals("We get the object", weakRef.get(), getRef.execute(ref).asHostObject()); -// ensoCtx.getReferencesManager().releaseAll(); + // ensoCtx.getReferencesManager().releaseAll(); assertEquals( "releaseAll has no effect on regular reference", weakRef.get(), From 276a8854046611dff3c4d127fa92c7cd0056caa6 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 28 Nov 2024 09:52:47 +0100 Subject: [PATCH 26/50] Allow system controlled Managed_Resource --- .../src/Runtime/Managed_Resource.enso | 17 ++++++-- .../interpreter/runtime/ResourceManager.java | 40 ++++++++++++++++++- .../runtime/data/ManagedResource.java | 5 ++- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso index 9312b0a121b6..b243b00aac80 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso @@ -2,6 +2,7 @@ import project.Any.Any import project.Nothing.Nothing +import project.Data.Boolean.Boolean ## Resource provides an API for manual management of computation resources. @@ -34,17 +35,25 @@ type Managed_Resource ADVANCED Registers a resource with the resource manager to be cleaned up using - function once it is no longer in use. + function once it is no longer in use. The optional `system_finalization_allowed` + flag allow the system to explicitly call `finalize` on the resource + when _"needed"_. The definition is intentionally vague, but + currently the IDE performs such a call when user requests a _"reload"_ - + e.g. using `Managed_Resource.register cache cleanup_fn True` is useful + for creating user managed caches. Arguments: - resource: The resource to register. - function: The action to be executed on resource to clean it up when it is no longer in use. + - system_finalization_allowed: is the system allowed to call `finalize` + on the resource when "needed" Returns: A `Managed_Resource` object that can be used to access the resource. - register : Any -> (Any -> Nothing) -> Managed_Resource - register resource function = @Builtin_Method "Managed_Resource.register" + register : Any -> (Any -> Nothing) -> Boolean -> Managed_Resource + register resource function system_finalization_allowed=Boolean.False = + @Tail_Call register_builtin resource function system_finalization_allowed ## PRIVATE ADVANCED @@ -78,3 +87,5 @@ type Managed_Resource managed resources system. take : Any take self = @Builtin_Method "Managed_Resource.take" + +register_builtin r fn sys:Boolean = @Builtin_Method "Managed_Resource.register_builtin" diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java index 78c8bb26937f..933a4506746d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java @@ -112,22 +112,40 @@ public void take(ManagedResource resource) { removeFromItems(resource.getPhantomReference()); } + /** + * Registers a new (non-system controlled) resource to the system. Calls {@link + * #register(java.lang.Object, java.lang.Object, boolean)} with {@code false} as last argument. + * + * @param object the underlying resource + * @param function the finalizer action to call on the underlying resource + * @return a wrapper object, containing the resource and serving as a reachability probe + */ + @CompilerDirectives.TruffleBoundary + public ManagedResource register(Object object, Object function) { + return register(object, function, false); + } + /** * Registers a new resource to the system. {@code function} will be called on {@code object} when * the value returned by this method becomes unreachable. * * @param object the underlying resource * @param function the finalizer action to call on the underlying resource + * @param systemResource resource is subject to finalization when {@link #scheduleFinalization} is + * called * @return a wrapper object, containing the resource and serving as a reachability probe */ @CompilerDirectives.TruffleBoundary - public synchronized ManagedResource register(Object object, Object function) { + public synchronized ManagedResource register( + Object object, Object function, boolean systemResource) { if (CLOSED == processor) { throw EnsoContext.get(null) .raiseAssertionPanic( null, "Can't register new resources after resource manager is closed.", null); } - var resource = new ManagedResource(object, r -> new Item(r, object, function, referenceQueue)); + var resource = + new ManagedResource( + object, r -> new Item(r, object, function, systemResource, referenceQueue)); var ref = (Item) resource.getPhantomReference(); addPendingItem(ref); return resource; @@ -186,6 +204,21 @@ private synchronized void removeFromItems(PhantomReference it) } } + /** + * Explicitly schedules all the system references registered with the manager for + * finalization. + * + * @see #register + */ + @CompilerDirectives.TruffleBoundary + public final synchronized void scheduleFinalization() { + for (var item : pendingItems) { + if (item.systemResource) { + item.enqueue(); + } + } + } + /** * Awaits next item in the queue, if any. * @@ -360,6 +393,7 @@ Collection awaitShutdown() { /** A storage representation of a finalizable object handled by this system. */ private static final class Item extends PhantomReference { + private final boolean systemResource; private final Object underlying; private final Object finalizer; @@ -393,10 +427,12 @@ private Item( ManagedResource referent, Object underlying, Object finalizer, + boolean systemResource, ReferenceQueue queue) { super(referent, queue); this.underlying = underlying; this.finalizer = finalizer; + this.systemResource = systemResource; } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java index 826e5554e788..e931f8bf01f7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java @@ -73,8 +73,9 @@ public PhantomReference getPhantomReference() { "Makes an object into a managed resource, automatically finalized when the returned" + " object is garbage collected.") @Builtin.Specialize - public static ManagedResource register(EnsoContext context, Object resource, Function function) { - return context.getResourceManager().register(resource, function); + public static ManagedResource register_builtin( + EnsoContext context, Object resource, Function function, boolean systemCanFinalize) { + return context.getResourceManager().register(resource, function, systemCanFinalize); } @Builtin.Method( From 40aedfc53d9b51cb7e34cb4d9fe8b0728a59e9d8 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 28 Nov 2024 16:51:46 +0100 Subject: [PATCH 27/50] on_missing behavior for managed resources that get access after being finalized --- .../src/Runtime/Managed_Resource.enso | 7 ++++-- .../0.0.0-dev/src/System/Input_Stream.enso | 9 ++++--- .../expression/builtin/resource/WithNode.java | 24 +++++++++---------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso index b243b00aac80..cc7a010ce406 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso @@ -73,11 +73,13 @@ type Managed_Resource Arguments: - action: The action that will be applied to the resource managed by resource. + - on_missing: Optional action to perform when the value is no longer + reachable Returns: Value returned from the `action` or `Nothing` if the managed resource was already finalized - with : (Any -> Any) -> Any - with self ~action = @Builtin_Method "Managed_Resource.with" + with : (Any -> Any) -> Any -> Any + with self ~action ~on_missing=Nothing = with_builtin self action on_missing... ## PRIVATE ADVANCED @@ -89,3 +91,4 @@ type Managed_Resource take self = @Builtin_Method "Managed_Resource.take" register_builtin r fn sys:Boolean = @Builtin_Method "Managed_Resource.register_builtin" +with_builtin r fn ~on_missing = @Builtin_Method "Managed_Resource.with_builtin" diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/System/Input_Stream.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/System/Input_Stream.enso index fb636a3873e3..e390603ec9a5 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/System/Input_Stream.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/System/Input_Stream.enso @@ -4,6 +4,7 @@ import project.Data.Numbers.Integer import project.Data.Text.Encoding.Encoding import project.Data.Vector.Vector import project.Error.Error +import project.Panic.Panic import project.Errors.File_Error.File_Error import project.Errors.Illegal_State.Illegal_State import project.Errors.Problem_Behavior.Problem_Behavior @@ -17,6 +18,7 @@ import project.System.File.Generic.Writable_File.Writable_File import project.System.Internal.Reporting_Stream_Decoder_Helper from project.Data.Boolean import Boolean, False, True +polyglot java import java.io.IOException polyglot java import java.io.BufferedInputStream polyglot java import java.io.ByteArrayInputStream polyglot java import java.io.InputStream as Java_Input_Stream @@ -114,9 +116,10 @@ type Input_Stream Arguments: - f: Applies a function over the internal java stream. with_java_stream : (Java_Input_Stream -> Any) -> Any - with_java_stream self f = self.stream_resource . with java_like_stream-> - java_stream = Stream_Utils.asInputStream java_like_stream - self.error_handler <| f java_stream + with_java_stream self f = + self.stream_resource . with on_missing=(Panic.throw <| IOException.new "Stream closed") java_like_stream-> + java_stream = Stream_Utils.asInputStream java_like_stream + self.error_handler <| f java_stream ## PRIVATE Runs an action with a `ReportingStreamDecoder` decoding data from the diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java index 1f61fab30186..b293199d3cd1 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/resource/WithNode.java @@ -1,6 +1,5 @@ package org.enso.interpreter.node.expression.builtin.resource; -import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; import org.enso.interpreter.dsl.BuiltinMethod; @@ -12,11 +11,12 @@ @BuiltinMethod( type = "Managed_Resource", - name = "with", + name = "with_builtin", description = "Applies the passed action to the underlying resource managed by the passed" + " Managed_Resource object.") -public abstract class WithNode extends Node { +public final class WithNode extends Node { + private WithNode() {} private @Child InvokeCallableNode invokeCallableNode = InvokeCallableNode.build( @@ -25,24 +25,22 @@ public abstract class WithNode extends Node { InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED); static WithNode build() { - return WithNodeGen.create(); + return new WithNode(); } - abstract Object execute(State state, VirtualFrame frame, Object self, Object action); - - @Specialization - Object doWith(State state, VirtualFrame frame, ManagedResource self, Object action) { + Object execute( + State state, VirtualFrame frame, ManagedResource mr, Object action, Object onMissing) { var ctx = EnsoContext.get(this); var resourceManager = ctx.getResourceManager(); - if (self.getPhantomReference().refersTo(self)) { - resourceManager.park(self); + if (mr.getPhantomReference().refersTo(mr)) { + resourceManager.park(mr); try { - return invokeCallableNode.execute(action, frame, state, new Object[] {self.getResource()}); + return invokeCallableNode.execute(action, frame, state, new Object[] {mr.getResource()}); } finally { - resourceManager.unpark(self); + resourceManager.unpark(mr); } } else { - return ctx.getBuiltins().nothing(); + return onMissing; } } } From 2e911357805ddbda3e1745f0b051e30c6506a343 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 28 Nov 2024 17:19:18 +0100 Subject: [PATCH 28/50] Updating micro-distribution with the new Managed_Resource builtins --- .../lib/Standard/Base/0.0.0-dev/src/Runtime/Resource.enso | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Resource.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Resource.enso index 3cfe8ff98f29..0e1d0ac72ad8 100644 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Resource.enso +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Resource.enso @@ -1,4 +1,5 @@ import project.Any.Any +import project.Data.Boolean.Boolean import project.Nothing.Nothing bracket : Any -> (Any -> Nothing) -> (Any -> Any) -> Any @@ -6,7 +7,10 @@ bracket ~constructor ~destructor ~action = @Builtin_Method "Resource.bracket" @Builtin_Type type Managed_Resource - register resource function = @Builtin_Method "Managed_Resource.register" + register obj fn system=Boolean.False = register_builtin obj fn system finalize self = @Builtin_Method "Managed_Resource.finalize" - with self ~action = @Builtin_Method "Managed_Resource.with" + with self ~action ~on_missing=Nothing = with_builtin self action on_missing take self = @Builtin_Method "Managed_Resource.take" + +register_builtin resource function system = @Builtin_Method "Managed_Resource.register_builtin" +with_builtin r action on_missing = @Builtin_Method "Managed_Resource.with_builtin" \ No newline at end of file From 786f1562b67e145e3a44ff64a6b9b91e2d6ccdad Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 28 Nov 2024 18:14:46 +0100 Subject: [PATCH 29/50] Moving the GC related parts of RefTest to ManagedResourceTest --- .../runtime/ManagedResourceTest.java | 116 ++++++++++++++++++ .../org/enso/interpreter/runtime/RefTest.java | 31 ----- 2 files changed, 116 insertions(+), 31 deletions(-) create mode 100644 engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/ManagedResourceTest.java diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/ManagedResourceTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/ManagedResourceTest.java new file mode 100644 index 000000000000..91b6bf8ca7d5 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/ManagedResourceTest.java @@ -0,0 +1,116 @@ +package org.enso.interpreter.runtime; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import org.enso.common.MethodNames; +import org.enso.test.utils.ContextUtils; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class ManagedResourceTest { + private static Context ctx; + private static EnsoContext ensoCtx; + private static Value newRef; + private static Value createRef; + private static Value getRef; + private static Value finalizeRef; + + @BeforeClass + public static void initCtx() throws Exception { + ctx = ContextUtils.createDefaultContext(); + ensoCtx = ContextUtils.leakContext(ctx); + var code = + """ + import Standard.Base.Runtime.Managed_Resource.Managed_Resource + + new_ref obj = + Managed_Resource.register obj (_->0) + + create_ref obj system_resource = + Managed_Resource.register obj (_->0) system_resource + + get_ref ref = ref.with it-> + it + finalize_ref ref = ref.finalize + """; + var src = Source.newBuilder("enso", code, "gc.enso").build(); + var gcEnso = ctx.eval(src); + newRef = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "new_ref"); + createRef = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create_ref"); + getRef = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "get_ref"); + finalizeRef = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "finalize_ref"); + } + + @AfterClass + public static void closeCtx() throws Exception { + ctx.close(); + ctx = null; + } + + @Test + public void regularReference() throws Exception { + var obj = new Object(); + var ref = newRef.execute(obj); + + assertFalse("Value returned", ref.isNull()); + assertEquals( + "Standard.Base.Runtime.Managed_Resource.Managed_Resource", + ref.getMetaObject().getMetaQualifiedName()); + + var weakRef = new WeakReference<>(obj); + obj = null; + + assertEquals("We get the object", weakRef.get(), getRef.execute(ref).asHostObject()); + + assertGC("Weak wasn't released", false, weakRef); + assertFalse("Value was not GCed", getRef.execute(ref).isNull()); + assertEquals("We get the object", weakRef.get(), getRef.execute(ref).asHostObject()); + + ensoCtx.getResourceManager().scheduleFinalization(); + assertEquals( + "scheduleFinalization has no effect on regular reference", + weakRef.get(), + getRef.execute(ref).asHostObject()); + } + + @Test + public void explicitlyReclaimableReference() throws Exception { + var obj = new Object(); + var ref = createRef.execute(obj, true); + + assertFalse("Value returned", ref.isNull()); + assertEquals( + "Standard.Base.Runtime.Managed_Resource.Managed_Resource", + ref.getMetaObject().getMetaQualifiedName()); + assertEquals("We get the object", obj, getRef.execute(ref).asHostObject()); + + ensoCtx.getResourceManager().scheduleFinalization(); + + assertTrue("Value was GCed", getRef.execute(ref).isNull()); + } + + private static void assertGC(String msg, boolean expectGC, Reference ref) { + for (var i = 1; i < Integer.MAX_VALUE / 2; i *= 2) { + if (ref.get() == null) { + break; + } + System.gc(); + } + var obj = ref.get(); + if (expectGC) { + assertNull(msg + " ref still alive", obj); + } else { + assertNotNull(msg + " ref has been cleaned", obj); + } + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java index 44edbc24f93a..1e0f06682b90 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java @@ -4,7 +4,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import java.lang.ref.Reference; import java.lang.ref.WeakReference; @@ -77,36 +76,6 @@ public void regularReference() throws Exception { getRef.execute(ref).asHostObject()); } - @Test - public void garbageCollectableReference() throws Exception { - var obj = new Object(); - var ref = createRef.execute(obj, true); - - assertFalse("Value returned", ref.isNull()); - assertEquals("Standard.Base.Runtime.Ref.Ref", ref.getMetaObject().getMetaQualifiedName()); - assertEquals("We get the object", obj, getRef.execute(ref).asHostObject()); - - var weakRef = new WeakReference<>(obj); - obj = null; - assertGC("Weak reference can be GCed", true, weakRef); - - assertTrue("Value was GCed", getRef.execute(ref).isNull()); - } - - @Test - public void explicitlyReclaimableReference() throws Exception { - var obj = new Object(); - var ref = createRef.execute(obj, true); - - assertFalse("Value returned", ref.isNull()); - assertEquals("Standard.Base.Runtime.Ref.Ref", ref.getMetaObject().getMetaQualifiedName()); - assertEquals("We get the object", obj, getRef.execute(ref).asHostObject()); - - // ensoCtx.getReferencesManager().releaseAll(); - - assertTrue("Value was GCed", getRef.execute(ref).isNull()); - } - private static void assertGC(String msg, boolean expectGC, Reference ref) { for (var i = 1; i < Integer.MAX_VALUE / 2; i *= 2) { if (ref.get() == null) { From 934ec5f4a0cd9944aa0ff0aa66bc1e8a7c1532a2 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 28 Nov 2024 18:19:51 +0100 Subject: [PATCH 30/50] Better note in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d05c24e64726..ee605064cfee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,7 +75,7 @@ operation.][11490] - [Added `Table.input` allowing creation of typed tables from vectors of data, including auto parsing text columns.][11562] -- [Ref.new with allow_gc][11577] +- [Enhance Managed_Resource to allow implementation of in-memory caches][11577] [11235]: https://github.com/enso-org/enso/pull/11235 [11255]: https://github.com/enso-org/enso/pull/11255 From a3c07e0ffd859044940b0f3197571a77b3985051 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 28 Nov 2024 18:46:53 +0100 Subject: [PATCH 31/50] Making public so the Fetch_Spec passes OK --- .../main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java index fee3f1f5b85e..ce86e013faa3 100644 --- a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java +++ b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java @@ -178,7 +178,7 @@ public EnsoHttpResponse reconstructResponseFromCachedStream( } } - private static EnsoHTTPResponseCache getOrCreateCache() { + public static EnsoHTTPResponseCache getOrCreateCache() { if (getCache() instanceof EnsoHTTPResponseCache httpCache) { return httpCache; } else { From a7890d2f4d881abcd5c94cf15730a594cb792883 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 28 Nov 2024 18:52:25 +0100 Subject: [PATCH 32/50] Using Managed_Resource inside of EnsoSecretHelper --- .../org/enso/base/enso_cloud/EnsoSecretHelper.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java index ce86e013faa3..ff6677f2ff2f 100644 --- a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java +++ b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java @@ -187,17 +187,18 @@ public static EnsoHTTPResponseCache getOrCreateCache() { .eval( "enso", """ - import Standard.Base.Runtime.Ref.Ref + import Standard.Base.Runtime.Managed_Resource.Managed_Resource import Standard.Base.Data.Boolean.Boolean type Cache - private Value ref:Ref + private Value ref:Managed_Resource new obj -> Cache = - ref = Ref.new obj - Cache.Value ref + on_finalize _ = 0 + ref = Managed_Resource.register obj on_finalize Boolean.True + Cache.Value ref - get self = self.ref.get + get self = self.ref.with (r->r) """); var cacheNew = module.invokeMember("eval_expression", "Cache.new"); var httpCache = new EnsoHTTPResponseCache(); From 20b14b5ba4b73ab9c72acca17420d54d4201644a Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Fri, 29 Nov 2024 11:49:34 -0500 Subject: [PATCH 33/50] update to mr-only --- .../org/enso/base/cache/ReloadDetector.java | 37 +++++++++++-------- test/Table_Tests/src/IO/Fetch_Spec.enso | 1 + 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java index fdafc1ca3bee..ad225dd781f2 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -13,47 +13,54 @@ */ public class ReloadDetector { // Weak reference that is set to null on reload. - private Value triggerRef; + private Value trigger; public ReloadDetector() { - resetTriggerRef(); + resetTrigger(); } public boolean hasReloadOccurred() { - var reloadHasOccurred = triggerRef.invokeMember("get").isNull(); + var reloadHasOccurred = trigger.invokeMember("get").isNull(); if (reloadHasOccurred) { - resetTriggerRef(); + resetTrigger(); } return reloadHasOccurred; } - private void resetTriggerRef() { - // The `0` value stored in the reference is not used; it just has to - // something other than null. + private void resetTrigger() { + // The `on_finalize` function and the `clear` method both write `Nothing` to + // the ref. This is a signal that a reload has happenend. `on_finalize` is + // called by the engine when a reload happens. `clear` is only for testing, + // to simulate a reload. + // + // The `0` value stored in the ref is not used; it just has to be something + // other than Nothing. var module = Context.getCurrent() .eval( "enso", """ - import Standard.Base.Runtime.Ref.Ref import Standard.Base.Data.Boolean.Boolean import Standard.Base.Nothing.Nothing + import Standard.Base.Runtime.Managed_Resource.Managed_Resource type Trigger - private Value ref:Ref + private Value mr:Managed_Resource new -> Trigger = - ref = Ref.new 0 Boolean.True - Trigger.Value ref + ref = Ref.new 0 + on_finalize ref = ref.put Nothing + mr = Managed_Resource.register ref on_finalize Boolean.True + Trigger.Value mr - get self = self.ref.get + get self = self.mr.with .get - clear self = self.ref.put Nothing + clear self = self.mr.with (ref-> ref.put Nothing) """); - triggerRef = module.invokeMember("eval_expression", "Trigger.new"); + trigger = module.invokeMember("eval_expression", "Trigger.new"); } void simulateReloadTestOnly() { - triggerRef.invokeMember("clear"); + trigger.invokeMember("clear"); } } diff --git a/test/Table_Tests/src/IO/Fetch_Spec.enso b/test/Table_Tests/src/IO/Fetch_Spec.enso index 030210d72c92..0d49c7b454ac 100644 --- a/test/Table_Tests/src/IO/Fetch_Spec.enso +++ b/test/Table_Tests/src/IO/Fetch_Spec.enso @@ -540,6 +540,7 @@ add_specs suite_builder = get_num_response_cache_entries . should_equal 3 # Cleaning is not triggered until the next request HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=16' get_num_response_cache_entries . should_equal 1 + group_builder.specify "Reissues the request if the cache file disappears" pending=pending_has_url <| Test.with_retries <| with_default_cache <| url = base_url_with_slash+'test_download?max-age=16&length=10' From c9ee8857f03c67f0d918912ead8ab41a007f7cf8 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Fri, 29 Nov 2024 12:16:29 -0500 Subject: [PATCH 34/50] schedule finalization --- .../interpreter/instrument/command/RecomputeContextCmd.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala index dba5e325edc7..6cda02ff2edd 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala @@ -43,7 +43,7 @@ class RecomputeContextCmd( ec: ExecutionContext ): Future[Boolean] = { Future { - EnsoContext.get(null).getReferencesManager().releaseAll() + EnsoContext.get(null).getResourceManager().scheduleFinalization(); ctx.jobControlPlane.abortJobs( request.contextId, "recompute context", From 00ccacab8c11daadadd5b8b3f7f47656a939b60c Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Sun, 1 Dec 2024 14:36:53 -0500 Subject: [PATCH 35/50] green, clear returns Nothing --- .../src/main/java/org/enso/base/cache/ReloadDetector.java | 5 ++++- .../java/org/enso/base/enso_cloud/EnsoSecretHelper.java | 6 +----- test/Table_Tests/src/IO/Fetch_Spec.enso | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java index ad225dd781f2..61b512d1e15f 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -43,6 +43,7 @@ private void resetTrigger() { import Standard.Base.Data.Boolean.Boolean import Standard.Base.Nothing.Nothing import Standard.Base.Runtime.Managed_Resource.Managed_Resource + import Standard.Base.Runtime.Ref.Ref type Trigger private Value mr:Managed_Resource @@ -55,7 +56,9 @@ private void resetTrigger() { get self = self.mr.with .get - clear self = self.mr.with (ref-> ref.put Nothing) + clear self = + self.mr.with (ref-> ref.put Nothing) + Nothing """); trigger = module.invokeMember("eval_expression", "Trigger.new"); } diff --git a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java index 9859ab54cc7e..2e7b622e6848 100644 --- a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java +++ b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java @@ -176,17 +176,13 @@ public EnsoHttpResponse reconstructResponseFromCachedStream( } } - private static EnsoHTTPResponseCache getOrCreateCache() { + public static EnsoHTTPResponseCache getOrCreateCache() { if (cache == null) { cache = new EnsoHTTPResponseCache(); } return cache; } - public static EnsoHTTPResponseCache getCache() { - return cache; - } - private static final Comparator> headerNameComparator = Comparator.comparing((Pair pair) -> pair.getLeft()) .thenComparing(Comparator.comparing(pair -> pair.getRight())); diff --git a/test/Table_Tests/src/IO/Fetch_Spec.enso b/test/Table_Tests/src/IO/Fetch_Spec.enso index 0d49c7b454ac..dc7569c90859 100644 --- a/test/Table_Tests/src/IO/Fetch_Spec.enso +++ b/test/Table_Tests/src/IO/Fetch_Spec.enso @@ -173,7 +173,7 @@ add_specs suite_builder = with_lru_cache lru_cache action fake_reload = - EnsoSecretHelper.getCache.getLRUCache.simulateReloadTestOnly + EnsoSecretHelper.getOrCreateCache.getLRUCache.simulateReloadTestOnly url0 = base_url_with_slash+'test_download?max-age=16&length=10' url1 = base_url_with_slash+'test_download?max-age=16&length=20' From eeba1dc04cf5519be178af0dd2f719134884a75d Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Tue, 3 Dec 2024 11:26:00 -0500 Subject: [PATCH 36/50] wip --- .../interpreter/instrument/command/RecomputeContextCmd.scala | 2 +- .../lib/Standard/Base/0.0.0-dev/src/Runtime/Resource.enso | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala index 6cda02ff2edd..c3939da1d1c6 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala @@ -43,7 +43,7 @@ class RecomputeContextCmd( ec: ExecutionContext ): Future[Boolean] = { Future { - EnsoContext.get(null).getResourceManager().scheduleFinalization(); + EnsoContext.get(null).getResourceManager().scheduleFinalizationOfSystemReferences(); ctx.jobControlPlane.abortJobs( request.contextId, "recompute context", diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Resource.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Resource.enso index f496a0839c71..34de4ddbfc27 100644 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Resource.enso +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Resource.enso @@ -13,4 +13,4 @@ type Managed_Resource take self = @Builtin_Method "Managed_Resource.take" register_builtin resource function system = @Builtin_Method "Managed_Resource.register_builtin" -with_builtin r action = @Builtin_Method "Managed_Resource.with_builtin" +with_builtin r action = @Builtin_Method "Managed_Resource.with_builtin" \ No newline at end of file From afa0dfc8856e14e128b7c190be37c77be07f6ccd Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Tue, 3 Dec 2024 14:14:36 -0500 Subject: [PATCH 37/50] remove Jaroslav POC --- .../base/enso_cloud/EnsoSecretHelper.java | 43 ++----------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java index ff6677f2ff2f..2e7b622e6848 100644 --- a/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java +++ b/std-bits/base/src/main/java/org/enso/base/enso_cloud/EnsoSecretHelper.java @@ -19,12 +19,10 @@ import org.enso.base.net.URISchematic; import org.enso.base.net.URIWithSecrets; import org.graalvm.collections.Pair; -import org.graalvm.polyglot.Context; -import org.graalvm.polyglot.Value; /** Makes HTTP requests with secrets in either header or query string. */ public final class EnsoSecretHelper extends SecretValueResolver { - private static Value cache; + private static EnsoHTTPResponseCache cache; /** Gets a JDBC connection resolving EnsoKeyValuePair into the properties. */ public static Connection getJDBCConnection( @@ -179,43 +177,10 @@ public EnsoHttpResponse reconstructResponseFromCachedStream( } public static EnsoHTTPResponseCache getOrCreateCache() { - if (getCache() instanceof EnsoHTTPResponseCache httpCache) { - return httpCache; - } else { - var module = - Context.getCurrent() - .eval( - "enso", - """ - import Standard.Base.Runtime.Managed_Resource.Managed_Resource - import Standard.Base.Data.Boolean.Boolean - - type Cache - private Value ref:Managed_Resource - - new obj -> Cache = - on_finalize _ = 0 - ref = Managed_Resource.register obj on_finalize Boolean.True - Cache.Value ref - - get self = self.ref.with (r->r) - """); - var cacheNew = module.invokeMember("eval_expression", "Cache.new"); - var httpCache = new EnsoHTTPResponseCache(); - cache = cacheNew.execute(httpCache); - return httpCache; - } - } - - public static EnsoHTTPResponseCache getCache() { - var c = cache instanceof Value v ? v.invokeMember("get") : null; - if (c != null - && c.isHostObject() - && c.asHostObject() instanceof EnsoHTTPResponseCache httpCache) { - return httpCache; - } else { - return null; + if (cache == null) { + cache = new EnsoHTTPResponseCache(); } + return cache; } private static final Comparator> headerNameComparator = From 4f22fe5bc9a808e438ce4310f9140de4b6bd9dea Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Tue, 3 Dec 2024 14:32:57 -0500 Subject: [PATCH 38/50] move ReloadDetector inline enso to a module --- .../src/Network/Reload_Detector.enso | 32 +++++++++++++++++ .../org/enso/base/cache/ReloadDetector.java | 35 ++----------------- 2 files changed, 34 insertions(+), 33 deletions(-) create mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso new file mode 100644 index 000000000000..a8f244fdcc90 --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso @@ -0,0 +1,32 @@ +import Standard.Base.Data.Boolean.Boolean +import Standard.Base.Nothing.Nothing +import Standard.Base.Runtime.Managed_Resource.Managed_Resource +import Standard.Base.Runtime.Ref.Ref + +## PRIVATE + This is used by ReloadDetector.java to create a `Managed_Resource` that is + finalized when the reload button is pressed. + + The `on_finalize` function and the `clear` method both write `Nothing` to the + ref. This is a signal that a reload has happenend. `on_finalize` is called by + the engine when a reload happens. `clear` is only for testing, to simulate a + reload. + + The `0` value stored in the ref is not used; it just has to be something + other than Nothing. +type Reload_Detector + private Value mr:Managed_Resource + + new -> Reload_Detector = + ref = Ref.new 0 + on_finalize ref = ref.put Nothing + mr = Managed_Resource.register ref on_finalize Boolean.True + Reload_Detector.Value mr + + get self = self.mr.with .get + + clear self = + self.mr.with (ref-> ref.put Nothing) + Nothing + +create_reload_detector = Reload_Detector.new diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java index 61b512d1e15f..816fc598bdcd 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -1,5 +1,6 @@ package org.enso.base.cache; +import org.enso.base.polyglot.EnsoMeta; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; @@ -28,39 +29,7 @@ public boolean hasReloadOccurred() { } private void resetTrigger() { - // The `on_finalize` function and the `clear` method both write `Nothing` to - // the ref. This is a signal that a reload has happenend. `on_finalize` is - // called by the engine when a reload happens. `clear` is only for testing, - // to simulate a reload. - // - // The `0` value stored in the ref is not used; it just has to be something - // other than Nothing. - var module = - Context.getCurrent() - .eval( - "enso", - """ - import Standard.Base.Data.Boolean.Boolean - import Standard.Base.Nothing.Nothing - import Standard.Base.Runtime.Managed_Resource.Managed_Resource - import Standard.Base.Runtime.Ref.Ref - - type Trigger - private Value mr:Managed_Resource - - new -> Trigger = - ref = Ref.new 0 - on_finalize ref = ref.put Nothing - mr = Managed_Resource.register ref on_finalize Boolean.True - Trigger.Value mr - - get self = self.mr.with .get - - clear self = - self.mr.with (ref-> ref.put Nothing) - Nothing - """); - trigger = module.invokeMember("eval_expression", "Trigger.new"); + trigger = EnsoMeta.callStaticModuleMethod("Standard.Base.Network.Reload_Detector", "create_reload_detector"); } void simulateReloadTestOnly() { From 0aa2490098083f68874eee13e028afeabc0fdfb0 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Tue, 3 Dec 2024 14:35:36 -0500 Subject: [PATCH 39/50] wip --- test/Table_Tests/src/IO/Fetch_Spec.enso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Table_Tests/src/IO/Fetch_Spec.enso b/test/Table_Tests/src/IO/Fetch_Spec.enso index dc7569c90859..217500c39b03 100644 --- a/test/Table_Tests/src/IO/Fetch_Spec.enso +++ b/test/Table_Tests/src/IO/Fetch_Spec.enso @@ -520,7 +520,7 @@ add_specs suite_builder = Test_Environment.unsafe_with_environment_override "ENSO_LIB_HTTP_CACHE_MAX_TOTAL_CACHE_LIMIT" "101%" <| LRUCache.new . getSettings . getTotalCacheLimit . should_equal (TotalCacheLimit.Percentage.new 0.2) - group_builder.specify "Cache should be cleared when the trigger reference is garbage collected because of a reload" <| + group_builder.specify "Cache should be cleared when a reload is detected" <| HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=10' HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=11' HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=12' From 2b016066887f4ea48977f4e779bc32ce761c1815 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Tue, 3 Dec 2024 15:37:36 -0500 Subject: [PATCH 40/50] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 691e15e84c7b..403a0c3d2314 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ - [Added `Table.input` allowing creation of typed tables from vectors of data, including auto parsing text columns.][11562] - [Enhance Managed_Resource to allow implementation of in-memory caches][11577] +- [The reload button clears the HTTP cache.][11673] [11235]: https://github.com/enso-org/enso/pull/11235 [11255]: https://github.com/enso-org/enso/pull/11255 @@ -95,6 +96,7 @@ [11490]: https://github.com/enso-org/enso/pull/11490 [11562]: https://github.com/enso-org/enso/pull/11562 [11577]: https://github.com/enso-org/enso/pull/11577 +[11673]: https://github.com/enso-org/enso/pull/11673 #### Enso Language & Runtime From a041045046c7ab0dded3e1920649fa357ecfe5b3 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Tue, 3 Dec 2024 15:37:52 -0500 Subject: [PATCH 41/50] fmt --- .../src/main/java/org/enso/base/cache/ReloadDetector.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java index 816fc598bdcd..b7fb1e51cdd0 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -1,7 +1,6 @@ package org.enso.base.cache; import org.enso.base.polyglot.EnsoMeta; -import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Value; /** @@ -29,7 +28,9 @@ public boolean hasReloadOccurred() { } private void resetTrigger() { - trigger = EnsoMeta.callStaticModuleMethod("Standard.Base.Network.Reload_Detector", "create_reload_detector"); + trigger = + EnsoMeta.callStaticModuleMethod( + "Standard.Base.Network.Reload_Detector", "create_reload_detector"); } void simulateReloadTestOnly() { From eebe9982a65d52367f8c9e6736e14e02a63f4f15 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Thu, 5 Dec 2024 16:58:29 -0500 Subject: [PATCH 42/50] wip --- .../src/Network/Reload_Detector.enso | 34 +++++++++++-------- .../src/Runtime/Managed_Resource.enso | 13 ++++++- .../org/enso/base/cache/ReloadDetector.java | 4 +-- test/Table_Tests/src/IO/Fetch_Spec.enso | 14 ++++---- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso index a8f244fdcc90..f9b4f0269538 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso @@ -1,32 +1,36 @@ -import Standard.Base.Data.Boolean.Boolean +import Standard.Base.Error.Error import Standard.Base.Nothing.Nothing import Standard.Base.Runtime.Managed_Resource.Managed_Resource import Standard.Base.Runtime.Ref.Ref +from Standard.Base.Data.Boolean import Boolean, True, False + +## PRIVATE +type Reload_Detector_Fake + Value ## PRIVATE This is used by ReloadDetector.java to create a `Managed_Resource` that is - finalized when the reload button is pressed. + garbage collected when the reload button is pressed. - The `on_finalize` function and the `clear` method both write `Nothing` to the - ref. This is a signal that a reload has happenend. `on_finalize` is called by - the engine when a reload happens. `clear` is only for testing, to simulate a - reload. + The managed resource contains a Ref containing a 0 (the value is + unimportant). When the reload button is pressed, the ref is removed and + attempting to access it using `with` throws an `Uninitialized_State`. When + the `Uninitialized_State` is detected, it indicates that the reload has been + initiated. - The `0` value stored in the ref is not used; it just has to be something - other than Nothing. + For standard library testing, a special fake value is stored in the ref to + indicate a reload. type Reload_Detector private Value mr:Managed_Resource new -> Reload_Detector = - ref = Ref.new 0 - on_finalize ref = ref.put Nothing - mr = Managed_Resource.register ref on_finalize Boolean.True + mr = Managed_Resource.register (Ref.new 0) (x-> Nothing) True Reload_Detector.Value mr - get self = self.mr.with .get + has_reload_occurred self = + self.mr.has_been_collected || self.mr.with (ref-> ref.get.is_a Reload_Detector_Fake) - clear self = - self.mr.with (ref-> ref.put Nothing) - Nothing + simulate_reload_test_only self = + self.mr.with ref-> ref.put Reload_Detector_Fake.Value create_reload_detector = Reload_Detector.new diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso index ed3bdbb23765..81cce22ce35f 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso @@ -1,8 +1,9 @@ ## An API for manual resource management. import project.Any.Any +import project.Errors.Common.Uninitialized_State import project.Nothing.Nothing -from project.Data.Boolean import Boolean, False +from project.Data.Boolean import Boolean, True, False ## Resource provides an API for manual management of computation resources. @@ -90,5 +91,15 @@ type Managed_Resource take : Any take self = @Builtin_Method "Managed_Resource.take" + ## PRIVATE + ADVANCED + + Returns true iff the resource has been collected by the engine, false + otherwise. If `with` throws any other error, it is propagated. + has_been_collected : Boolean + has_been_collected self -> Boolean = self.with x-> + if x.is_error.not then False else + if x.catch.is_a Uninitialized_State then True else x + register_builtin r fn sys:Boolean = @Builtin_Method "Managed_Resource.register_builtin" with_builtin r fn = @Builtin_Method "Managed_Resource.with_builtin" diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java index b7fb1e51cdd0..2714372b14e4 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -20,7 +20,7 @@ public ReloadDetector() { } public boolean hasReloadOccurred() { - var reloadHasOccurred = trigger.invokeMember("get").isNull(); + var reloadHasOccurred = trigger.invokeMember("has_reload_occurred").asBoolean(); if (reloadHasOccurred) { resetTrigger(); } @@ -34,6 +34,6 @@ private void resetTrigger() { } void simulateReloadTestOnly() { - trigger.invokeMember("clear"); + trigger.invokeMember("simulate_reload_test_only"); } } diff --git a/test/Table_Tests/src/IO/Fetch_Spec.enso b/test/Table_Tests/src/IO/Fetch_Spec.enso index 217500c39b03..54d950b2aceb 100644 --- a/test/Table_Tests/src/IO/Fetch_Spec.enso +++ b/test/Table_Tests/src/IO/Fetch_Spec.enso @@ -521,24 +521,24 @@ add_specs suite_builder = LRUCache.new . getSettings . getTotalCacheLimit . should_equal (TotalCacheLimit.Percentage.new 0.2) group_builder.specify "Cache should be cleared when a reload is detected" <| - HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=10' - HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=11' - HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=12' + HTTP.fetch base_url_with_slash+'test_download?length=10' + HTTP.fetch base_url_with_slash+'test_download?length=11' + HTTP.fetch base_url_with_slash+'test_download?length=12' get_num_response_cache_entries . should_equal 3 fake_reload get_num_response_cache_entries . should_equal 3 # Cleaning is not triggered until the next request - HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=13' + HTTP.fetch base_url_with_slash+'test_download?length=13' get_num_response_cache_entries . should_equal 1 - HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=14' - HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=15' + HTTP.fetch base_url_with_slash+'test_download?length=14' + HTTP.fetch base_url_with_slash+'test_download?length=15' get_num_response_cache_entries . should_equal 3 fake_reload get_num_response_cache_entries . should_equal 3 # Cleaning is not triggered until the next request - HTTP.fetch base_url_with_slash+'test_download?max-age=16&length=16' + HTTP.fetch base_url_with_slash+'test_download?length=16' get_num_response_cache_entries . should_equal 1 group_builder.specify "Reissues the request if the cache file disappears" pending=pending_has_url <| Test.with_retries <| From f168299631e2beaac95c6798971cffcceabaed24 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Thu, 5 Dec 2024 17:06:37 -0500 Subject: [PATCH 43/50] fake with finalize --- .../Base/0.0.0-dev/src/Network/Reload_Detector.enso | 12 +++--------- .../Base/0.0.0-dev/src/Runtime/Managed_Resource.enso | 1 + 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso index f9b4f0269538..f9d47db40856 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso @@ -1,13 +1,10 @@ import Standard.Base.Error.Error +import Standard.Base.Meta import Standard.Base.Nothing.Nothing import Standard.Base.Runtime.Managed_Resource.Managed_Resource import Standard.Base.Runtime.Ref.Ref from Standard.Base.Data.Boolean import Boolean, True, False -## PRIVATE -type Reload_Detector_Fake - Value - ## PRIVATE This is used by ReloadDetector.java to create a `Managed_Resource` that is garbage collected when the reload button is pressed. @@ -17,20 +14,17 @@ type Reload_Detector_Fake attempting to access it using `with` throws an `Uninitialized_State`. When the `Uninitialized_State` is detected, it indicates that the reload has been initiated. - - For standard library testing, a special fake value is stored in the ref to - indicate a reload. type Reload_Detector private Value mr:Managed_Resource new -> Reload_Detector = - mr = Managed_Resource.register (Ref.new 0) (x-> Nothing) True + mr = Managed_Resource.register (Ref.new 1) (x-> Nothing) True Reload_Detector.Value mr has_reload_occurred self = self.mr.has_been_collected || self.mr.with (ref-> ref.get.is_a Reload_Detector_Fake) simulate_reload_test_only self = - self.mr.with ref-> ref.put Reload_Detector_Fake.Value + self.mr.finalize create_reload_detector = Reload_Detector.new diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso index 81cce22ce35f..39700eb45d3e 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso @@ -2,6 +2,7 @@ import project.Any.Any import project.Errors.Common.Uninitialized_State +import project.Meta import project.Nothing.Nothing from project.Data.Boolean import Boolean, True, False From 95208acece55a9e8fa8c8b252f5d5af0eb6b63d6 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Thu, 5 Dec 2024 17:08:31 -0500 Subject: [PATCH 44/50] wip --- .../Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso | 2 +- .../Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso index f9d47db40856..d2f456eb6257 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso @@ -22,7 +22,7 @@ type Reload_Detector Reload_Detector.Value mr has_reload_occurred self = - self.mr.has_been_collected || self.mr.with (ref-> ref.get.is_a Reload_Detector_Fake) + self.mr.has_been_finalized || self.mr.with (ref-> ref.get.is_a Reload_Detector_Fake) simulate_reload_test_only self = self.mr.finalize diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso index 39700eb45d3e..33beacf92d7e 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Managed_Resource.enso @@ -97,8 +97,8 @@ type Managed_Resource Returns true iff the resource has been collected by the engine, false otherwise. If `with` throws any other error, it is propagated. - has_been_collected : Boolean - has_been_collected self -> Boolean = self.with x-> + has_been_finalized : Boolean + has_been_finalized self -> Boolean = self.with x-> if x.is_error.not then False else if x.catch.is_a Uninitialized_State then True else x From 97962344cf9b7505511ec215226d3ff008f6ee55 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Thu, 5 Dec 2024 17:09:11 -0500 Subject: [PATCH 45/50] wip --- test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso b/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso index 7e30fe2111ca..83cffffda03e 100644 --- a/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso +++ b/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso @@ -95,6 +95,7 @@ add_specs suite_builder = suite_builder.group "Managed_Resource" group_builder-> # finalizes the resource mr.finalize + mr.has_been_finalized . should_be_true builder.append "Finalized:"+mr.to_text # operation on finalized resource From 07f413de74c42688dc7502577e7e2a329bc8b3f9 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Thu, 5 Dec 2024 17:31:34 -0500 Subject: [PATCH 46/50] wip --- .../Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso | 2 +- .../base/src/main/java/org/enso/base/cache/ReloadDetector.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso index d2f456eb6257..10418f111570 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso @@ -22,7 +22,7 @@ type Reload_Detector Reload_Detector.Value mr has_reload_occurred self = - self.mr.has_been_finalized || self.mr.with (ref-> ref.get.is_a Reload_Detector_Fake) + self.mr.has_been_finalized simulate_reload_test_only self = self.mr.finalize diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java index 2714372b14e4..4c63a1d8c7a1 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -9,7 +9,7 @@ *

.hasReloadOccurred() returns true if the reload button was pressed since the last call to * .hasReloadOccurred(). * - *

This uses a weak reference (created in eval'd Enso code) that is set to null on reload. + *

This uses a weak reference (created in eval'd Enso code) that is cleared on reload. */ public class ReloadDetector { // Weak reference that is set to null on reload. From e6ad441b127559357c72fdbe7b36b42ddd98a0e1 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Thu, 5 Dec 2024 17:48:36 -0500 Subject: [PATCH 47/50] wip --- .../org/enso/base/cache/ReloadDetector.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java index 4c63a1d8c7a1..3bf919077555 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -9,31 +9,30 @@ *

.hasReloadOccurred() returns true if the reload button was pressed since the last call to * .hasReloadOccurred(). * - *

This uses a weak reference (created in eval'd Enso code) that is cleared on reload. + *

This uses a `Managed_Resource` (created in eval'd Enso code) that is cleared on reload. */ public class ReloadDetector { - // Weak reference that is set to null on reload. - private Value trigger; + private Value ensoReloadDetector; public ReloadDetector() { - resetTrigger(); + resetEnsoReloadDetector(); } public boolean hasReloadOccurred() { - var reloadHasOccurred = trigger.invokeMember("has_reload_occurred").asBoolean(); + var reloadHasOccurred = ensoReloadDetector.invokeMember("has_reload_occurred").asBoolean(); if (reloadHasOccurred) { - resetTrigger(); + resetEnsoReloadDetector(); } return reloadHasOccurred; } - private void resetTrigger() { - trigger = + private void resetEnsoReloadDetector() { + ensoReloadDetector = EnsoMeta.callStaticModuleMethod( "Standard.Base.Network.Reload_Detector", "create_reload_detector"); } void simulateReloadTestOnly() { - trigger.invokeMember("simulate_reload_test_only"); + ensoReloadDetector.invokeMember("simulate_reload_test_only"); } } From 828ac2d9d29f0df720f5e4a16dcbc76681f42854 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Mon, 9 Dec 2024 17:30:19 -0500 Subject: [PATCH 48/50] review --- .../Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso | 5 +++-- .../src/main/java/org/enso/base/cache/ReloadDetector.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso index 10418f111570..966975d0ecf7 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Network/Reload_Detector.enso @@ -24,7 +24,8 @@ type Reload_Detector has_reload_occurred self = self.mr.has_been_finalized - simulate_reload_test_only self = - self.mr.finalize +## PRIVATE +simulate_reload_test_only reload_detector = reload_detector.mr.finalize +## PRIVATE create_reload_detector = Reload_Detector.new diff --git a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java index 3bf919077555..5328c8003a16 100644 --- a/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java +++ b/std-bits/base/src/main/java/org/enso/base/cache/ReloadDetector.java @@ -33,6 +33,7 @@ private void resetEnsoReloadDetector() { } void simulateReloadTestOnly() { - ensoReloadDetector.invokeMember("simulate_reload_test_only"); + EnsoMeta.callStaticModuleMethod( + "Standard.Base.Network.Reload_Detector", "simulate_reload_test_only", ensoReloadDetector); } } From be50dcd58cb9e2abafd4461425f10195ef1364eb Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Tue, 10 Dec 2024 09:59:39 -0500 Subject: [PATCH 49/50] fmt --- .../interpreter/instrument/command/RecomputeContextCmd.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala index c3939da1d1c6..165828850209 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala @@ -43,7 +43,10 @@ class RecomputeContextCmd( ec: ExecutionContext ): Future[Boolean] = { Future { - EnsoContext.get(null).getResourceManager().scheduleFinalizationOfSystemReferences(); + EnsoContext + .get(null) + .getResourceManager() + .scheduleFinalizationOfSystemReferences(); ctx.jobControlPlane.abortJobs( request.contextId, "recompute context", From 5aa4003e01ce690d907fefd9488e3bd688c06760 Mon Sep 17 00:00:00 2001 From: Gregory Travis Date: Tue, 10 Dec 2024 12:53:37 -0500 Subject: [PATCH 50/50] repeat url in fake reload test --- test/Table_Tests/src/IO/Fetch_Spec.enso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Table_Tests/src/IO/Fetch_Spec.enso b/test/Table_Tests/src/IO/Fetch_Spec.enso index 54d950b2aceb..ed941557fe48 100644 --- a/test/Table_Tests/src/IO/Fetch_Spec.enso +++ b/test/Table_Tests/src/IO/Fetch_Spec.enso @@ -529,7 +529,7 @@ add_specs suite_builder = fake_reload get_num_response_cache_entries . should_equal 3 # Cleaning is not triggered until the next request - HTTP.fetch base_url_with_slash+'test_download?length=13' + HTTP.fetch base_url_with_slash+'test_download?length=10' get_num_response_cache_entries . should_equal 1 HTTP.fetch base_url_with_slash+'test_download?length=14' HTTP.fetch base_url_with_slash+'test_download?length=15'