mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 13:02:07 +03:00
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:
parent
4076a64f33
commit
e47eb49ea8
@ -688,6 +688,8 @@
|
||||
- [One can define lazy atom fields][6151]
|
||||
- [Replace IOContexts with Execution Environment and generic Context][6171]
|
||||
- [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
|
||||
[3248]: https://github.com/enso-org/enso/pull/3248
|
||||
@ -794,6 +796,7 @@
|
||||
[6151]: https://github.com/enso-org/enso/pull/6151
|
||||
[6171]: https://github.com/enso-org/enso/pull/6171
|
||||
[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)
|
||||
|
||||
|
@ -9,7 +9,7 @@ import org.junit.Assert;
|
||||
final class InliningBuiltinsOutNode extends Node {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
package org.enso.interpreter.runtime;
|
||||
|
||||
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 org.enso.interpreter.runtime.data.ManagedResource;
|
||||
|
||||
@ -9,8 +11,10 @@ import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
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. */
|
||||
public class ResourceManager {
|
||||
@ -59,7 +63,7 @@ public class ResourceManager {
|
||||
return;
|
||||
}
|
||||
it.getParkedCount().decrementAndGet();
|
||||
tryFinalize(it);
|
||||
scheduleFinalizationAtSafepoint(it);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,7 +79,7 @@ public class ResourceManager {
|
||||
return;
|
||||
}
|
||||
// Unconditional finalization – user controls the resource manually.
|
||||
it.doFinalize(context);
|
||||
it.finalizeNow(context);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,7 +93,7 @@ public class ResourceManager {
|
||||
items.remove(resource.getPhantomReference());
|
||||
}
|
||||
|
||||
private void tryFinalize(Item it) {
|
||||
private void scheduleFinalizationAtSafepoint(Item it) {
|
||||
if (it.isFlaggedForFinalization().get()) {
|
||||
if (it.getParkedCount().get() == 0) {
|
||||
// We already know that isFlaggedForFinalization was true at some
|
||||
@ -101,8 +105,21 @@ public class ResourceManager {
|
||||
// no further attempts are made.
|
||||
boolean continueFinalizing = it.isFlaggedForFinalization().compareAndSet(true, false);
|
||||
if (continueFinalizing) {
|
||||
it.doFinalize(context);
|
||||
items.remove(it.reference);
|
||||
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);
|
||||
}
|
||||
};
|
||||
futureToCancel.set(context.getEnvironment().submitThreadLocal(null, performFinalizeNow));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -124,7 +141,7 @@ public class ResourceManager {
|
||||
}
|
||||
if (workerThread == null || !workerThread.isAlive()) {
|
||||
worker.setKilled(false);
|
||||
workerThread = context.getEnvironment().createThread(worker);
|
||||
workerThread = context.getEnvironment().createSystemThread(worker);
|
||||
workerThread.start();
|
||||
}
|
||||
ManagedResource resource = new ManagedResource(object);
|
||||
@ -158,7 +175,7 @@ public class ResourceManager {
|
||||
Item it = items.remove(key);
|
||||
if (it != null) {
|
||||
// Finalize unconditionally – all other threads are dead by now.
|
||||
it.doFinalize(context);
|
||||
it.finalizeNow(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -181,7 +198,7 @@ public class ResourceManager {
|
||||
continue;
|
||||
}
|
||||
it.isFlaggedForFinalization().set(true);
|
||||
tryFinalize(it);
|
||||
scheduleFinalizationAtSafepoint(it);
|
||||
}
|
||||
if (killed) {
|
||||
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
|
||||
*/
|
||||
public void doFinalize(EnsoContext context) {
|
||||
Object p = context.getThreadManager().enter();
|
||||
public void finalizeNow(EnsoContext context) {
|
||||
try {
|
||||
InteropLibrary.getUncached(finalizer).execute(finalizer, underlying);
|
||||
} catch (Exception e) {
|
||||
context.getErr().println("Exception in finalizer: " + e.getMessage());
|
||||
} finally {
|
||||
context.getThreadManager().leave(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,6 +113,7 @@ class InterpreterContext(
|
||||
.newBuilder(LanguageInfo.ID)
|
||||
.allowExperimentalOptions(true)
|
||||
.allowAllAccess(true)
|
||||
.allowCreateThread(false)
|
||||
.out(output)
|
||||
.err(err)
|
||||
.option(RuntimeOptions.LOG_LEVEL, "WARNING")
|
||||
|
@ -81,7 +81,11 @@ class RuntimeManagementTest extends InterpreterTest {
|
||||
totalOut = consumeOut
|
||||
while (totalOut.length < expect && round < 500) {
|
||||
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)
|
||||
totalOut ++= consumeOut
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user