diff --git a/CHANGELOG.md b/CHANGELOG.md index e419aab92fb4..54582535dd1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ operation.][11490] - [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] [11235]: https://github.com/enso-org/enso/pull/11235 [11255]: https://github.com/enso-org/enso/pull/11255 @@ -91,6 +92,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 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..ed3bdbb23765 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 +from project.Data.Boolean import Boolean, False ## 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=False = + @Tail_Call register_builtin resource function system_finalization_allowed ## PRIVATE ADVANCED @@ -58,13 +67,19 @@ type Managed_Resource ADVANCED Executes the provided action on the resource managed by the managed - resource object. + resource object. The action is invoked with the managed resource only if + it has not yet been finalized. If the resource has already been finalized + then `Error` with `Uninitialized_State` payload is passed into the + action instead of the resource. Arguments: - action: The action that will be applied to the resource managed by - resource. - with : (Any -> Any) -> Any - with self ~action = @Builtin_Method "Managed_Resource.with" + the `Managed_Resource` (or to `Uninitialized_State` error). + Returns: + Value returned from the `action` + + with : (Any -> Any) -> Any -> Any + with self ~action = @Tail_Call with_builtin self action ## PRIVATE ADVANCED @@ -74,3 +89,6 @@ 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" +with_builtin r fn = @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..a7256efb0cc7 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,9 +4,11 @@ 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 +from project.Errors.Common import Uninitialized_State import project.Nothing.Nothing import project.Runtime.Managed_Resource.Managed_Resource import project.System.Advanced.Restartable_Input_Stream.Restartable_Input_Stream @@ -17,6 +19,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 +117,12 @@ 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 java_like_stream-> + java_like_stream.catch Uninitialized_State _-> + Panic.throw <| IOException.new "Stream closed" + 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-integration-tests/src/test/java/org/enso/compiler/test/ExecCompilerTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/ExecCompilerTest.java index 87d340a15641..4b41e8dfaa10 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/ExecCompilerTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/ExecCompilerTest.java @@ -176,7 +176,7 @@ public void testSelfAssignment() throws Exception { var error = run.execute(-1); assertTrue("We get an error value back", error.isException()); assertTrue("The error value also represents null", error.isNull()); - assertEquals("(Error: Uninitialized value)", error.toString()); + assertEquals("(Error: 'Uninitialized value')", error.toString()); } @Test @@ -195,7 +195,7 @@ public void testRecursiveDefinition() throws Exception { var error = run.execute("Nope: "); assertTrue("We get an error value back", error.isException()); assertTrue("The error value also represents null", error.isNull()); - assertEquals("(Error: Uninitialized value)", error.toString()); + assertEquals("(Error: 'Uninitialized value')", error.toString()); } @Ignore("Explicitly-default arguments will be implemented in #8480") 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..36381d537f0d --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/ManagedResourceTest.java @@ -0,0 +1,125 @@ +package org.enso.interpreter.runtime; + +import static org.hamcrest.MatcherAssert.assertThat; +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.hamcrest.Matchers; +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 newResource; + private static Value createResource; + private static Value getResource; + + @BeforeClass + public static void initCtx() throws Exception { + ctx = ContextUtils.createDefaultContext(); + ensoCtx = ContextUtils.leakContext(ctx); + var code = + """ + import Standard.Base.Runtime.Managed_Resource.Managed_Resource + + make_new obj = + Managed_Resource.register obj (_->0) + + create_new obj system_resource = + Managed_Resource.register obj (_->0) system_resource + + get_res ref = ref.with it-> + it + """; + var src = Source.newBuilder("enso", code, "gc.enso").build(); + var gcEnso = ctx.eval(src); + newResource = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "make_new"); + createResource = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create_new"); + getResource = gcEnso.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "get_res"); + } + + @AfterClass + public static void closeCtx() throws Exception { + ctx.close(); + ctx = null; + } + + @Test + public void regularReference() throws Exception { + var obj = new Object(); + var ref = newResource.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(), getResource.execute(ref).asHostObject()); + + assertGC("Weak wasn't released", false, weakRef); + assertFalse("Value was not GCed", getResource.execute(ref).isNull()); + assertEquals("We get the object", weakRef.get(), getResource.execute(ref).asHostObject()); + + ensoCtx.getResourceManager().scheduleFinalizationOfSystemReferences(); + assertEquals( + "scheduleFinalization has no effect on regular reference", + weakRef.get(), + getResource.execute(ref).asHostObject()); + } + + @Test + public void explicitlyReclaimableReference() throws Exception { + var obj = new Object(); + var ref = createResource.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, getResource.execute(ref).asHostObject()); + + ensoCtx.getResourceManager().scheduleFinalizationOfSystemReferences(); + + var none = getResource.execute(ref); + assertTrue("Value was GCed", none.isException()); + assertEquals( + "It is an error", "Standard.Base.Error.Error", none.getMetaObject().getMetaQualifiedName()); + assertThat( + "Contains Uninitialized_State as payload", + none.toString(), + Matchers.allOf( + Matchers.containsString("Uninitialized_State"), + Matchers.containsString("Error"), + Matchers.containsString("Managed_Resource"))); + } + + 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 new file mode 100644 index 000000000000..1e0f06682b90 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/RefTest.java @@ -0,0 +1,93 @@ +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 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()); + } + + 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/test/instrument/IncrementalUpdatesTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/instrument/IncrementalUpdatesTest.java index f5c0cc4f86cc..1c4ca009ba24 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/instrument/IncrementalUpdatesTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/instrument/IncrementalUpdatesTest.java @@ -133,7 +133,7 @@ public void sendNotANumberChange() { result.head().payload() instanceof Runtime$Api$ExecutionComplete); Assert.assertEquals( "Error is printed as a result", - List.newBuilder().addOne("(Error: Uninitialized value)"), + List.newBuilder().addOne("(Error: 'Uninitialized value')"), context.consumeOut()); } 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..09b22fa139c4 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,23 +1,23 @@ 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; 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.error.DataflowError; import org.enso.interpreter.runtime.state.State; @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( @@ -26,19 +26,23 @@ 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) { - ResourceManager resourceManager = EnsoContext.get(this).getResourceManager(); - resourceManager.park(self); - try { - return invokeCallableNode.execute(action, frame, state, new Object[] {self.getResource()}); - } finally { - resourceManager.unpark(self); + Object execute(State state, VirtualFrame frame, ManagedResource mr, Object action) { + var ctx = EnsoContext.get(this); + var resourceManager = ctx.getResourceManager(); + if (mr.getPhantomReference().refersTo(mr)) { + resourceManager.park(mr); + try { + return invokeCallableNode.execute(action, frame, state, new Object[] {mr.getResource()}); + } finally { + resourceManager.unpark(mr); + } + } else { + var payload = ctx.getBuiltins().error().makeUninitializedStateError(mr); + var err = DataflowError.withDefaultTrace(payload, this); + return invokeCallableNode.execute(action, frame, state, new Object[] {err}); } } } 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 ca6898cc793b..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); - } -} 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/ResourceManager.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/ResourceManager.java index b8295c6b9457..4ce4608c47c4 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 @@ -113,21 +113,39 @@ public void take(ManagedResource resource) { } /** - * Registers a new resource to the system. {@code function} will be called on {@code object} when + * 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 + * #scheduleFinalizationOfSystemReferences} 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 scheduleFinalizationOfSystemReferences() { + 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; } /** @@ -408,6 +444,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/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/java/org/enso/interpreter/runtime/data/ManagedResource.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/ManagedResource.java index 424eac477f3c..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 @@ -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") @@ -52,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( @@ -97,8 +119,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/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java index 1216a7c958f2..a4b7fa2d2a8b 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java @@ -21,6 +21,7 @@ import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.data.Type; +import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.enso.interpreter.runtime.state.HasContextEnabledNode; @@ -116,8 +117,8 @@ private DataflowError(Object payload, int stackTraceElementLimit, Node location) * * @return the payload object */ - public Object getPayload() { - return payload != null ? payload : "Uninitialized value"; + public final Object getPayload() { + return payload != null ? payload : Text.create("Uninitialized value"); } /** 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 1ad5b986a45f..cab00b9c6025 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, @@ -1969,16 +1968,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." 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(); } /** 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 2e7b622e6848..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 @@ -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,10 +179,43 @@ public EnsoHttpResponse reconstructResponseFromCachedStream( } public 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.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; } - return cache; } private static final Comparator> headerNameComparator = diff --git a/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso b/test/Base_Tests/src/Runtime/Managed_Resource_Spec.enso index 14f3cd6730cc..7e30fe2111ca 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,61 @@ 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-> + builder.append " is_error:"+v.is_error.to_text + v.if_not_error <| + # should never be called + builder.append " empty :"+v.to_text + "Don't call me!" + + builder.append none.to_text + none.is_error . should_be_true + + exp_text = """ + + Allocated: Managed_Resource 42 + with :42 + modify:7 + With finished:6 + finalizing:-1 + Finalized:Managed_Resource -1 + is_error:True + (Error: (Uninitialized_State.Error Managed_Resource -1)) + + 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 diff --git a/test/Table_Tests/src/IO/Fetch_Spec.enso b/test/Table_Tests/src/IO/Fetch_Spec.enso index 94054cb2903d..9c263a03cd36 100644 --- a/test/Table_Tests/src/IO/Fetch_Spec.enso +++ b/test/Table_Tests/src/IO/Fetch_Spec.enso @@ -145,14 +145,14 @@ add_specs suite_builder = get_cache_file_sizes -> Vector Integer = Vector.from_polyglot_array EnsoSecretHelper.getOrCreateCache.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.getOrCreateCache.setLRUCache LRUCache.new Panic.with_finalizer reset <| EnsoSecretHelper.getOrCreateCache.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 @@ -160,14 +160,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 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..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 @@ -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 = with_builtin self action 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" \ No newline at end of file