Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance Managed_Resource to allow implementation of in-memory caches #11577

Merged
merged 29 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5266a4b
Using Ref.new allow_gc=True to implement in-memory caches
JaroslavTulach Nov 18, 2024
d803b28
Hide operations with References behind @TruffleBoundary
JaroslavTulach Nov 18, 2024
38a20b4
Work with v and not this.value
JaroslavTulach Nov 18, 2024
0f437fc
Merging with latest develop
JaroslavTulach Nov 20, 2024
404e3cc
Fixing typo
JaroslavTulach Nov 20, 2024
5629e20
Using Ref.new to cache reference to EnsoHTTPResponseCache
JaroslavTulach Nov 20, 2024
b1d4b37
Removing (unused) support for Name.Special
JaroslavTulach Nov 20, 2024
0e42e9d
Fixing moved import
JaroslavTulach Nov 20, 2024
0b4e1f8
Providing access to bodyNode in the builtin methods
JaroslavTulach Nov 20, 2024
f5f1614
Enabling allow_gc in the caches
JaroslavTulach Nov 20, 2024
dad2bb6
Note in changelog
JaroslavTulach Nov 20, 2024
85e8b6d
Can no longer invoke Managed_Resource.with when Managed_Resource.fina…
JaroslavTulach Nov 27, 2024
e56b867
Backing out the Ref changes
JaroslavTulach Nov 28, 2024
3ca29aa
Merge tag '2024.5.1-nightly.2024.11.27' into wip/jtulach/ReferenceMan…
JaroslavTulach Nov 28, 2024
da2d438
Ref.new takes only one argument (again)
JaroslavTulach Nov 28, 2024
ae83008
Commenting out releaseAll call for now
JaroslavTulach Nov 28, 2024
276a885
Allow system controlled Managed_Resource
JaroslavTulach Nov 28, 2024
205811c
Merging with most recent develop
JaroslavTulach Nov 28, 2024
40aedfc
on_missing behavior for managed resources that get access after being…
JaroslavTulach Nov 28, 2024
2e91135
Updating micro-distribution with the new Managed_Resource builtins
JaroslavTulach Nov 28, 2024
786f156
Moving the GC related parts of RefTest to ManagedResourceTest
JaroslavTulach Nov 28, 2024
934ec5f
Better note in changelog
JaroslavTulach Nov 28, 2024
a3c07e0
Making public so the Fetch_Spec passes OK
JaroslavTulach Nov 28, 2024
a7890d2
Using Managed_Resource inside of EnsoSecretHelper
JaroslavTulach Nov 28, 2024
a1f7e6f
Radek demands use of False
JaroslavTulach Dec 2, 2024
873bac3
Return DataflowError on with of already finalized Managed_Resource
JaroslavTulach Dec 2, 2024
068d223
Rename to scheduleFinalizationOfSystemReferences
JaroslavTulach Dec 2, 2024
7daf941
Uninitialized_State payload is Text
JaroslavTulach Dec 2, 2024
ca7592b
Invoke the Managed_Resource.with an error if GCed
JaroslavTulach Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,15 @@
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
[11371]: https://github.com/enso-org/enso/pull/11371
[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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand All @@ -58,13 +67,18 @@ 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. If the resource has already been finalized
then `Error` representing `Uninitialized_State` is returned.

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"
Returns:
Value returned from the `action` or `Uninitialized_State` `Error`
if the managed resource was already finalized
with : (Any -> Any) -> Any -> Any
with self ~action = @Tail_Call with_builtin self action

## PRIVATE
ADVANCED
Expand All @@ -74,3 +88,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"
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 =
r = self.stream_resource . with java_like_stream->
java_stream = Stream_Utils.asInputStream java_like_stream
self.error_handler <| f java_stream
r.catch Uninitialized_State _->
Panic.throw <| IOException.new "Stream closed"

## PRIVATE
Runs an action with a `ReportingStreamDecoder` decoding data from the
Expand Down
Original file line number Diff line number Diff line change
@@ -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 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 {
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
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());
}

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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
""";
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) {
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -26,19 +26,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) {
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 err = ctx.getBuiltins().error().makeUninitializedStateError(mr);
return DataflowError.withDefaultTrace(err, this);
}
}
}
Loading
Loading