classlib: support ReferenceQueue.remove

This commit is contained in:
Alexey Andreev 2023-08-21 16:23:51 +02:00
parent df40dedba3
commit 9ecb3ad817
6 changed files with 197 additions and 20 deletions

View File

@ -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<T> {
private RemoveCallback firstCallback;
private RemoveCallback lastCallback;
public TReference<T> poll() {
return null;
}
public TReference<T> remove() throws InterruptedException {
return remove(0);
}
@Async
public native TReference<T> remove(long timeout) throws InterruptedException;
public void remove(long timeout, AsyncCallback<TReference<T>> 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<T> ref) {
if (firstCallback == null) {
return false;
}
var callback = firstCallback;
callback.complete(ref);
return true;
}
private class RemoveCallback implements EventQueue.Event, PlatformRunnable, AsyncCallback<TReference<T>>,
TThreadInterruptHandler {
RemoveCallback next;
RemoveCallback prev;
int id;
AsyncCallback<TReference<T>> callback;
RemoveCallback(AsyncCallback<TReference<T>> 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<T> 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;
}
}
}

View File

@ -447,7 +447,7 @@ public class TArrayBlockingQueue<E> extends TAbstractQueue<E> implements TBlocki
TThread.currentThread().interruptHandler = handler;
}
class WaitHandler implements PlatformRunnable, TThreadInterruptHandler, EventQueue.Event {
static class WaitHandler implements PlatformRunnable, TThreadInterruptHandler, EventQueue.Event {
AsyncCallback<Boolean> callback;
boolean complete;
int timerId;

View File

@ -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 {

View File

@ -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 "<init>": {
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 "<init>":
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;
}
}
}
}

View File

@ -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();
}

View File

@ -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<Object> createAndTestRef(ReferenceQueue<Object> queue) {
var obj = new byte[4 * 1024 * 1024];
var ref = new WeakReference<Object>(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;