Can't close the server after upgrading a connection to Websocket
Code to reproduce the issue:
import { Application, Context } from 'https://deno.land/x/[email protected]/mod.ts';
const app = new Application();
app.use(async (ctx) => {
console.log('upgrading');
const socket = await ctx.upgrade();
console.log('upgraded');
});
const controller = new AbortController();
const listening = app.listen({ port: 8000, signal: controller.signal });
await new Promise((ok) => setTimeout(ok, 100));
const ws = new WebSocket("ws://127.0.0.1:8000");
await new Promise<void>((ok, err) => {
ws.onopen = () => ok();
ws.onerror = (e) => {
console.error(e);
ok();
}
});
await new Promise((ok) => setTimeout(ok, 500));
ws.close();
await new Promise((ok) => setTimeout(ok, 500));
controller.abort();
console.log('aborting');
await Promise.all([
listening,
new Promise((_, err) => setTimeout(() => err(new Error("stuck")), 1000))
]);
Looks like the listen() promise is stuck in the Server async iterator.
Even after an connection is upgraded to WebSocket, it will still be handled by the iterator, waiting for a deferred promise called done.
By doing the following workaround, I've managed to end the listening promise:
import { Application, Context } from 'https://deno.land/x/[email protected]/mod.ts';
const app = new Application();
app.use(async (ctx) => {
console.log('upgrading');
const socket = await ctx.upgrade();
// <--------------------------------- Workaround, force untracking this connection
ctx.request.serverRequest.done.resolve(new Error('force untrack'));
console.log('upgraded');
});
const controller = new AbortController();
const listening = app.listen({ port: 8000, signal: controller.signal });
await new Promise((ok) => setTimeout(ok, 100));
const ws = new WebSocket("ws://127.0.0.1:8000");
await new Promise<void>((ok, err) => {
ws.onopen = () => ok();
ws.onerror = (e) => {
console.error(e);
ok();
}
});
await new Promise((ok) => setTimeout(ok, 500));
ws.close();
await new Promise((ok) => setTimeout(ok, 500));
controller.abort();
console.log('aborting');
await Promise.all([
listening,
new Promise((_, err) => setTimeout(() => err(new Error("stuck")), 1000))
]);
However, the main process is still stuck. There is an async op that is leaking. By debugging the async op's I've figured out that a op named "ws next event" never returns. Even after the websocket is closed.
The second problem (stuck after listening finishes) is happening on the client side, not related to OAK.
https://github.com/denoland/deno/issues/7457