denoland / deno

A modern runtime for JavaScript and TypeScript.
https://deno.com
MIT License
94.57k stars 5.25k forks source link

new WebSocket() takes 2 seconds to open ? #22388

Open e3dio opened 7 months ago

e3dio commented 7 months ago

Version: Deno 1.40.4

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}`);

ws open: 2050ms

mmastrac commented 7 months ago

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
e3dio commented 7 months ago

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

mmastrac commented 7 months ago

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.

e3dio commented 7 months ago

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

mmastrac commented 7 months ago

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

nobitextcode commented 7 months ago

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

e3dio commented 7 months ago

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

aapoalas commented 7 months ago

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.

e3dio commented 7 months ago

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

sinclairzx81 commented 2 months ago

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?