fix(core): poll async ops eagerly (#12385)

Currently all async ops are polled lazily, which means that op
initialization code is postponed until control is yielded to the event
loop. This has some weird consequences, e.g.

```js
let listener = Deno.listen(...);
let conn_promise = listener.accept();
listener.close();
// `BadResource` is thrown. A reasonable error would be `Interrupted`.
let conn = await conn_promise;
```

JavaScript promises are expected to be eagerly evaluated. This patch
makes ops actually do that.
This commit is contained in:
Bert Belder 2021-10-09 22:37:19 +02:00
parent ff95fc167d
commit ff932b411d
No known key found for this signature in database
GPG key ID: 7A77887B2E2ED461
9 changed files with 121 additions and 74 deletions

View file

@ -892,15 +892,13 @@ unitTest(
respondPromise,
]);
httpConn.close();
listener.close();
assert(errors.length >= 1);
for (const error of errors) {
assertEquals(error.name, "Http");
assertEquals(
error.message,
"connection closed before message completed",
);
assert(error.message.includes("connection"));
}
},
);
@ -975,44 +973,29 @@ unitTest(
unitTest(
{ permissions: { net: true } },
async function droppedConnSenderNoPanic() {
async function server(listener: Deno.Listener) {
async function server() {
const listener = Deno.listen({ port: 8000 });
const conn = await listener.accept();
const http = Deno.serveHttp(conn);
for (;;) {
const req = await http.nextRequest();
if (req == null) break;
nextloop()
.then(() => {
http.close();
return req.respondWith(new Response("boom"));
})
.catch(() => {});
}
const evt = await http.nextRequest();
http.close();
try {
http.close();
await evt!.respondWith(new Response("boom"));
} catch {
"nop";
// Ignore error.
}
listener.close();
}
async function client() {
const resp = await fetch("http://127.0.0.1:8000/");
await resp.body?.cancel();
try {
const resp = await fetch("http://127.0.0.1:8000/");
await resp.body?.cancel();
} catch {
// Ignore error
}
}
function nextloop() {
return new Promise((resolve) => setTimeout(resolve, 0));
}
async function main() {
const listener = Deno.listen({ port: 8000 });
await Promise.all([server(listener), client()]);
}
await main();
await Promise.all([server(), client()]);
},
);

View file

@ -117,10 +117,10 @@ unitTest(
const listener = Deno.listen({ port: 4501 });
const p = listener.accept();
listener.close();
// TODO(piscisaureus): the error type should be `Interrupted` here, which
// gets thrown, but then ext/net catches it and rethrows `BadResource`.
await assertRejects(
async () => {
await p;
},
() => p,
Deno.errors.BadResource,
"Listener has been closed",
);
@ -141,11 +141,8 @@ unitTest(
const p = listener.accept();
listener.close();
await assertRejects(
async () => {
await p;
},
Deno.errors.BadResource,
"Listener has been closed",
() => p,
Deno.errors.Interrupted,
);
},
);
@ -173,27 +170,29 @@ unitTest(
},
);
// TODO(jsouto): Enable when tokio updates mio to v0.7!
unitTest(
{ ignore: true, permissions: { read: true, write: true } },
{
ignore: Deno.build.os === "windows",
permissions: { read: true, write: true },
},
async function netUnixConcurrentAccept() {
const filePath = await Deno.makeTempFile();
const listener = Deno.listen({ transport: "unix", path: filePath });
let acceptErrCount = 0;
const checkErr = (e: Error) => {
if (e.message === "Listener has been closed") {
if (e instanceof Deno.errors.Interrupted) { // "operation canceled"
assertEquals(acceptErrCount, 1);
} else if (e.message === "Another accept task is ongoing") {
} else if (e instanceof Deno.errors.Busy) { // "Listener already in use"
acceptErrCount++;
} else {
throw new Error("Unexpected error message");
throw e;
}
};
const p = listener.accept().catch(checkErr);
const p1 = listener.accept().catch(checkErr);
await Promise.race([p, p1]);
listener.close();
await [p, p1];
await Promise.all([p, p1]);
assertEquals(acceptErrCount, 1);
},
);
@ -500,11 +499,7 @@ unitTest(
);
unitTest(
{
// FIXME(bartlomieju)
ignore: true,
permissions: { net: true },
},
{ permissions: { net: true } },
async function netListenAsyncIterator() {
const addr = { hostname: "127.0.0.1", port: 3500 };
const listener = Deno.listen(addr);
@ -590,8 +585,8 @@ unitTest(
await conn.write(new Uint8Array([1, 2, 3]));
}
} catch (err) {
assert(!!err);
assert(err instanceof Deno.errors.BadResource);
assert(err);
assert(err instanceof Deno.errors.Interrupted);
}
}