denoland / deno

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

Deno.listen should throw on same port #17330

Closed sant123 closed 1 year ago

sant123 commented 1 year ago
const listener1 = Deno.listen({
  port: 4505,
  hostname: "0.0.0.0",
  transport: "tcp",
});

console.log(listener1.addr);

const listener2 = Deno.listen({
  port: 4505,
  hostname: "0.0.0.0",
  transport: "tcp",
});

console.log(listener2.addr);

Expected behavior

The code should throw according with docs.

Current behavior

The constant listener2 will have assigned a random port.

image

Also this test is hanging because of this https://github.com/denoland/deno_std/blob/main/http/server_test.ts#L1280

magurotuna commented 1 year ago

I attempted to run your snippet both in macOS and in Ubuntu, and I saw it throwing an error as expected in both environments.

Ubuntu

$ deno --version
deno 1.29.2 (release, x86_64-unknown-linux-gnu)
v8 10.9.194.5
typescript 4.9.4

$ deno run --allow-net port.ts
{ hostname: "0.0.0.0", port: 4505, transport: "tcp" }
error: Uncaught AddrInUse: Address already in use (os error 98)
const listener2 = Deno.listen({
                       ^
    at Object.listen (deno:ext/net/01_net.js:333:33)
    at file:///tmp/port.ts:9:24

macOS

$ deno --version
deno 1.29.2 (release, aarch64-apple-darwin)
v8 10.9.194.5
typescript 4.9.4

$ deno run --allow-net port.ts
{ hostname: "0.0.0.0", port: 4505, transport: "tcp" }
error: Uncaught AddrInUse: Address already in use (os error 48)
const listener2 = Deno.listen({
                       ^
    at Object.listen (deno:ext/net/01_net.js:333:33)
    at file:///private/tmp/port.ts:9:24
sant123 commented 1 year ago

Interesting, I'm currently using a Fedora 36 machine. Also have the same Deno version as you mentioned earlier.

sant123 commented 1 year ago

image

kamilogorek commented 1 year ago

My hunch is that Fedoras implementation of TCP differs with how SO_REUSEADDR and SO_REUSEPORT behaves - https://man7.org/linux/man-pages/man7/socket.7.html

@sant123 you can try to play around with https://github.com/denoland/deno/blob/a6b3910bdfe0183e458015d00a61295779e46eb1/ext/net/ops.rs#L248-L292 locally and see what socket2 and std::net::TcpListener implementation tell you.

sant123 commented 1 year ago

Hey @kamilogorek don't exactly know how to play around with the code above 😅 perhaps could you check the behavior in a Docker container with Fedora please?

magurotuna commented 1 year ago

I have tested the original snippet using the fedora 36 container, and confirmed that it failed with AddrInUse as expected.

[root@cd48c6b3b08d tmp]# cat /etc/fedora-release
Fedora release 36 (Thirty Six)
[root@cd48c6b3b08d tmp]# cat port.ts
const listener1 = Deno.listen({
  port: 4505,
  hostname: "0.0.0.0",
  transport: "tcp",
});

console.log(listener1.addr);

const listener2 = Deno.listen({
  port: 4505,
  hostname: "0.0.0.0",
  transport: "tcp",
});

console.log(listener2.addr);
[root@cd48c6b3b08d tmp]# deno --version
deno 1.29.3 (release, x86_64-unknown-linux-gnu)
v8 10.9.194.5
typescript 4.9.4
[root@cd48c6b3b08d tmp]# deno run --allow-net port.ts
{ hostname: "0.0.0.0", port: 4505, transport: "tcp" }
error: Uncaught AddrInUse: Address already in use (os error 98)
const listener2 = Deno.listen({
                       ^
    at Object.listen (deno:ext/net/01_net.js:333:33)
    at file:///tmp/port.ts:9:24

@sant123 I'm wondering what happens if you try to run your code within the container. I would like to check if the issue is because of your OS setup or other. Also, could you run the following python one in your environment? This is to check if the issue happens only with Deno or not.

import socket

sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock1.bind(('0.0.0.0', 4505))
sock1.listen(5)
print('sock1 starts listening')

sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock2.bind(('0.0.0.0', 4505))
sock2.listen(5)
print('sock2 starts listening')

In my environment (e.g. macOS, Ubuntu, and Fedora 36 in docker), I got AddrInUse from this python script, which looks like:

[root@cd48c6b3b08d tmp]# python3 --version
Python 3.10.8
[root@cd48c6b3b08d tmp]# python3 port.py
sock1 starts listening
Traceback (most recent call last):
  File "/tmp/port.py", line 10, in <module>
    sock2.bind(('0.0.0.0', 4505))
OSError: [Errno 98] Address already in use
magurotuna commented 1 year ago

Oh, actually, the python script I would like you to try is:

import socket

sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock1.bind(('0.0.0.0', 4505))
sock1.listen(5)
print('sock1 starts listening')

sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock2.bind(('0.0.0.0', 4505))
sock2.listen(5)
print('sock2 starts listening')

This behaves in really similar way to what Deno does internally.

sant123 commented 1 year ago

I got the same behavior as in Deno:

image

😳

sant123 commented 1 year ago

And this is from a Fedora 36 container:

image

sant123 commented 1 year ago

docker run -it fedora:36 /bin/bash

sant123 commented 1 year ago

Perhaps the tests should be done in a Fedora installation? Because kernel versions in these distros are the most recent ones and since Docker uses the current kernel installed it causes that tests in Fedora containers to mismatch the expected result.

magurotuna commented 1 year ago

I gave it another try to run Fedora with vagrant (i.e. virtualbox) instead of docker, and got AddrInUse again. What you are seeing is really weird...

sant123 commented 1 year ago

I gave it another try to run Fedora with vagrant (i.e. virtualbox) instead of docker, and got AddrInUse again.

What you are seeing is really weird...

I'm curious did you try with Fedora 36 or the latest version? (Currently 37).

magurotuna commented 1 year ago

I was using this one (Fedora 36) https://app.vagrantup.com/fedora/boxes/36-cloud-base

sant123 commented 1 year ago

What is the Kernel of that VM? Mine is 6.0.18-200.fc36.x86_64

sant123 commented 1 year ago

Just updated the Kernel of my machine and now everything works as expected. New version is 6.1.5-100.fc36.x86_64.

image

magurotuna commented 1 year ago

That's a nice discovery @sant123. The kernel version I tried was 5.17.5-300.fc36.x86_64. I discovered that there is a regression report for Linux kernel which can be found at: https://lore.kernel.org/netdev/CAFsF8vJ3wS-Yoy9tNZD2ZESevGenvYKqieD4F8+UztRsrJ=png@mail.gmail.com/T/ According to this, 6.0.16 to 6.0.18 suffers from this problem, so it's most likely that you ran into this kernel regression.

I'm going to close this issue since it's turned out that this is actually not a issue with Deno. If you have another question or something, feel free to ask :)