Treat fd=0 as a valid file descriptor with reload/workers (#2927)

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
This commit is contained in:
Eugene Toder 2026-04-30 14:26:13 -04:00 committed by GitHub
parent 6761b2c8f9
commit ad5ff87c86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 40 additions and 13 deletions

View File

@ -553,6 +553,37 @@ def test_bind_fd_works_with_reload_or_workers(reload: bool, workers: int): # pr
fdsock.close() fdsock.close()
@pytest.fixture
def stdin_socket() -> Iterator[socket.socket]: # pragma: py-win32
with closing(socket.socket(socket.AF_INET)) as sock:
sock.bind(("127.0.0.1", 0))
saved_stdin = os.dup(0)
os.dup2(sock.fileno(), 0)
try:
yield sock
finally:
os.dup2(saved_stdin, 0)
os.close(saved_stdin)
@pytest.mark.parametrize(
"reload, workers",
[
(True, 1),
(False, 2),
],
ids=["--reload=True --workers=1", "--reload=False --workers=2"],
)
@pytest.mark.skipif(sys.platform == "win32", reason="require unix-like system")
def test_bind_stdin_works_with_reload_or_workers(
reload: bool, workers: int, stdin_socket: socket.socket
): # pragma: py-win32
config = Config(app=asgi_app, fd=0, reload=reload, workers=workers)
config.load()
with closing(config.bind_socket()) as sock:
assert sock.getsockname() == stdin_socket.getsockname()
@pytest.mark.parametrize( @pytest.mark.parametrize(
"reload, workers, expected", "reload, workers, expected",
[ [

View File

@ -142,17 +142,13 @@ async def test_limit_max_requests_jitter(
config = Config( config = Config(
app=app, limit_max_requests=1, limit_max_requests_jitter=2, port=unused_tcp_port, http=http_protocol_cls app=app, limit_max_requests=1, limit_max_requests_jitter=2, port=unused_tcp_port, http=http_protocol_cls
) )
server = Server(config=config) async with run_server(config) as server:
limit = server.limit_max_requests limit = server.limit_max_requests
assert limit is not None assert limit is not None
assert 1 <= limit <= 3 assert 1 <= limit <= 3
task = asyncio.create_task(server.serve()) async with httpx.AsyncClient() as client:
while not server.started: tasks = [client.get(f"http://127.0.0.1:{unused_tcp_port}") for _ in range(limit + 1)]
await asyncio.sleep(0.01) await asyncio.gather(*tasks)
async with httpx.AsyncClient() as client:
for _ in range(limit + 1):
await client.get(f"http://127.0.0.1:{unused_tcp_port}")
await task
assert f"Maximum request limit of {limit} exceeded. Terminating process." in caplog.text assert f"Maximum request limit of {limit} exceeded. Terminating process." in caplog.text

View File

@ -537,7 +537,7 @@ class Config:
def bind_socket(self) -> socket.socket: def bind_socket(self) -> socket.socket:
logger_args: list[str | int] logger_args: list[str | int]
if self.uds: # pragma: py-win32 if self.uds is not None: # pragma: py-win32
path = self.uds path = self.uds
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try: try:
@ -552,7 +552,7 @@ class Config:
sock_name_format = "%s" sock_name_format = "%s"
color_message = "Uvicorn running on " + click.style(sock_name_format, bold=True) + " (Press CTRL+C to quit)" color_message = "Uvicorn running on " + click.style(sock_name_format, bold=True) + " (Press CTRL+C to quit)"
logger_args = [self.uds] logger_args = [self.uds]
elif self.fd: # pragma: py-win32 elif self.fd is not None: # pragma: py-win32
sock = socket.fromfd(self.fd, socket.AF_UNIX, socket.SOCK_STREAM) sock = socket.fromfd(self.fd, socket.AF_UNIX, socket.SOCK_STREAM)
message = "Uvicorn running on socket %s (Press CTRL+C to quit)" message = "Uvicorn running on socket %s (Press CTRL+C to quit)"
fd_name_format = "%s" fd_name_format = "%s"