From 9ecb3ad817eac0d9bfe834979ca16f86dc49790e Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 21 Aug 2023 16:23:51 +0200 Subject: [PATCH] classlib: support ReferenceQueue.remove --- .../java/lang/ref/TReferenceQueue.java | 120 ++++++++++++++++++ .../util/concurrent/TArrayBlockingQueue.java | 2 +- .../ref/ReferenceQueueGenerator.java | 13 +- .../ref/WeakReferenceDependencyListener.java | 35 +++-- .../ref/WeakReferenceGenerator.java | 2 +- .../java/lang/ref/WeakReferenceTest.java | 45 ++++++- 6 files changed, 197 insertions(+), 20 deletions(-) diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/ref/TReferenceQueue.java b/classlib/src/main/java/org/teavm/classlib/java/lang/ref/TReferenceQueue.java index a653ad4e5..2a2e4a199 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/ref/TReferenceQueue.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/ref/TReferenceQueue.java @@ -15,8 +15,128 @@ */ package org.teavm.classlib.java.lang.ref; +import org.teavm.classlib.PlatformDetector; +import org.teavm.classlib.java.lang.TThread; +import org.teavm.classlib.java.lang.TThreadInterruptHandler; +import org.teavm.interop.Async; +import org.teavm.interop.AsyncCallback; +import org.teavm.platform.Platform; +import org.teavm.platform.PlatformRunnable; +import org.teavm.runtime.EventQueue; + public class TReferenceQueue { + private RemoveCallback firstCallback; + private RemoveCallback lastCallback; + public TReference poll() { return null; } + + public TReference remove() throws InterruptedException { + return remove(0); + } + + @Async + public native TReference remove(long timeout) throws InterruptedException; + + public void remove(long timeout, AsyncCallback> callback) { + var ref = poll(); + if (ref != null) { + callback.complete(ref); + } else { + var callbackWrapper = new RemoveCallback(callback); + if (timeout != 0) { + callbackWrapper.id = PlatformDetector.isLowLevel() + ? EventQueue.offer(callbackWrapper, timeout + System.currentTimeMillis()) + : Platform.schedule(callbackWrapper, (int) timeout); + } + TThread.currentThread().interruptHandler = callbackWrapper; + registerCallback(callbackWrapper); + } + } + + private void registerCallback(RemoveCallback callback) { + callback.prev = lastCallback; + if (lastCallback != null) { + lastCallback.next = callback; + } else { + firstCallback = callback; + } + lastCallback = callback; + } + + protected boolean reportNext(TReference ref) { + if (firstCallback == null) { + return false; + } + var callback = firstCallback; + callback.complete(ref); + return true; + } + + private class RemoveCallback implements EventQueue.Event, PlatformRunnable, AsyncCallback>, + TThreadInterruptHandler { + RemoveCallback next; + RemoveCallback prev; + int id; + AsyncCallback> callback; + + RemoveCallback(AsyncCallback> callback) { + this.callback = callback; + } + + @Override + public void run() { + if (PlatformDetector.isLowLevel()) { + EventQueue.kill(id); + } else { + Platform.killSchedule(id); + } + complete(null); + } + + @Override + public void complete(TReference result) { + var callback = this.callback; + if (callback != null) { + remove(); + callback.complete(result); + } + } + + @Override + public void error(Throwable e) { + var callback = this.callback; + if (callback != null) { + remove(); + callback.error(e); + } + } + + @Override + public void interrupted() { + var callback = this.callback; + if (callback != null) { + remove(); + callback.error(new InterruptedException()); + } + } + + private void remove() { + TThread.currentThread().interruptHandler = null; + callback = null; + if (prev != null) { + prev.next = next; + } else { + firstCallback = next; + } + if (next != null) { + next.prev = prev; + } else { + lastCallback = prev; + } + next = null; + prev = null; + } + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TArrayBlockingQueue.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TArrayBlockingQueue.java index f3abf38db..7f24e76ac 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TArrayBlockingQueue.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TArrayBlockingQueue.java @@ -447,7 +447,7 @@ public class TArrayBlockingQueue extends TAbstractQueue implements TBlocki TThread.currentThread().interruptHandler = handler; } - class WaitHandler implements PlatformRunnable, TThreadInterruptHandler, EventQueue.Event { + static class WaitHandler implements PlatformRunnable, TThreadInterruptHandler, EventQueue.Event { AsyncCallback callback; boolean complete; int timerId; diff --git a/core/src/main/java/org/teavm/backend/javascript/intrinsics/ref/ReferenceQueueGenerator.java b/core/src/main/java/org/teavm/backend/javascript/intrinsics/ref/ReferenceQueueGenerator.java index cd761a3de..dafd958a4 100644 --- a/core/src/main/java/org/teavm/backend/javascript/intrinsics/ref/ReferenceQueueGenerator.java +++ b/core/src/main/java/org/teavm/backend/javascript/intrinsics/ref/ReferenceQueueGenerator.java @@ -16,6 +16,7 @@ package org.teavm.backend.javascript.intrinsics.ref; import java.io.IOException; +import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.spi.Generator; @@ -27,6 +28,8 @@ public class ReferenceQueueGenerator implements Generator { private static final FieldReference INNER_FIELD = new FieldReference(ReferenceQueue.class.getName(), "inner"); private static final FieldReference REGISTRY_FIELD = new FieldReference(ReferenceQueue.class.getName(), "registry"); + private static final MethodReference REPORT_METHOD = new MethodReference(ReferenceQueue.class, + "reportNext", Reference.class, boolean.class); @Override public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { @@ -43,10 +46,14 @@ public class ReferenceQueueGenerator implements Generator { private void generateInitMethod(GeneratorContext context, SourceWriter writer) throws IOException { writer.append(context.getParameterName(0)).append(".").appendField(INNER_FIELD).ws().append("=") .ws().append("[];").softNewLine(); + writer.append(context.getParameterName(0)).append(".").appendField(REGISTRY_FIELD).ws().append("=") - .ws().append("new $rt_globals.FinalizationRegistry(x").ws().append("=>").ws() - .append(context.getParameterName(0)).appendField(INNER_FIELD) - .append(".push(x));").softNewLine(); + .ws().append("new $rt_globals.FinalizationRegistry(ref").ws().append("=>").appendBlockStart(); + writer.appendIf().append("!").appendMethodBody(REPORT_METHOD).append("(") + .append(context.getParameterName(0)).append(",").ws().append("ref))").ws(); + writer.append(context.getParameterName(0)).append(".").appendField(INNER_FIELD) + .append(".push(ref)").softNewLine(); + writer.appendBlockEnd().append(");").softNewLine(); } private void generatePollMethod(GeneratorContext context, SourceWriter writer) throws IOException { diff --git a/core/src/main/java/org/teavm/backend/javascript/intrinsics/ref/WeakReferenceDependencyListener.java b/core/src/main/java/org/teavm/backend/javascript/intrinsics/ref/WeakReferenceDependencyListener.java index ffeb72d01..f49982931 100644 --- a/core/src/main/java/org/teavm/backend/javascript/intrinsics/ref/WeakReferenceDependencyListener.java +++ b/core/src/main/java/org/teavm/backend/javascript/intrinsics/ref/WeakReferenceDependencyListener.java @@ -20,31 +20,31 @@ import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.DependencyNode; import org.teavm.dependency.MethodDependency; import org.teavm.model.FieldReference; import org.teavm.model.MethodReference; public class WeakReferenceDependencyListener extends AbstractDependencyListener { + private DependencyNode initRef; + @Override public void methodReached(DependencyAgent agent, MethodDependency method) { + initRef = agent.createNode(); if (method.getMethod().getOwnerName().equals(WeakReference.class.getName())) { referenceMethodReached(agent, method); } else if (method.getMethod().getOwnerName().equals(ReferenceQueue.class.getName())) { - agent.linkField(new FieldReference(ReferenceQueue.class.getName(), "inner")); - agent.linkField(new FieldReference(ReferenceQueue.class.getName(), "registry")); + queueMethodReached(agent, method); } } private void referenceMethodReached(DependencyAgent agent, MethodDependency method) { switch (method.getMethod().getName()) { case "": { - if (method.getParameterCount() == 2) { + if (method.getParameterCount() == 3) { var field = agent.linkField(new FieldReference(method.getMethod().getOwnerName(), "value")); - method.getVariable(1).connect(field.getValue()); - var pollResult = agent - .linkMethod(new MethodReference(ReferenceQueue.class, "poll", Reference.class)) - .getResult(); - method.getVariable(0).connect(pollResult); + method.getVariable(2).connect(field.getValue()); + method.getVariable(1).connect(initRef); } break; } @@ -55,4 +55,23 @@ public class WeakReferenceDependencyListener extends AbstractDependencyListener } } } + + private void queueMethodReached(DependencyAgent agent, MethodDependency method) { + switch (method.getMethod().getName()) { + case "poll": + initRef.connect(method.getResult()); + break; + case "": + agent.linkField(new FieldReference(ReferenceQueue.class.getName(), "inner")); + agent.linkField(new FieldReference(ReferenceQueue.class.getName(), "registry")); + break; + case "registerCallback": { + var reportMethod = agent.linkMethod(new MethodReference(ReferenceQueue.class, + "reportNext", Reference.class, boolean.class)); + initRef.connect(reportMethod.getVariable(1)); + reportMethod.use(); + break; + } + } + } } diff --git a/core/src/main/java/org/teavm/backend/javascript/intrinsics/ref/WeakReferenceGenerator.java b/core/src/main/java/org/teavm/backend/javascript/intrinsics/ref/WeakReferenceGenerator.java index 8560cd8c1..68200e5ca 100644 --- a/core/src/main/java/org/teavm/backend/javascript/intrinsics/ref/WeakReferenceGenerator.java +++ b/core/src/main/java/org/teavm/backend/javascript/intrinsics/ref/WeakReferenceGenerator.java @@ -58,7 +58,7 @@ public class WeakReferenceGenerator implements Generator { writer.append(context.getParameterName(2)).append(".") .appendField(new FieldReference(ReferenceQueue.class.getName(), "registry")) .append(".").append("register(").append(context.getParameterName(1)) - .append(",").ws().append("value);").softNewLine(); + .append(",").ws().append(context.getParameterName(0)).append(");").softNewLine(); writer.appendBlockEnd(); } diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/ref/WeakReferenceTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/ref/WeakReferenceTest.java index 13e8bb1e1..3ff5e3bf6 100644 --- a/tests/src/test/java/org/teavm/classlib/java/lang/ref/WeakReferenceTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/lang/ref/WeakReferenceTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; +import java.util.concurrent.ArrayBlockingQueue; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,26 +37,29 @@ public class WeakReferenceTest { @Test @Ignore - public void deref() { + public void deref() throws InterruptedException { var ref = createAndTestRef(null); for (var i = 0; i < 100; ++i) { - lastNode = createNodes(18); + lastNode = createNodes(20); + Thread.sleep(1); if (ref.get() == null) { break; } + assertNotNull(lastNode); } assertNull(ref.get()); } @Test @Ignore - public void refQueue() { + public void refQueue() throws InterruptedException { var queue = new ReferenceQueue<>(); var ref = createAndTestRef(queue); var hasValue = false; for (var i = 0; i < 100; ++i) { - lastNode = createNodes(18); + lastNode = createNodes(20); + Thread.sleep(1); var polledRef = queue.poll(); if (polledRef != null) { hasValue = true; @@ -64,13 +68,41 @@ public class WeakReferenceTest { } else { assertNotNull(ref.get()); } + assertNotNull(lastNode); } assertTrue(hasValue); } + @Test + public void queueRemove() throws InterruptedException { + var queue = new ReferenceQueue<>(); + var ref = createAndTestRef(queue); + var threadQueue = new ArrayBlockingQueue<>(4); + var thread = new Thread(() -> { + try { + threadQueue.add(queue.remove()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + thread.setDaemon(true); + thread.start(); + Object value = null; + for (var i = 0; i < 100; ++i) { + lastNode = createNodes(20); + Thread.sleep(1); + value = threadQueue.poll(); + if (value != null) { + break; + } + assertNotNull(lastNode); + } + assertSame(ref, value); + } + private WeakReference createAndTestRef(ReferenceQueue queue) { - var obj = new byte[4 * 1024 * 1024]; - var ref = new WeakReference(obj, queue); + var obj = new Object(); + var ref = new WeakReference<>(obj, queue); assertSame(obj, ref.get()); return ref; } @@ -96,7 +128,6 @@ public class WeakReferenceTest { private class Node { Node left; Node right; - byte[] data = new byte[64]; Node(Node left, Node right) { this.left = left;