From 8a4e389bca8d1061311203eb003e12eb183b8d8d Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Tue, 6 Sep 2022 23:08:37 +0530 Subject: [PATCH] perf(runtime): short-circuit `queue_async_op` for Poll::Ready (#15773) --- cli/bench/async_ops.js | 19 ++ cli/bench/tcp.js | 22 +++ cli/tests/integration/mod.rs | 4 +- cli/tests/testdata/044_bad_resource.ts.out | 2 +- cli/tests/testdata/wasm_unreachable.out | 2 +- cli/tests/unit/fetch_test.ts | 57 +++--- cli/tests/unit/http_test.ts | 200 ++++++++++++--------- core/01_core.js | 3 +- core/ops.rs | 14 +- core/runtime.rs | 122 ++++++++----- ext/web/timers.rs | 2 +- ext/websocket/lib.rs | 2 +- ops/lib.rs | 10 +- runtime/js/40_testing.js | 1 + runtime/ops/web_worker.rs | 2 +- 15 files changed, 295 insertions(+), 167 deletions(-) create mode 100644 cli/bench/async_ops.js create mode 100644 cli/bench/tcp.js diff --git a/cli/bench/async_ops.js b/cli/bench/async_ops.js new file mode 100644 index 0000000000..ac4eb68589 --- /dev/null +++ b/cli/bench/async_ops.js @@ -0,0 +1,19 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +const queueMicrotask = globalThis.queueMicrotask || process.nextTick; +let [total, count] = typeof Deno !== "undefined" + ? Deno.args + : [process.argv[2], process.argv[3]]; + +total = total ? parseInt(total, 0) : 50; +count = count ? parseInt(count, 10) : 100000; + +async function bench(fun) { + const start = Date.now(); + for (let i = 0; i < count; i++) await fun(); + const elapsed = Date.now() - start; + const rate = Math.floor(count / (elapsed / 1000)); + console.log(`time ${elapsed} ms rate ${rate}`); + if (--total) queueMicrotask(() => bench(fun)); +} + +bench(() => Deno.core.opAsync("op_void_async")); diff --git a/cli/bench/tcp.js b/cli/bench/tcp.js new file mode 100644 index 0000000000..826f5f87b1 --- /dev/null +++ b/cli/bench/tcp.js @@ -0,0 +1,22 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +const listener = Deno.listen({ port: 4500 }); +const response = new TextEncoder().encode( + "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n", +); + +// Accept a connection and write packets as fast as possible. +async function acceptWrite() { + const conn = await listener.accept(); + try { + while (true) { + await conn.write(response); + } + } catch { + // Pass + } + conn.close(); +} + +await acceptWrite(); +await acceptWrite(); diff --git a/cli/tests/integration/mod.rs b/cli/tests/integration/mod.rs index 84ec34f2b6..143bda819d 100644 --- a/cli/tests/integration/mod.rs +++ b/cli/tests/integration/mod.rs @@ -928,7 +928,7 @@ async fn test_resolve_dns() { let out = String::from_utf8_lossy(&output.stdout); assert!(!output.status.success()); assert!(err.starts_with("Check file")); - assert!(err.contains(r#"error: Uncaught (in promise) PermissionDenied: Requires net access to "127.0.0.1:4553""#)); + assert!(err.contains(r#"error: Uncaught PermissionDenied: Requires net access to "127.0.0.1:4553""#)); assert!(out.is_empty()); } @@ -950,7 +950,7 @@ async fn test_resolve_dns() { let out = String::from_utf8_lossy(&output.stdout); assert!(!output.status.success()); assert!(err.starts_with("Check file")); - assert!(err.contains(r#"error: Uncaught (in promise) PermissionDenied: Requires net access to "127.0.0.1:4553""#)); + assert!(err.contains(r#"error: Uncaught PermissionDenied: Requires net access to "127.0.0.1:4553""#)); assert!(out.is_empty()); } diff --git a/cli/tests/testdata/044_bad_resource.ts.out b/cli/tests/testdata/044_bad_resource.ts.out index 33c95fc446..b208f9d25c 100644 --- a/cli/tests/testdata/044_bad_resource.ts.out +++ b/cli/tests/testdata/044_bad_resource.ts.out @@ -1,2 +1,2 @@ -[WILDCARD]error: Uncaught (in promise) BadResource: Bad resource ID +[WILDCARD]error: Uncaught [WILDCARD] BadResource: Bad resource ID [WILDCARD] diff --git a/cli/tests/testdata/wasm_unreachable.out b/cli/tests/testdata/wasm_unreachable.out index cbb6b65194..7fb5e309d1 100644 --- a/cli/tests/testdata/wasm_unreachable.out +++ b/cli/tests/testdata/wasm_unreachable.out @@ -1,3 +1,3 @@ -error: Uncaught (in promise) RuntimeError: unreachable +error: Uncaught [WILDCARD] RuntimeError: unreachable at (wasm://wasm/d1c677ea:1:41) at [WILDCARD]/wasm_unreachable.js:[WILDCARD] diff --git a/cli/tests/unit/fetch_test.ts b/cli/tests/unit/fetch_test.ts index 175431e76b..cbb2cd6809 100644 --- a/cli/tests/unit/fetch_test.ts +++ b/cli/tests/unit/fetch_test.ts @@ -1202,47 +1202,44 @@ Deno.test({}, function fetchWritableRespProps() { assertEquals(new_.headers.get("x-deno"), "foo"); }); -function returnHostHeaderServer(addr: string): Deno.Listener { - const [hostname, port] = addr.split(":"); - const listener = Deno.listen({ - hostname, - port: Number(port), - }) as Deno.Listener; - - listener.accept().then(async (conn: Deno.Conn) => { - const httpConn = Deno.serveHttp(conn); - - await httpConn.nextRequest() - .then(async (requestEvent: Deno.RequestEvent | null) => { - const hostHeader = requestEvent?.request.headers.get("Host"); - const headersToReturn = hostHeader ? { "Host": hostHeader } : undefined; - - await requestEvent?.respondWith( - new Response("", { - status: 200, - headers: headersToReturn, - }), - ); - }); - - httpConn.close(); - }); - - return listener; -} - Deno.test( { permissions: { net: true } }, async function fetchFilterOutCustomHostHeader(): Promise< void > { const addr = "127.0.0.1:4511"; - const listener = returnHostHeaderServer(addr); + const [hostname, port] = addr.split(":"); + const listener = Deno.listen({ + hostname, + port: Number(port), + }) as Deno.Listener; + + let httpConn: Deno.HttpConn; + listener.accept().then(async (conn: Deno.Conn) => { + httpConn = Deno.serveHttp(conn); + + await httpConn.nextRequest() + .then(async (requestEvent: Deno.RequestEvent | null) => { + const hostHeader = requestEvent?.request.headers.get("Host"); + const headersToReturn = hostHeader + ? { "Host": hostHeader } + : undefined; + + await requestEvent?.respondWith( + new Response("", { + status: 200, + headers: headersToReturn, + }), + ); + }); + }); + const response = await fetch(`http://${addr}/`, { headers: { "Host": "example.com" }, }); await response.text(); listener.close(); + httpConn!.close(); assertEquals(response.headers.get("Host"), addr); }, diff --git a/cli/tests/unit/http_test.ts b/cli/tests/unit/http_test.ts index b52fbdec99..eeb75c9341 100644 --- a/cli/tests/unit/http_test.ts +++ b/cli/tests/unit/http_test.ts @@ -45,11 +45,12 @@ async function writeRequestAndReadResponse(conn: Deno.Conn): Promise { } Deno.test({ permissions: { net: true } }, async function httpServerBasic() { + let httpConn: Deno.HttpConn; const promise = (async () => { const listener = Deno.listen({ port: 4501 }); const conn = await listener.accept(); listener.close(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const reqEvent = await httpConn.nextRequest(); assert(reqEvent); const { request, respondWith } = reqEvent; @@ -58,7 +59,6 @@ Deno.test({ permissions: { net: true } }, async function httpServerBasic() { await respondWith( new Response("Hello World", { headers: { "foo": "bar" } }), ); - httpConn.close(); })(); const resp = await fetch("http://127.0.0.1:4501/", { @@ -71,6 +71,8 @@ Deno.test({ permissions: { net: true } }, async function httpServerBasic() { const cloneText = await clone.text(); assertEquals(cloneText, "Hello World"); await promise; + + httpConn!.close(); }); // https://github.com/denoland/deno/issues/15107 @@ -88,7 +90,7 @@ Deno.test( const { request } = e; request.text(); headers = request.headers; - httpConn.close(); + httpConn!.close(); })(); const conn = await Deno.connect({ port: 2333 }); @@ -120,7 +122,7 @@ Deno.test( await respondWith(new Response("Hello World")); // Closes request assertThrows(() => request.headers, TypeError, "request closed"); - httpConn.close(); + httpConn!.close(); })(); const conn = await Deno.connect({ port: 2334 }); @@ -138,17 +140,17 @@ Deno.test( Deno.test( { permissions: { net: true } }, async function httpServerGetRequestBody() { + let httpConn: Deno.HttpConn; const promise = (async () => { const listener = Deno.listen({ port: 4501 }); const conn = await listener.accept(); listener.close(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; assertEquals(request.body, null); await respondWith(new Response("", { headers: {} })); - httpConn.close(); })(); const conn = await Deno.connect({ port: 4501 }); @@ -166,6 +168,7 @@ Deno.test( conn.close(); await promise; + httpConn!.close(); }, ); @@ -178,23 +181,24 @@ Deno.test( writer.write(new TextEncoder().encode("world")); writer.close(); + let httpConn: Deno.HttpConn; + const listener = Deno.listen({ port: 4501 }); const promise = (async () => { - const listener = Deno.listen({ port: 4501 }); const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const evt = await httpConn.nextRequest(); assert(evt); const { request, respondWith } = evt; assert(!request.body); await respondWith(new Response(stream.readable)); - httpConn.close(); - listener.close(); })(); const resp = await fetch("http://127.0.0.1:4501/"); const respBody = await resp.text(); assertEquals("hello world", respBody); await promise; + httpConn!.close(); + listener.close(); }, ); @@ -240,17 +244,16 @@ Deno.test( Deno.test( { permissions: { net: true } }, async function httpServerStreamDuplex() { + let httpConn: Deno.HttpConn; + const listener = Deno.listen({ port: 4501 }); const promise = (async () => { - const listener = Deno.listen({ port: 4501 }); const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const evt = await httpConn.nextRequest(); assert(evt); const { request, respondWith } = evt; assert(request.body); await respondWith(new Response(request.body)); - httpConn.close(); - listener.close(); })(); const ts = new TransformStream(); @@ -269,10 +272,13 @@ Deno.test( const chunk2 = await reader.read(); assert(!chunk2.done); assertEquals(chunk2.value, new Uint8Array([2])); + await writable.close(); const chunk3 = await reader.read(); assert(chunk3.done); await promise; + httpConn!.close(); + listener.close(); }, ); @@ -351,18 +357,17 @@ Deno.test( Deno.test( { permissions: { net: true } }, async function httpServerRegressionHang() { + let httpConn: Deno.HttpConn; + const listener = Deno.listen({ port: 4501 }); const promise = (async () => { - const listener = Deno.listen({ port: 4501 }); const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const event = await httpConn.nextRequest(); assert(event); const { request, respondWith } = event; const reqBody = await request.text(); assertEquals("request", reqBody); await respondWith(new Response("response")); - httpConn.close(); - listener.close(); })(); const resp = await fetch("http://127.0.0.1:4501/", { @@ -372,6 +377,9 @@ Deno.test( const respBody = await resp.text(); assertEquals("response", respBody); await promise; + + httpConn!.close(); + listener.close(); }, ); @@ -410,7 +418,7 @@ Deno.test( cancelReason!, ); assert(cancelReason!); - httpConn.close(); + httpConn!.close(); listener.close(); })(); @@ -468,22 +476,23 @@ Deno.test( Deno.test( { permissions: { net: true } }, async function httpServerEmptyBlobResponse() { + let httpConn: Deno.HttpConn; + const listener = Deno.listen({ port: 4501 }); const promise = (async () => { - const listener = Deno.listen({ port: 4501 }); const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const event = await httpConn.nextRequest(); assert(event); const { respondWith } = event; await respondWith(new Response(new Blob([]))); - httpConn.close(); - listener.close(); })(); const resp = await fetch("http://127.0.0.1:4501/"); const respBody = await resp.text(); assertEquals("", respBody); await promise; + httpConn!.close(); + listener.close(); }, ); @@ -633,14 +642,14 @@ Deno.test( }).pipeThrough(new TextEncoderStream()); } + let httpConn: Deno.HttpConn; const listener = Deno.listen({ port: 4501 }); const finished = (async () => { const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const requestEvent = await httpConn.nextRequest(); const { respondWith } = requestEvent!; await respondWith(new Response(periodicStream())); - httpConn.close(); })(); // start a client @@ -651,6 +660,8 @@ Deno.test( await finished; clientConn.close(); + + httpConn!.close(); listener.close(); }, ); @@ -658,11 +669,12 @@ Deno.test( Deno.test( { permissions: { net: true } }, async function httpRequestLatin1Headers() { + let httpConn: Deno.HttpConn; const promise = (async () => { const listener = Deno.listen({ port: 4501 }); const conn = await listener.accept(); listener.close(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const reqEvent = await httpConn.nextRequest(); assert(reqEvent); const { request, respondWith } = reqEvent; @@ -670,7 +682,6 @@ Deno.test( await respondWith( new Response("", { headers: { "X-Header-Test": "Æ" } }), ); - httpConn.close(); })(); const clientConn = await Deno.connect({ port: 4501 }); @@ -688,11 +699,14 @@ Deno.test( let responseText = ""; const buf = new Uint8Array(1024); let read; + while ((read = await clientConn.read(buf)) !== null) { + httpConn!.close(); for (let i = 0; i < read; i++) { responseText += String.fromCharCode(buf[i]); } } + clientConn.close(); assert(/\r\n[Xx]-[Hh]eader-[Tt]est: Æ\r\n/.test(responseText)); @@ -704,18 +718,18 @@ Deno.test( Deno.test( { permissions: { net: true } }, async function httpServerRequestWithoutPath() { + let httpConn: Deno.HttpConn; + const listener = Deno.listen({ port: 4501 }); const promise = (async () => { - const listener = Deno.listen({ port: 4501 }); const conn = await listener.accept(); listener.close(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const reqEvent = await httpConn.nextRequest(); assert(reqEvent); const { request, respondWith } = reqEvent; assertEquals(new URL(request.url).href, "http://127.0.0.1:4501/"); assertEquals(await request.text(), ""); await respondWith(new Response()); - httpConn.close(); })(); const clientConn = await Deno.connect({ port: 4501 }); @@ -744,6 +758,7 @@ Deno.test( await writeRequest(clientConn); clientConn.close(); await promise; + httpConn!.close(); }, ); @@ -868,11 +883,12 @@ Deno.test(function httpUpgradeWebSocketWithoutUpgradeHeader() { Deno.test( { permissions: { net: true } }, async function httpCookieConcatenation() { + let httpConn: Deno.HttpConn; const promise = (async () => { const listener = Deno.listen({ port: 4501 }); const conn = await listener.accept(); listener.close(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const reqEvent = await httpConn.nextRequest(); assert(reqEvent); const { request, respondWith } = reqEvent; @@ -880,7 +896,6 @@ Deno.test( assertEquals(await request.text(), ""); assertEquals(request.headers.get("cookie"), "foo=bar; bar=foo"); await respondWith(new Response("ok")); - httpConn.close(); })(); const resp = await fetch("http://127.0.0.1:4501/", { @@ -893,6 +908,7 @@ Deno.test( const text = await resp.text(); assertEquals(text, "ok"); await promise; + httpConn!.close(); }, ); @@ -910,7 +926,7 @@ Deno.test({ permissions: { net: true } }, async function httpServerPanic() { httpConn.nextRequest(); await client.write(encoder.encode("\r\n\r\n")); - httpConn.close(); + httpConn!.close(); client.close(); listener.close(); @@ -923,21 +939,23 @@ Deno.test( const file = await Deno.open(tmpFile, { write: true, read: true }); await file.write(new Uint8Array(70 * 1024).fill(1)); // 70kb sent in 64kb + 6kb chunks file.close(); + + let httpConn: Deno.HttpConn; + const listener = Deno.listen({ port: 4503 }); const promise = (async () => { - const listener = Deno.listen({ port: 4503 }); const conn = await listener.accept(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const ev = await httpConn.nextRequest(); const { respondWith } = ev!; const f = await Deno.open(tmpFile, { read: true }); await respondWith(new Response(f.readable, { status: 200 })); - httpConn.close(); - listener.close(); })(); const resp = await fetch("http://127.0.0.1:4503/"); const body = await resp.arrayBuffer(); assertEquals(body.byteLength, 70 * 1024); await promise; + httpConn!.close(); + listener.close(); }, ); @@ -976,7 +994,7 @@ Deno.test( } assert(didThrow); - httpConn.close(); + httpConn!.close(); listener.close(); client.close(); }, @@ -1018,7 +1036,7 @@ Deno.test( await respondWith(res).catch((error: Error) => errors.push(error)); - httpConn.close(); + httpConn!.close(); listener.close(); assert(errors.length >= 1); @@ -1047,7 +1065,7 @@ Deno.test( )), ]); - httpConn.close(); + httpConn!.close(); listener.close(); clientConn.close(); }, @@ -1133,10 +1151,11 @@ Deno.test( const hostname = "localhost"; const port = 4501; + let httpConn: Deno.HttpConn; + const listener = Deno.listen({ hostname, port }); async function server() { - const listener = Deno.listen({ hostname, port }); const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const promises = new Array(10).fill(null).map(async (_, i) => { const event = await httpConn.nextRequest(); assert(event); @@ -1146,8 +1165,6 @@ Deno.test( await event.respondWith(response); }); await Promise.all(promises); - httpConn.close(); - listener.close(); } async function client() { @@ -1159,6 +1176,8 @@ Deno.test( } await Promise.all([server(), delay(100).then(client)]); + httpConn!.close(); + listener.close(); }, ); @@ -1213,16 +1232,15 @@ Deno.test( const hostname = "localhost"; const port = 4501; + let httpConn: Deno.HttpConn; + const listener = Deno.listen({ hostname, port }); async function server() { - const listener = Deno.listen({ hostname, port }); const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const event = await httpConn.nextRequest() as Deno.RequestEvent; assert(event.request.body); const response = new Response(); await event.respondWith(response); - httpConn.close(); - listener.close(); } async function client() { @@ -1237,17 +1255,20 @@ Deno.test( } await Promise.all([server(), client()]); + httpConn!.close(); + listener.close(); }, ); Deno.test( { permissions: { net: true } }, async function httpServerRespondNonAsciiUint8Array() { + let httpConn: Deno.HttpConn; + const listener = Deno.listen({ port: 4501 }); const promise = (async () => { - const listener = Deno.listen({ port: 4501 }); const conn = await listener.accept(); listener.close(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1255,7 +1276,6 @@ Deno.test( await respondWith( new Response(new Uint8Array([128]), {}), ); - httpConn.close(); })(); const resp = await fetch("http://localhost:4501/"); @@ -1264,6 +1284,7 @@ Deno.test( assertEquals(new Uint8Array(body), new Uint8Array([128])); await promise; + httpConn!.close(); }, ); @@ -1276,11 +1297,12 @@ Deno.test( async function httpServerOnUnixSocket() { const filePath = Deno.makeTempFileSync(); + let httpConn: Deno.HttpConn; const promise = (async () => { const listener = Deno.listen({ path: filePath, transport: "unix" }); const conn = await listener.accept(); listener.close(); - const httpConn = Deno.serveHttp(conn); + httpConn = Deno.serveHttp(conn); const reqEvent = await httpConn.nextRequest(); assert(reqEvent); const { request, respondWith } = reqEvent; @@ -1289,7 +1311,6 @@ Deno.test( assertEquals(decodeURIComponent(url.host), filePath); assertEquals(url.pathname, "/path/name"); await respondWith(new Response("", { headers: {} })); - httpConn.close(); })(); // fetch() does not supports unix domain sockets yet https://github.com/denoland/deno/issues/8821 @@ -1307,6 +1328,7 @@ Deno.test( conn.close(); await promise; + httpConn!.close(); }, ); @@ -1324,9 +1346,10 @@ Deno.test({ const data = { hello: "deno", now: "with", compressed: "body" }; + let httpConn: Deno.HttpConn; async function server() { const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1335,7 +1358,6 @@ Deno.test({ headers: { "content-type": "application/json" }, }); await respondWith(response); - httpConn.close(); listener.close(); } @@ -1361,6 +1383,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -1374,9 +1397,10 @@ Deno.test({ const data = { hello: "deno", now: "with", compressed: "body" }; + let httpConn: Deno.HttpConn; async function server() { const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1385,7 +1409,6 @@ Deno.test({ headers: { "content-type": "application/json" }, }); await respondWith(response); - httpConn.close(); listener.close(); } @@ -1413,6 +1436,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -1423,10 +1447,11 @@ Deno.test({ const hostname = "localhost"; const port = 4501; + let httpConn: Deno.HttpConn; async function server() { const listener = Deno.listen({ hostname, port }); const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1438,7 +1463,6 @@ Deno.test({ }, ); await respondWith(response); - httpConn.close(); listener.close(); } @@ -1464,6 +1488,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -1474,10 +1499,11 @@ Deno.test({ const hostname = "localhost"; const port = 4501; + let httpConn: Deno.HttpConn; async function server() { const listener = Deno.listen({ hostname, port }); const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1492,7 +1518,6 @@ Deno.test({ }, ); await respondWith(response); - httpConn.close(); listener.close(); } @@ -1518,6 +1543,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -1528,10 +1554,11 @@ Deno.test({ const hostname = "localhost"; const port = 4501; + let httpConn: Deno.HttpConn; async function server() { const listener = Deno.listen({ hostname, port }); const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1543,7 +1570,6 @@ Deno.test({ }, ); await respondWith(response); - httpConn.close(); listener.close(); } @@ -1569,6 +1595,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -1579,10 +1606,11 @@ Deno.test({ const hostname = "localhost"; const port = 4501; + let httpConn: Deno.HttpConn; async function server() { const listener = Deno.listen({ hostname, port }); const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1597,7 +1625,6 @@ Deno.test({ }, ); await respondWith(response); - httpConn.close(); listener.close(); } @@ -1627,6 +1654,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -1637,10 +1665,11 @@ Deno.test({ const hostname = "localhost"; const port = 4501; + let httpConn: Deno.HttpConn; async function server() { const listener = Deno.listen({ hostname, port }); const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1655,7 +1684,6 @@ Deno.test({ }, ); await respondWith(response); - httpConn.close(); listener.close(); } @@ -1684,6 +1712,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -1694,10 +1723,11 @@ Deno.test({ const hostname = "localhost"; const port = 4501; + let httpConn: Deno.HttpConn; async function server() { const listener = Deno.listen({ hostname, port }); const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1712,7 +1742,6 @@ Deno.test({ }, ); await respondWith(response); - httpConn.close(); listener.close(); } @@ -1738,6 +1767,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -1748,10 +1778,11 @@ Deno.test({ const hostname = "localhost"; const port = 4501; + let httpConn: Deno.HttpConn; async function server() { const listener = Deno.listen({ hostname, port }); const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1766,7 +1797,6 @@ Deno.test({ }, ); await respondWith(response); - httpConn.close(); listener.close(); } @@ -1792,6 +1822,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -1807,9 +1838,10 @@ Deno.test({ const data = { hello: "deno", now: "with", compressed: "body" }; + let httpConn: Deno.HttpConn; async function server() { const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1825,7 +1857,6 @@ Deno.test({ { headers: { "content-type": "application/json" } }, ); await respondWith(response); - httpConn.close(); listener.close(); } @@ -1852,6 +1883,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -1867,9 +1899,10 @@ Deno.test({ const data = { hello: "deno", now: "with", compressed: "body" }; + let httpConn: Deno.HttpConn; async function server() { const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1885,7 +1918,6 @@ Deno.test({ { headers: { "content-type": "application/json" } }, ); await respondWith(response); - httpConn.close(); listener.close(); } @@ -1913,6 +1945,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -1924,10 +1957,11 @@ Deno.test({ const port = 4501; let contentLength: string; + let httpConn: Deno.HttpConn; async function server() { const listener = Deno.listen({ hostname, port }); const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -1948,7 +1982,6 @@ Deno.test({ }, ); await respondWith(response); - httpConn.close(); listener.close(); } @@ -1977,6 +2010,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -1988,10 +2022,11 @@ Deno.test({ const port = 4501; let contentLength: string; + let httpConn: Deno.HttpConn; async function server() { const listener = Deno.listen({ hostname, port }); const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -2007,7 +2042,6 @@ Deno.test({ }, ); await respondWith(response); - httpConn.close(); listener.close(); } @@ -2037,6 +2071,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -2048,10 +2083,11 @@ Deno.test({ const port = 4501; let contentLength: string; + let httpConn: Deno.HttpConn; async function server() { const listener = Deno.listen({ hostname, port }); const tcpConn = await listener.accept(); - const httpConn = Deno.serveHttp(tcpConn); + httpConn = Deno.serveHttp(tcpConn); const e = await httpConn.nextRequest(); assert(e); const { request, respondWith } = e; @@ -2068,7 +2104,6 @@ Deno.test({ }, ); await respondWith(response); - httpConn.close(); listener.close(); } @@ -2095,6 +2130,7 @@ Deno.test({ } await Promise.all([server(), client()]); + httpConn!.close(); }, }); @@ -2248,7 +2284,7 @@ Deno.test("upgradeHttp unix", { const resp = new Response(null, { status: 101 }); await respondWith(resp); await promise; - httpConn.close(); + httpConn!.close(); })(); await Promise.all([server, client()]); diff --git a/core/01_core.js b/core/01_core.js index ab9722bc18..fda3e49773 100644 --- a/core/01_core.js +++ b/core/01_core.js @@ -160,10 +160,11 @@ function opAsync(opName, ...args) { const promiseId = nextPromiseId++; + let p = setPromise(promiseId); const maybeError = ops[opName](promiseId, ...args); // Handle sync error (e.g: error parsing args) if (maybeError) return unwrapOpResult(maybeError); - let p = PromisePrototypeThen(setPromise(promiseId), unwrapOpResult); + p = PromisePrototypeThen(p, unwrapOpResult); if (opCallTracingEnabled) { // Capture a stack trace by creating a new `Error` object. We remove the // first 6 characters (the `Error\n` prefix) to get just the stack trace. diff --git a/core/ops.rs b/core/ops.rs index d22a703bd8..c14fcdd7b2 100644 --- a/core/ops.rs +++ b/core/ops.rs @@ -28,17 +28,25 @@ use std::task::Poll; /// turn of the event loop, which is too late for certain ops. pub struct OpCall(MaybeDone>>>); +pub enum EagerPollResult { + Ready(T), + Pending(OpCall), +} + impl OpCall { /// Wraps a future, and polls the inner future immediately. /// This should be the default choice for ops. - pub fn eager(fut: impl Future + 'static) -> Self { + pub fn eager(fut: impl Future + 'static) -> EagerPollResult { let boxed = Box::pin(fut) as Pin>>; let mut inner = maybe_done(boxed); let waker = noop_waker(); let mut cx = Context::from_waker(&waker); let mut pinned = Pin::new(&mut inner); - let _ = pinned.as_mut().poll(&mut cx); - Self(inner) + let poll = pinned.as_mut().poll(&mut cx); + match poll { + Poll::Ready(_) => EagerPollResult::Ready(pinned.take_output().unwrap()), + _ => EagerPollResult::Pending(Self(inner)), + } } /// Wraps a future; the inner future is polled the usual way (lazily). diff --git a/core/runtime.rs b/core/runtime.rs index 4aad2bd765..2c16ddeb88 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -2145,14 +2145,54 @@ impl JsRealm { #[inline] pub fn queue_async_op( - scope: &v8::Isolate, + state: Rc>, + scope: &mut v8::HandleScope, + deferred: bool, op: impl Future, PromiseId, OpId, OpResult)> + 'static, ) { - let state_rc = JsRuntime::state(scope); - let mut state = state_rc.borrow_mut(); - state.pending_ops.push(OpCall::eager(op)); - state.have_unpolled_ops = true; + match OpCall::eager(op) { + // This calls promise.resolve() before the control goes back to userland JS. It works something + // along the lines of: + // + // function opresolve(promiseId, ...) { + // getPromise(promiseId).resolve(...); + // } + // const p = setPromise(); + // op.op_async(promiseId, ...); // Calls `opresolve` + // return p; + EagerPollResult::Ready((context, promise_id, op_id, mut resp)) + if !deferred => + { + let args = &[ + v8::Integer::new(scope, promise_id).into(), + resp.to_v8(scope).unwrap(), + ]; + + let realm = JsRealm::new(context); + let js_recv_cb_handle = + realm.state(scope).borrow().js_recv_cb.clone().unwrap(); + state.borrow().tracker.track_async_completed(op_id); + + let tc_scope = &mut v8::TryCatch::new(scope); + let js_recv_cb = js_recv_cb_handle.open(tc_scope); + let this = v8::undefined(tc_scope).into(); + js_recv_cb.call(tc_scope, this, args); + } + EagerPollResult::Ready(op) => { + let ready = OpCall::ready(op); + let state_rc = JsRuntime::state(scope); + let mut state = state_rc.borrow_mut(); + state.pending_ops.push(ready); + state.have_unpolled_ops = true; + } + EagerPollResult::Pending(op) => { + let state_rc = JsRuntime::state(scope); + let mut state = state_rc.borrow_mut(); + state.pending_ops.push(op); + state.have_unpolled_ops = true; + } + } } #[cfg(test)] @@ -2194,7 +2234,7 @@ pub mod tests { dispatch_count: Arc, } - #[op] + #[op(deferred)] async fn op_test( rc_op_state: Rc>, control: u8, @@ -2255,41 +2295,6 @@ pub mod tests { (runtime, dispatch_count) } - #[test] - fn test_dispatch() { - let (mut runtime, dispatch_count) = setup(Mode::Async); - runtime - .execute_script( - "filename.js", - r#" - let control = 42; - Deno.core.opAsync("op_test", control); - async function main() { - Deno.core.opAsync("op_test", control); - } - main(); - "#, - ) - .unwrap(); - assert_eq!(dispatch_count.load(Ordering::Relaxed), 2); - } - - #[test] - fn test_op_async_promise_id() { - let (mut runtime, _dispatch_count) = setup(Mode::Async); - runtime - .execute_script( - "filename.js", - r#" - const p = Deno.core.opAsync("op_test", 42); - if (p[Symbol.for("Deno.core.internalPromiseId")] == undefined) { - throw new Error("missing id on returned promise"); - } - "#, - ) - .unwrap(); - } - #[test] fn test_ref_unref_ops() { let (mut runtime, _dispatch_count) = setup(Mode::Async); @@ -2344,6 +2349,41 @@ pub mod tests { } } + #[test] + fn test_dispatch() { + let (mut runtime, dispatch_count) = setup(Mode::Async); + runtime + .execute_script( + "filename.js", + r#" + let control = 42; + Deno.core.opAsync("op_test", control); + async function main() { + Deno.core.opAsync("op_test", control); + } + main(); + "#, + ) + .unwrap(); + assert_eq!(dispatch_count.load(Ordering::Relaxed), 2); + } + + #[test] + fn test_op_async_promise_id() { + let (mut runtime, _dispatch_count) = setup(Mode::Async); + runtime + .execute_script( + "filename.js", + r#" + const p = Deno.core.opAsync("op_test", 42); + if (p[Symbol.for("Deno.core.internalPromiseId")] == undefined) { + throw new Error("missing id on returned promise"); + } + "#, + ) + .unwrap(); + } + #[test] fn test_dispatch_no_zero_copy_buf() { let (mut runtime, dispatch_count) = setup(Mode::AsyncZeroCopy(false)); diff --git a/ext/web/timers.rs b/ext/web/timers.rs index d9ceef8758..f6b2cc9e73 100644 --- a/ext/web/timers.rs +++ b/ext/web/timers.rs @@ -93,7 +93,7 @@ pub fn op_timer_handle(state: &mut OpState) -> ResourceId { /// Waits asynchronously until either `millis` milliseconds have passed or the /// [`TimerHandle`] resource given by `rid` has been canceled. -#[op] +#[op(deferred)] pub async fn op_sleep( state: Rc>, millis: u64, diff --git a/ext/websocket/lib.rs b/ext/websocket/lib.rs index 515f798acd..fad217585b 100644 --- a/ext/websocket/lib.rs +++ b/ext/websocket/lib.rs @@ -420,7 +420,7 @@ pub async fn op_ws_send( Ok(()) } -#[op] +#[op(deferred)] pub async fn op_ws_close( state: Rc>, rid: ResourceId, diff --git a/ops/lib.rs b/ops/lib.rs index f75972c51a..a41ba03207 100644 --- a/ops/lib.rs +++ b/ops/lib.rs @@ -51,6 +51,7 @@ struct MacroArgs { is_unstable: bool, is_v8: bool, must_be_fast: bool, + deferred: bool, } impl syn::parse::Parse for MacroArgs { @@ -62,7 +63,7 @@ impl syn::parse::Parse for MacroArgs { let vars: Vec<_> = vars.iter().map(Ident::to_string).collect(); let vars: Vec<_> = vars.iter().map(String::as_str).collect(); for var in vars.iter() { - if !["unstable", "v8", "fast"].contains(var) { + if !["unstable", "v8", "fast", "deferred"].contains(var) { return Err(syn::Error::new( input.span(), "Ops expect #[op] or #[op(unstable)]", @@ -73,6 +74,7 @@ impl syn::parse::Parse for MacroArgs { is_unstable: vars.contains(&"unstable"), is_v8: vars.contains(&"v8"), must_be_fast: vars.contains(&"fast"), + deferred: vars.contains(&"deferred"), }) } } @@ -84,6 +86,7 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream { is_unstable, is_v8, must_be_fast, + deferred, } = margs; let func = syn::parse::(item).expect("expected a function"); let name = &func.sig.ident; @@ -110,7 +113,7 @@ pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream { let asyncness = func.sig.asyncness.is_some(); let is_async = asyncness || is_future(&func.sig.output); let v8_body = if is_async { - codegen_v8_async(&core, &func, margs, asyncness) + codegen_v8_async(&core, &func, margs, asyncness, deferred) } else { codegen_v8_sync(&core, &func, margs) }; @@ -173,6 +176,7 @@ fn codegen_v8_async( f: &syn::ItemFn, margs: MacroArgs, asyncness: bool, + deferred: bool, ) -> TokenStream2 { let MacroArgs { is_v8, .. } = margs; let special_args = f @@ -256,7 +260,7 @@ fn codegen_v8_async( }; #pre_result - #core::_ops::queue_async_op(scope, async move { + #core::_ops::queue_async_op(state, scope, #deferred, async move { let result = #result_fut #result_wrapper (context, promise_id, op_id, #core::_ops::to_op_result(get_class, result)) diff --git a/runtime/js/40_testing.js b/runtime/js/40_testing.js index e3a6ce3243..c1ce3e726c 100644 --- a/runtime/js/40_testing.js +++ b/runtime/js/40_testing.js @@ -147,6 +147,7 @@ // cleared can actually be removed from resource table, otherwise // false positives may occur (https://github.com/denoland/deno/issues/4591) await opSanitizerDelay(); + await opSanitizerDelay(); } if (shouldSkipSanitizers(desc)) return; diff --git a/runtime/ops/web_worker.rs b/runtime/ops/web_worker.rs index 184ebfddba..3c85d7b9ee 100644 --- a/runtime/ops/web_worker.rs +++ b/runtime/ops/web_worker.rs @@ -39,7 +39,7 @@ fn op_worker_post_message( Ok(()) } -#[op] +#[op(deferred)] async fn op_worker_recv_message( state: Rc>, ) -> Result, AnyError> {