summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2020-10-14 14:04:09 +0200
committerGitHub <noreply@github.com>2020-10-14 14:04:09 +0200
commit135053486c4bd112ebd7d0b25c94a8dd346472e6 (patch)
tree24eae514d8de332a0968de869172f651b30b5078 /cli
parent10654fa95553866c63a56a7f84c7ec47fb7aac9c (diff)
fix: top-level-await module execution (#7946)
This commit changes implementation of top-level-await in "deno_core". Previously promise returned from module evaluation was not awaited, leading to out-of-order execution of modules that have TLA. It's been fixed by changing "JsRuntime::mod_evaluate" to be an async function that resolves when the promise returned from module evaluation also resolves. When waiting for promise resolution event loop is polled repeatedly, until there are no more dynamic imports or pending ops.
Diffstat (limited to 'cli')
-rw-r--r--cli/ops/worker_host.rs10
-rw-r--r--cli/tests/integration_tests.rs22
-rw-r--r--cli/tests/tla/a.js3
-rw-r--r--cli/tests/tla/b.js7
-rw-r--r--cli/tests/tla/c.js3
-rw-r--r--cli/tests/tla/d.js6
-rw-r--r--cli/tests/tla/order.js1
-rw-r--r--cli/tests/tla/parent.js9
-rw-r--r--cli/tests/tla2/a.js5
-rw-r--r--cli/tests/tla2/b.js5
-rw-r--r--cli/tests/tla3/b.js7
-rw-r--r--cli/tests/tla3/timeout_loop.js23
-rw-r--r--cli/tests/top_level_await_circular.js8
-rw-r--r--cli/tests/top_level_await_circular.out7
-rw-r--r--cli/tests/top_level_await_loop.js18
-rw-r--r--cli/tests/top_level_await_loop.out5
-rw-r--r--cli/tests/top_level_await_order.js21
-rw-r--r--cli/tests/top_level_await_order.out2
-rw-r--r--cli/tests/top_level_await_unresolved.js1
-rw-r--r--cli/tests/top_level_await_unresolved.out1
-rw-r--r--cli/worker.rs2
21 files changed, 163 insertions, 3 deletions
diff --git a/cli/ops/worker_host.rs b/cli/ops/worker_host.rs
index 8ebf8b9e6..ad915f7f7 100644
--- a/cli/ops/worker_host.rs
+++ b/cli/ops/worker_host.rs
@@ -153,9 +153,15 @@ fn run_worker_thread(
rt.block_on(load_future)
};
- if let Err(e) = result {
- let mut sender = worker.internal_channels.sender.clone();
+ let mut sender = worker.internal_channels.sender.clone();
+ // If sender is closed it means that worker has already been closed from
+ // within using "globalThis.close()"
+ if sender.is_closed() {
+ return;
+ }
+
+ if let Err(e) = result {
sender
.try_send(WorkerEvent::TerminalError(e))
.expect("Failed to post message to host");
diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs
index 90dc5a4a8..66a83a655 100644
--- a/cli/tests/integration_tests.rs
+++ b/cli/tests/integration_tests.rs
@@ -2421,6 +2421,28 @@ itest!(wasm_unreachable {
exit_code: 1,
});
+itest!(top_level_await_order {
+ args: "run --allow-read top_level_await_order.js",
+ output: "top_level_await_order.out",
+});
+
+itest!(top_level_await_loop {
+ args: "run --allow-read top_level_await_loop.js",
+ output: "top_level_await_loop.out",
+});
+
+itest!(top_level_await_circular {
+ args: "run --allow-read top_level_await_circular.js",
+ output: "top_level_await_circular.out",
+ exit_code: 1,
+});
+
+itest!(top_level_await_unresolved {
+ args: "run top_level_await_unresolved.js",
+ output: "top_level_await_unresolved.out",
+ exit_code: 1,
+});
+
itest!(top_level_await {
args: "run --allow-read top_level_await.js",
output: "top_level_await.out",
diff --git a/cli/tests/tla/a.js b/cli/tests/tla/a.js
new file mode 100644
index 000000000..c3ef3f7db
--- /dev/null
+++ b/cli/tests/tla/a.js
@@ -0,0 +1,3 @@
+import order from "./order.js";
+
+order.push("b");
diff --git a/cli/tests/tla/b.js b/cli/tests/tla/b.js
new file mode 100644
index 000000000..3271c92d8
--- /dev/null
+++ b/cli/tests/tla/b.js
@@ -0,0 +1,7 @@
+import order from "./order.js";
+
+await new Promise((resolve) => {
+ setTimeout(resolve, 200);
+});
+
+order.push("a");
diff --git a/cli/tests/tla/c.js b/cli/tests/tla/c.js
new file mode 100644
index 000000000..806eb0a8b
--- /dev/null
+++ b/cli/tests/tla/c.js
@@ -0,0 +1,3 @@
+import order from "./order.js";
+
+order.push("c");
diff --git a/cli/tests/tla/d.js b/cli/tests/tla/d.js
new file mode 100644
index 000000000..2b5fd3c45
--- /dev/null
+++ b/cli/tests/tla/d.js
@@ -0,0 +1,6 @@
+import order from "./order.js";
+
+const end = Date.now() + 500;
+while (end < Date.now()) {}
+
+order.push("d");
diff --git a/cli/tests/tla/order.js b/cli/tests/tla/order.js
new file mode 100644
index 000000000..f213a562c
--- /dev/null
+++ b/cli/tests/tla/order.js
@@ -0,0 +1 @@
+export default ["order"];
diff --git a/cli/tests/tla/parent.js b/cli/tests/tla/parent.js
new file mode 100644
index 000000000..1ecc15463
--- /dev/null
+++ b/cli/tests/tla/parent.js
@@ -0,0 +1,9 @@
+import order from "./order.js";
+import "./a.js";
+import "./b.js";
+import "./c.js";
+import "./d.js";
+
+order.push("parent");
+
+export default order;
diff --git a/cli/tests/tla2/a.js b/cli/tests/tla2/a.js
new file mode 100644
index 000000000..d07bcb94d
--- /dev/null
+++ b/cli/tests/tla2/a.js
@@ -0,0 +1,5 @@
+export default class Foo {
+ constructor(message) {
+ this.message = message;
+ }
+}
diff --git a/cli/tests/tla2/b.js b/cli/tests/tla2/b.js
new file mode 100644
index 000000000..68e357c1e
--- /dev/null
+++ b/cli/tests/tla2/b.js
@@ -0,0 +1,5 @@
+export default class Bar {
+ constructor(message) {
+ this.message = message;
+ }
+}
diff --git a/cli/tests/tla3/b.js b/cli/tests/tla3/b.js
new file mode 100644
index 000000000..b74c659e4
--- /dev/null
+++ b/cli/tests/tla3/b.js
@@ -0,0 +1,7 @@
+import { foo } from "./timeout_loop.js";
+import { collection } from "../top_level_await_circular.js";
+
+console.log("collection in b", collection);
+console.log("foo in b", foo);
+
+export const a = "a";
diff --git a/cli/tests/tla3/timeout_loop.js b/cli/tests/tla3/timeout_loop.js
new file mode 100644
index 000000000..860e6cd2a
--- /dev/null
+++ b/cli/tests/tla3/timeout_loop.js
@@ -0,0 +1,23 @@
+export const foo = "foo";
+
+export function delay(ms) {
+ return new Promise((res) =>
+ setTimeout(() => {
+ res();
+ }, ms)
+ );
+}
+
+let i = 0;
+
+async function timeoutLoop() {
+ await delay(1000);
+ console.log("timeout loop", i);
+ i++;
+ if (i > 5) {
+ return;
+ }
+ timeoutLoop();
+}
+
+timeoutLoop();
diff --git a/cli/tests/top_level_await_circular.js b/cli/tests/top_level_await_circular.js
new file mode 100644
index 000000000..ff2964b6a
--- /dev/null
+++ b/cli/tests/top_level_await_circular.js
@@ -0,0 +1,8 @@
+import { foo } from "./tla3/timeout_loop.js";
+
+export const collection = [];
+
+const mod = await import("./tla3/b.js");
+
+console.log("foo in main", foo);
+console.log("mod", mod);
diff --git a/cli/tests/top_level_await_circular.out b/cli/tests/top_level_await_circular.out
new file mode 100644
index 000000000..d47564441
--- /dev/null
+++ b/cli/tests/top_level_await_circular.out
@@ -0,0 +1,7 @@
+timeout loop 0
+timeout loop 1
+timeout loop 2
+timeout loop 3
+timeout loop 4
+timeout loop 5
+error: Dynamically imported module evaluation is still pending but there are no pending ops. This situation is often caused by unresolved promise.
diff --git a/cli/tests/top_level_await_loop.js b/cli/tests/top_level_await_loop.js
new file mode 100644
index 000000000..384f8d0ed
--- /dev/null
+++ b/cli/tests/top_level_await_loop.js
@@ -0,0 +1,18 @@
+const importsDir = Deno.readDirSync(Deno.realPathSync("./tla2"));
+
+const resolvedPaths = [];
+
+for (const { name } of importsDir) {
+ const filePath = Deno.realPathSync(`./tla2/${name}`);
+ resolvedPaths.push(filePath);
+}
+
+resolvedPaths.sort();
+
+for (const filePath of resolvedPaths) {
+ console.log("loading", filePath);
+ const mod = await import(`file://${filePath}`);
+ console.log("loaded", mod);
+}
+
+console.log("all loaded");
diff --git a/cli/tests/top_level_await_loop.out b/cli/tests/top_level_await_loop.out
new file mode 100644
index 000000000..d704e3afd
--- /dev/null
+++ b/cli/tests/top_level_await_loop.out
@@ -0,0 +1,5 @@
+loading [WILDCARD]a.js
+loaded Module { default: [Function: Foo], [Symbol(Symbol.toStringTag)]: "Module" }
+loading [WILDCARD]b.js
+loaded Module { default: [Function: Bar], [Symbol(Symbol.toStringTag)]: "Module" }
+all loaded
diff --git a/cli/tests/top_level_await_order.js b/cli/tests/top_level_await_order.js
new file mode 100644
index 000000000..30659cdfb
--- /dev/null
+++ b/cli/tests/top_level_await_order.js
@@ -0,0 +1,21 @@
+// Ported from Node
+// https://github.com/nodejs/node/blob/54746bb763ebea0dc7e99d88ff4b379bcd680964/test/es-module/test-esm-tla.mjs
+
+const { default: order } = await import("./tla/parent.js");
+
+console.log("order", JSON.stringify(order));
+
+if (
+ !(
+ order[0] === "order" &&
+ order[1] === "b" &&
+ order[2] === "c" &&
+ order[3] === "d" &&
+ order[4] === "a" &&
+ order[5] === "parent"
+ )
+) {
+ throw new Error("TLA wrong order");
+}
+
+console.log("TLA order correct");
diff --git a/cli/tests/top_level_await_order.out b/cli/tests/top_level_await_order.out
new file mode 100644
index 000000000..4cc27858c
--- /dev/null
+++ b/cli/tests/top_level_await_order.out
@@ -0,0 +1,2 @@
+order ["order","b","c","d","a","parent"]
+TLA order correct
diff --git a/cli/tests/top_level_await_unresolved.js b/cli/tests/top_level_await_unresolved.js
new file mode 100644
index 000000000..231a8cd63
--- /dev/null
+++ b/cli/tests/top_level_await_unresolved.js
@@ -0,0 +1 @@
+await new Promise(() => {});
diff --git a/cli/tests/top_level_await_unresolved.out b/cli/tests/top_level_await_unresolved.out
new file mode 100644
index 000000000..77395f5d0
--- /dev/null
+++ b/cli/tests/top_level_await_unresolved.out
@@ -0,0 +1 @@
+error: Module evaluation is still pending but there are no pending ops or dynamic imports. This situation is often caused by unresolved promise.
diff --git a/cli/worker.rs b/cli/worker.rs
index 97e39d20c..d9d9f61c1 100644
--- a/cli/worker.rs
+++ b/cli/worker.rs
@@ -191,7 +191,7 @@ impl Worker {
) -> Result<(), AnyError> {
let id = self.preload_module(module_specifier).await?;
self.wait_for_inspector_session();
- self.js_runtime.mod_evaluate(id)
+ self.js_runtime.mod_evaluate(id).await
}
/// Returns a way to communicate with the Worker from other threads.