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

Open
wants to merge 27 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
27 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
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
18 changes: 15 additions & 3 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Ref.enso
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import project.Any.Any
import project.Data.Boolean.Boolean

## PRIVATE
ADVANCED
Expand All @@ -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?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we replace this with an atom type reference_type?

e.g.

type Reference_Type
    Weak
    Regular

Then if we ever want to also add support for soft references, it is just the matter of adding yet another variant to Reference_Type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now I am thinking about:

type Ref_Auto_Clean
       Never # regular reference
       Explicit # only per user request
       Soon # using `WeakReference` disappears as soon as "possible"
       Managed estimated_memory_size:Integer time_in_ms_to_recompute:Integer # automatic when needed

the ..Explicit cleaning policy would solve @GregoryTravis's

the reference ... collected, then I know that the refresh button has been clicked

We would connect cleanup of ..Explicit directly to refresh button click and no GC.

Or we could map all above types to estimates of memory and recomputation time:

  • Never - zero memory, maximal time
  • Explicit - zero memory, zero time
  • Soon - some memory, zero time
  • Managed - some memory and some 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
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());
}

@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) {
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
Expand Up @@ -7,6 +7,6 @@
@BuiltinMethod(type = "Special", name = "<new_ref>")
public class NewRefNode extends Node {
public Ref execute() {
return new Ref(null);
return new Ref(null, 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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<Reference> refs = new ConcurrentLinkedQueue<>();
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

ReferencesManager(EnsoContext ctx) {
this.ctx = ctx;
}

/**
* Creates new reference to provided object and registers it in the manager.
*
* @param <T> 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 <T> Reference<T> create(T obj, int type) {
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,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;
Expand All @@ -16,16 +19,24 @@
@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}
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
*/
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);
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
this.value = wrapValue(value);
}

/**
Expand All @@ -34,7 +45,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);
}

/**
Expand All @@ -47,8 +58,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
Expand All @@ -71,6 +82,24 @@ 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 v;
}
}

@ExportMessage
Object toDisplayString(
boolean allowSideEffects, @CachedLibrary(limit = "3") InteropLibrary interop) {
Expand Down
Loading
Loading