-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
Description
Bug report
Bug description:
Unable to reliably be able to reuse a outgoing port using a client socket.
Depending on the interpreter version and/or OS, it sometime fails, sometime succeed.
import niquests
if __name__ == "__main__":
with niquests.Session(source_address=("0.0.0.0", 8755)) as s:
print(s.get("https://1.1.1.1"))
with niquests.Session(source_address=("0.0.0.0", 8755)) as s:
print(s.get("https://1.1.1.1"))import niquests
import asyncio
async def main():
async with niquests.AsyncSession(source_address=("0.0.0.0", 8755)) as s:
print(await s.get("https://1.1.1.1"))
async with niquests.AsyncSession(source_address=("0.0.0.0", 8755)) as s:
print(await s.get("https://1.1.1.1"))
if __name__ == "__main__":
asyncio.run(main())You should get intermittent [Errno 99] Cannot assign requested address or similar depending on the OS.
I ran the following in:
- Windows
- MacOS
- Linux
Across Python 3.7 -- 3.13
Here are the results:
-
Linux 3.7 (sync OK, async OK)
-
Windows 3.7 (sync OK, async OK)
-
Linux 3.11 (sync OK, async KO)
-
Windows 3.11 (sync OK, async KO)
-
Windows 3.10 (sync KO, async KO)
-
MacOS 3.8+ (sync KO, async KO)
Curiously, if you ran:
import niquests
if __name__ == "__main__":
with niquests.Session(source_address=("0.0.0.0", 8755)) as s:
print(s.get("https://1.1.1.1"))By running the interpreter twice (exec python sample.py twice or more), it will work as much as needed. Something happen at interpreter shutdown that should happen before?
Low level speaking, socket.SO_REUSEPORT is applied when available, otherwise using socket.SO_REUSEADDR instead.
The sock.bind((addr, port)) is applied after setting sock opts and before connecting to remote peer.
As it seems to work flawlessly on Python 3.7, I expected it to work on later versions also.
See the minimal code to reproduce this (sync only):
import socket
def cpython_bug_bind_so_reuseport():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
except (AttributeError, OSError): # Windows branch or old OS
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("0.0.0.0", 5784))
sock.connect(("1.1.1.1", 443))
sock.shutdown(socket.SHUT_RD)
sock.close()
if __name__ == "__main__":
cpython_bug_bind_so_reuseport()
cpython_bug_bind_so_reuseport()I posted the "higher" level code because, sometime the execution does not raise "Cannot assign requested address" but timeout instead. So the bind and connect pass but the socket is unusable. You will have to insist a bit to get this behavior.
Did I miss something? The official docs does not clearly mention .bind(..) usage with client-side socket, so we're in a grey area.
Regards,
CPython versions tested on:
3.9, 3.10, 3.11, 3.12, 3.13
Operating systems tested on:
Linux, macOS, Windows