Skip to content

Commit

Permalink
feat: abort controller
Browse files Browse the repository at this point in the history
  • Loading branch information
4e6 committed Apr 9, 2024
1 parent 6f2e2f1 commit 27df630
Show file tree
Hide file tree
Showing 10 changed files with 440 additions and 24 deletions.
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,8 @@ lazy val `polyglot-ydoc-server` = project
autoScalaLibrary := false,
Compile / run / fork := true,
Test / fork := true,
run / connectInput := true,
commands += WithDebugCommand.withDebug,
modulePath := {
JPMSUtils.filterModulesFromUpdate(
update.value,
Expand Down Expand Up @@ -1162,7 +1164,7 @@ lazy val `polyglot-ydoc-server` = project
},
libraryDependencies ++= Seq(
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion % "provided",
"org.graalvm.polyglot" % "inspect" % graalMavenPackagesVersion % "provided",
"org.graalvm.polyglot" % "inspect" % graalMavenPackagesVersion % "runtime",
"org.graalvm.polyglot" % "js" % graalMavenPackagesVersion % "provided,runtime",
"io.helidon.webclient" % "helidon-webclient-websocket" % helidonVersion,
"io.helidon.webserver" % "helidon-webserver-websocket" % helidonVersion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public static URI createTempFile(String name) throws IOException {
target.toFile().deleteOnExit();

try (InputStream in = ClasspathResource.class.getResourceAsStream(name)) {
if (in == null) {
throw new IOException("Cannot find " + name);
}
Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package org.enso.polyfill;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;

import org.enso.polyfill.crypto.CryptoPolyfill;
import org.enso.polyfill.encoding.EncodingPolyfill;
import org.enso.polyfill.timers.TimersPolyfill;
import org.enso.polyfill.web.AbortControllerPolyfill;
import org.enso.polyfill.web.EventTargetPolyfill;
import org.enso.polyfill.websocket.WebSocketPolyfill;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
Expand All @@ -19,11 +23,8 @@ private Main() {
}

public static void main(String[] args) throws Exception {
var demo = ClasspathResource.createTempFile(YDOC_SERVER_PATH);
if (demo == null) {
throw new IOException("Cannot find " + YDOC_SERVER_PATH);
}
var commonJsRoot = new File(demo).getParent();
var ydoc = ClasspathResource.createTempFile(YDOC_SERVER_PATH);
var commonJsRoot = new File(ydoc).getParent();

HostAccess hostAccess = HostAccess.newBuilder(HostAccess.EXPLICIT)
.allowArrayAccess(true)
Expand All @@ -35,22 +36,38 @@ public static void main(String[] args) throws Exception {
.allowExperimentalOptions(true)
.option("js.commonjs-require", "true")
.option("js.commonjs-require-cwd", commonJsRoot);
var chromePort = Integer.getInteger("inspectPort", -1);
var chromePort = 34567;// Integer.getInteger("inspectPort", -1);
if (chromePort > 0) {
b.option("inspect", ":" + chromePort);
}

try (var executor = Executors.newSingleThreadExecutor()) {
var webSocketPolyfill = new WebSocketPolyfill(executor);
var demoJs = Source.newBuilder("js", demo.toURL())
var ydocJs = Source.newBuilder("js", ydoc.toURL())
.mimeType("application/javascript+module")
.build();

CompletableFuture
.supplyAsync(b::build, executor)
.thenAcceptAsync(ctx -> {
var eventTarget = new EventTargetPolyfill(executor);
eventTarget.initialize(ctx);

var timers = new TimersPolyfill(executor);
timers.initialize(ctx);

var crypto = new CryptoPolyfill();
crypto.initialize(ctx);

var encoding = new EncodingPolyfill();
encoding.initialize(ctx);

var abortController = new AbortControllerPolyfill();
abortController.initialize(ctx);

var webSocketPolyfill = new WebSocketPolyfill(executor);
webSocketPolyfill.initialize(ctx);
ctx.eval(demoJs);

ctx.eval(ydocJs);
}, executor)
.get();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.enso.polyfill.web;

import java.util.Arrays;
import java.util.UUID;
import org.enso.polyfill.Polyfill;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyExecutable;

public final class AbortControllerPolyfill implements ProxyExecutable, Polyfill {

private static final String RANDOM_UUID = "random-uuid";

private static final String ABORT_CONTROLLER_POLYFILL_JS = "abort-controller-polyfill.js";

public AbortControllerPolyfill() {
}

@Override
public void initialize(Context ctx) {
Source abortControllerPolyfillJs = Source
.newBuilder("js", AbortControllerPolyfill.class.getResource(ABORT_CONTROLLER_POLYFILL_JS))
.buildLiteral();

ctx.eval(abortControllerPolyfillJs).execute(this);
}

@Override
public Object execute(Value... arguments) {
var command = arguments[0].asString();
System.err.println(command + " " + Arrays.toString(arguments));

return switch (command) {
case RANDOM_UUID ->
UUID.randomUUID().toString();

default ->
throw new IllegalStateException(command);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.enso.polyfill.web;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;

import org.enso.polyfill.Polyfill;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyExecutable;

public final class EventTargetPolyfill implements ProxyExecutable, Polyfill {

private static final String NEW_EVENT_TARGET = "new-event-target";
private static final String ADD_EVENT_LISTENER = "add-event-listener";
private static final String REMOVE_EVENT_LISTENER = "remove-event-listener";
private static final String DISPATCH_EVENT = "dispatch-event";

private static final String EVENT_TARGET_POLYFILL_JS = "event-target-polyfill.js";

private final ExecutorService executor;

public EventTargetPolyfill(ExecutorService executor) {
this.executor = executor;
}

@Override
public void initialize(Context ctx) {
Source eventTargetPolyfillJs = Source
.newBuilder("js", EventTargetPolyfill.class.getResource(EVENT_TARGET_POLYFILL_JS))
.buildLiteral();

ctx.eval(eventTargetPolyfillJs).execute(this);
}

@Override
public Object execute(Value... arguments) {
var command = arguments[0].asString();
System.err.println(command + " " + Arrays.toString(arguments));

return switch (command) {
case NEW_EVENT_TARGET ->
new EventStore(executor, new HashMap<>());

case ADD_EVENT_LISTENER -> {
var store = arguments[1].as(EventStore.class);
var type = arguments[2].asString();
var listener = arguments[3];

store.addEventListener(type, listener);
yield null;
}

case REMOVE_EVENT_LISTENER -> {
var store = arguments[1].as(EventStore.class);
var type = arguments[2].asString();
var listener = arguments[3];

store.removeEventListener(type, listener);
yield null;
}

case DISPATCH_EVENT -> {
var store = arguments[1].as(EventStore.class);
var type = arguments[2].asString();
var event = arguments[3];

store.dispatchEvent(type, event);
yield null;
}

default ->
throw new IllegalStateException(command);
};
}

private static final class EventStore {

private final ExecutorService executor;
private final Map<String, Set<Value>> listeners;

EventStore(ExecutorService executor, Map<String, Set<Value>> listeners) {
this.executor = executor;
this.listeners = listeners;
}

public void addEventListener(String type, Value listener) {
listeners.compute(type, (k, v) -> {
var set = v == null ? new HashSet<Value>() : v;
set.add(listener);
return set;
});
}

public void removeEventListener(String type, Value listener) {
listeners.compute(type, (k, v) -> {
if (v == null) {
return v;
} else {
v.remove(listener);
return v.isEmpty() ? null : v;
}
});
}

public void dispatchEvent(String type, Value event) {
listeners.getOrDefault(type, Set.of()).forEach(listener -> {
try {
listener.executeVoid(event);
} catch (Exception e) {
System.err.println("Error dispatching event [" + type + "] " + listener + " " + event + " " + e);
}
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
import java.util.concurrent.ExecutorService;

import org.enso.polyfill.Polyfill;
import org.enso.polyfill.crypto.CryptoPolyfill;
import org.enso.polyfill.encoding.EncodingPolyfill;
import org.enso.polyfill.timers.TimersPolyfill;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
Expand Down Expand Up @@ -53,15 +50,6 @@ public WebSocketPolyfill(ExecutorService executor) {

@Override
public void initialize(Context ctx) {
var timers = new TimersPolyfill(executor);
timers.initialize(ctx);

var crypto = new CryptoPolyfill();
crypto.initialize(ctx);

var encoding = new EncodingPolyfill();
encoding.initialize(ctx);

Source webSocketPolyfillJs = Source
.newBuilder("js", WebSocketPolyfill.class.getResource(WEBSOCKET_POLYFILL_JS))
.buildLiteral();
Expand Down Expand Up @@ -127,7 +115,7 @@ public Object execute(Value... arguments) {
var handleMessage = arguments[6];
var connection = new WebSocketConnection(executor, new HashMap<>(), handleOpen, handleClose, handleError, handleMessage);

URI uri = null;
URI uri;
try {
uri = new URI(urlString);
} catch (URISyntaxException ex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
(function (jvm) {

const ABORT_ERR = { code: 20, name: "AbortError" };

class AbortSignal extends EventTarget {

#aborted;
#reason;

constructor(aborted, reason) {
super();
this.#aborted = aborted === undefined ? false : aborted;
this.#reason = reason === undefined ? ABORT_ERR : reason;
}

get aborted() {
return this.#aborted;
}

set aborted(value) {
this.#aborted = value === undefined ? false : value;
}

get reason() {
return this.#reason;
}

set reason(value) {
const reasonValue = value === undefined ? ABORT_ERR : value;
this.#reason = reasonValue;
}

set onabort(callback) {
this.addEventListener('abort', callback);
}

static abort(reason) {
return new AbortSignal(true, reason);
}

throwIfAborted() {
if (this.#aborted) {
throw this.#reason;
}
}
}

class AbortController {

#signal;

constructor() {
const signal = new AbortSignal();
signal.addEventListener('abort', (event) => {
signal.aborted = true;
signal.reason = event.reason;
})
this.#signal = signal;
}

get signal() {
return this.#signal;
}

abort(reason) {
this.#signal.dispatchEvent({type: 'abort', reason: reason});
}
}

globalThis.AbortController = AbortController;

})
Loading

0 comments on commit 27df630

Please sign in to comment.