Removing need for asynchronous thread to execute ResourceManager finalizers (#6335)

While Enso runs single-threaded, its `ResourceManager` required additional asynchronous thread to execute its _"finalizers"_. What has been necessary back then is no longer needed since _GraalVM 21.1_. GraalVM now provides support for submitting `ThreadLocalAction` that gets then picked and executed via `TruffleSafepoint` locations. This PR uses such mechanism to _"inject"_ finalizer execution into already running Enso evaluation thread.

Requiring more than one thread has complicated Enso's co-existence with other Truffle language. For example Graal.js is strictly singlethreaded and used to refuse (simple) co-existence with Enso. By allowing Enso to perform all its actions in a single thread, the synergy with Graal.js becomes better.
This commit is contained in:
Jaroslav Tulach 2023-04-20 13:33:45 +02:00 committed by GitHub
parent 4076a64f33
commit e47eb49ea8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 38 additions and 15 deletions

View File

@ -688,6 +688,8 @@
- [One can define lazy atom fields][6151] - [One can define lazy atom fields][6151]
- [Replace IOContexts with Execution Environment and generic Context][6171] - [Replace IOContexts with Execution Environment and generic Context][6171]
- [Vector.sort handles incomparable types][5998] - [Vector.sort handles incomparable types][5998]
- [Removing need for asynchronous thread to execute ResourceManager
finalizers][6335]
[3227]: https://github.com/enso-org/enso/pull/3227 [3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248 [3248]: https://github.com/enso-org/enso/pull/3248
@ -794,6 +796,7 @@
[6151]: https://github.com/enso-org/enso/pull/6151 [6151]: https://github.com/enso-org/enso/pull/6151
[6171]: https://github.com/enso-org/enso/pull/6171 [6171]: https://github.com/enso-org/enso/pull/6171
[5998]: https://github.com/enso-org/enso/pull/5998 [5998]: https://github.com/enso-org/enso/pull/5998
[6335]: https://github.com/enso-org/enso/pull/6335
# Enso 2.0.0-alpha.18 (2021-10-12) # Enso 2.0.0-alpha.18 (2021-10-12)

View File

@ -9,7 +9,7 @@ import org.junit.Assert;
final class InliningBuiltinsOutNode extends Node { final class InliningBuiltinsOutNode extends Node {
long execute(VirtualFrame frame, long a, long b) { long execute(VirtualFrame frame, long a, long b) {
Assert.assertNotNull("VirtualFrame is always provided " + frame); Assert.assertNotNull("VirtualFrame is always provided", frame);
return a + b; return a + b;
} }

View File

@ -1,6 +1,8 @@
package org.enso.interpreter.runtime; package org.enso.interpreter.runtime;
import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.ThreadLocalAction;
import com.oracle.truffle.api.TruffleSafepoint;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InteropLibrary;
import org.enso.interpreter.runtime.data.ManagedResource; import org.enso.interpreter.runtime.data.ManagedResource;
@ -9,8 +11,10 @@ import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue; import java.lang.ref.ReferenceQueue;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/** Allows the context to attach garbage collection hooks on the removal of certain objects. */ /** Allows the context to attach garbage collection hooks on the removal of certain objects. */
public class ResourceManager { public class ResourceManager {
@ -59,7 +63,7 @@ public class ResourceManager {
return; return;
} }
it.getParkedCount().decrementAndGet(); it.getParkedCount().decrementAndGet();
tryFinalize(it); scheduleFinalizationAtSafepoint(it);
} }
/** /**
@ -75,7 +79,7 @@ public class ResourceManager {
return; return;
} }
// Unconditional finalization user controls the resource manually. // Unconditional finalization user controls the resource manually.
it.doFinalize(context); it.finalizeNow(context);
} }
/** /**
@ -89,7 +93,7 @@ public class ResourceManager {
items.remove(resource.getPhantomReference()); items.remove(resource.getPhantomReference());
} }
private void tryFinalize(Item it) { private void scheduleFinalizationAtSafepoint(Item it) {
if (it.isFlaggedForFinalization().get()) { if (it.isFlaggedForFinalization().get()) {
if (it.getParkedCount().get() == 0) { if (it.getParkedCount().get() == 0) {
// We already know that isFlaggedForFinalization was true at some // We already know that isFlaggedForFinalization was true at some
@ -101,9 +105,22 @@ public class ResourceManager {
// no further attempts are made. // no further attempts are made.
boolean continueFinalizing = it.isFlaggedForFinalization().compareAndSet(true, false); boolean continueFinalizing = it.isFlaggedForFinalization().compareAndSet(true, false);
if (continueFinalizing) { if (continueFinalizing) {
it.doFinalize(context); var futureToCancel = new AtomicReference<Future<Void>>(null);
var performFinalizeNow =
new ThreadLocalAction(false, false, true) {
@Override
protected void perform(ThreadLocalAction.Access access) {
var tmp = futureToCancel.getAndSet(null);
if (tmp == null) {
return;
}
tmp.cancel(false);
it.finalizeNow(context);
items.remove(it.reference); items.remove(it.reference);
} }
};
futureToCancel.set(context.getEnvironment().submitThreadLocal(null, performFinalizeNow));
}
} }
} }
} }
@ -124,7 +141,7 @@ public class ResourceManager {
} }
if (workerThread == null || !workerThread.isAlive()) { if (workerThread == null || !workerThread.isAlive()) {
worker.setKilled(false); worker.setKilled(false);
workerThread = context.getEnvironment().createThread(worker); workerThread = context.getEnvironment().createSystemThread(worker);
workerThread.start(); workerThread.start();
} }
ManagedResource resource = new ManagedResource(object); ManagedResource resource = new ManagedResource(object);
@ -158,7 +175,7 @@ public class ResourceManager {
Item it = items.remove(key); Item it = items.remove(key);
if (it != null) { if (it != null) {
// Finalize unconditionally all other threads are dead by now. // Finalize unconditionally all other threads are dead by now.
it.doFinalize(context); it.finalizeNow(context);
} }
} }
} }
@ -181,7 +198,7 @@ public class ResourceManager {
continue; continue;
} }
it.isFlaggedForFinalization().set(true); it.isFlaggedForFinalization().set(true);
tryFinalize(it); scheduleFinalizationAtSafepoint(it);
} }
if (killed) { if (killed) {
return; return;
@ -229,18 +246,16 @@ public class ResourceManager {
} }
/** /**
* Unconditionally performs the finalization action of this resource. * Performs the finalization action of this resource right now. The thread must be inside of a
* context.
* *
* @param context current execution context * @param context current execution context
*/ */
public void doFinalize(EnsoContext context) { public void finalizeNow(EnsoContext context) {
Object p = context.getThreadManager().enter();
try { try {
InteropLibrary.getUncached(finalizer).execute(finalizer, underlying); InteropLibrary.getUncached(finalizer).execute(finalizer, underlying);
} catch (Exception e) { } catch (Exception e) {
context.getErr().println("Exception in finalizer: " + e.getMessage()); context.getErr().println("Exception in finalizer: " + e.getMessage());
} finally {
context.getThreadManager().leave(p);
} }
} }

View File

@ -113,6 +113,7 @@ class InterpreterContext(
.newBuilder(LanguageInfo.ID) .newBuilder(LanguageInfo.ID)
.allowExperimentalOptions(true) .allowExperimentalOptions(true)
.allowAllAccess(true) .allowAllAccess(true)
.allowCreateThread(false)
.out(output) .out(output)
.err(err) .err(err)
.option(RuntimeOptions.LOG_LEVEL, "WARNING") .option(RuntimeOptions.LOG_LEVEL, "WARNING")

View File

@ -81,7 +81,11 @@ class RuntimeManagementTest extends InterpreterTest {
totalOut = consumeOut totalOut = consumeOut
while (totalOut.length < expect && round < 500) { while (totalOut.length < expect && round < 500) {
round = round + 1 round = round + 1
if (round % 10 == 0) forceGC(); if (round % 10 == 0) {
forceGC();
}
val res = eval("main a b = a * b").execute(7, 6)
assertResult(42)(res.asInt)
Thread.sleep(100) Thread.sleep(100)
totalOut ++= consumeOut totalOut ++= consumeOut
} }