oak icon indicating copy to clipboard operation
oak copied to clipboard

Can't close the server after upgrading a connection to Websocket

Open Andrepuel opened this issue 5 years ago • 3 comments

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))
]);

Andrepuel avatar Jan 03 '21 00:01 Andrepuel

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.

Andrepuel avatar Jan 03 '21 00:01 Andrepuel

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.

Andrepuel avatar Jan 03 '21 01:01 Andrepuel

The second problem (stuck after listening finishes) is happening on the client side, not related to OAK.

https://github.com/denoland/deno/issues/7457

Andrepuel avatar Jan 03 '21 02:01 Andrepuel