deno icon indicating copy to clipboard operation
deno copied to clipboard

new WebSocket() takes 2 seconds to open ?

Open e3dio opened this issue 2 years ago • 2 comments

Version: Deno 1.40.3

Why does new WebSocket() take 2 seconds to open a connection ? It should be instant

const port = 3007;
const server = Deno.serve({ port }, req => {
  const { socket, response } = Deno.upgradeWebSocket(req);
  socket.onopen = () => console.timeEnd('ws open');
  return response;
});
console.time('ws open');
const ws = new WebSocket(`ws://localhost:${port}`);

e3dio avatar Feb 11 '24 20:02 e3dio

Which platform is this? I'm running the same on an M1 mac and I get a 9ms timeout.

$ deno run -A /tmp/ws.ts 
Listening on http://localhost:3007/
ws open: 4ms

mmastrac avatar Feb 11 '24 23:02 mmastrac

I tried Linux and was only 8ms, I get 2050ms on Windows

e3dio avatar Feb 11 '24 23:02 e3dio

Do you have any sort of firewall process running on Windows? It may also be some weird interaction with the event loop but it's probably worth ruling out any firewall.

mmastrac avatar Feb 12 '24 03:02 mmastrac

No firewall, it connects instantly when I manually connect with TCP and do websocket handshake:

const port = 3007;
const server = Deno.serve({ port }, req => {
  const { socket, response } = Deno.upgradeWebSocket(req);
  socket.onopen = () => console.timeEnd('ws open');
  return response;
});

console.time('ws open');
const tcp = await Deno.connect({ port });
await tcp.writable.getWriter().write(
  new TextEncoder().encode([
    "GET / HTTP/1.1",
    "connection: upgrade",
    "upgrade: websocket",
    "sec-websocket-key: SGVsbG8sIHdvcmxkIQ==",
    "\n",
  ].join("\n")),
);

ws open: 3ms

e3dio avatar Feb 12 '24 04:02 e3dio

Interesting. It sounds like Tokio isn't waking up when it should. Thanks for the info.

mmastrac avatar Feb 12 '24 04:02 mmastrac

deno 1.40.4 Windows11

const port = 3007;
const server = Deno.serve({ port }, req => {
  const { socket, response } = Deno.upgradeWebSocket(req);
  socket.onopen = () => console.timeEnd('ws open');
  return response;
});

console.time('ws open');
const ws = new WebSocket(`ws://localhost:${port}`);

ws open: 2022ms

const port = 3007;
const server = Deno.serve({ port }, req => {
  const { socket, response } = Deno.upgradeWebSocket(req);
  socket.onopen = () => console.timeEnd('ws open');
  return response;
});

console.time('ws open');
const ws = new WebSocket(`ws://127.0.0.1:${port}`);

ws open: 3ms

nobitextcode avatar Feb 12 '24 18:02 nobitextcode

I see the same, 127.0.0.1 gives 2ms, localhost gives 2050ms. I tried fetch instead of new WebSocket() and I get 310ms for localhost and 2ms for 127.0.0.1, so WebSocket is taking extra long for some reason, and not sure why localhost is slower for both. But I can use 127.0.0.1 for now so I don't have to wait 2 seconds :)

const port = 3007;
const server = Deno.serve({ port }, r => new Response());

console.time();
await fetch(`http://localhost:${port}`);
console.timeEnd();

console.time();
await fetch(`http://127.0.0.1:${port}`);
console.timeEnd();

default: 310ms default: 2ms

e3dio avatar Feb 12 '24 19:02 e3dio

If you Google "localhost slow windows" it turns up a bunch of results from different servers of different kinds and makes. I think this might be a fairly "known" Windows thing. One possibility is that localhost ends up resolving to the IPv6 address? Or then again if may be something completely unrelated to that.

aapoalas avatar Feb 12 '24 19:02 aapoalas

I tried new WebSocket('ws://localhost') from different client (chrome browser) and I get 310ms, same for fetch. 127.0.0.1 is 2ms. So Deno WebSocket is taking 1700ms longer for some reason than other clients on localhost

e3dio avatar Feb 12 '24 19:02 e3dio

Have noticed that explicitly setting hostname to localhost when listening seems to resolve the issue.

// ensure hostname is set (omitting the hostname results in 2000ms connection times)
await Deno.serve({ port: 5000, hostname: 'localhost' }, request => {
  const { response, socket } = Deno.upgradeWebSocket(request)
  socket.addEventListener('open', () => console.log('server open'))
  return response
})

// measure time to open: (around 8ms)
console.time('client open')
const socket = new WebSocket('ws://localhost:5000')
socket.addEventListener('open', () => console.timeEnd('client open'))

Not entirely sure what the issue is, but assume windows is having problems differentiating between IPv4 and IPv6 on localhost. If omitting the hostname, you can attain similar performance by passing just passing a IPv4 loopback address.

await Deno.serve({ port: 5000 }, request => { ... }) // omit hostname

console.time('client open')
const socket = new WebSocket('ws://127.0.0.1:5000') // around 8ms
socket.addEventListener('open', () => console.timeEnd('client open'))

Is this still a Deno bug? (it is curious this issue would be specific to WebSocket connections). Also, would it make sense for Deno to automatically assign the hostname option to localhost if not specified by the user?

sinclairzx81 avatar Aug 03 '24 08:08 sinclairzx81