Revert early HTTP port reservation (#17754, #19805) (#20468)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kangyan-Zhou
2026-03-12 16:17:33 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 9c6f166600
commit f5a4a5429f
2 changed files with 63 additions and 130 deletions
+9 -21
View File
@@ -173,7 +173,7 @@ from sglang.srt.utils import (
set_uvicorn_logging_configs, set_uvicorn_logging_configs,
) )
from sglang.srt.utils.auth import AuthLevel, app_has_admin_force_endpoints, auth_level from sglang.srt.utils.auth import AuthLevel, app_has_admin_force_endpoints, auth_level
from sglang.utils import _prebind_listening_socket, get_exception_traceback from sglang.utils import get_exception_traceback
from sglang.version import __version__ from sglang.version import __version__
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -1971,17 +1971,9 @@ def _setup_and_run_http_server(
Called by launch_server after subprocesses have been launched. Called by launch_server after subprocesses have been launched.
""" """
# Reserve the HTTP port before launching subprocesses to fail fast if port is unavailable.
# This prevents wasting time loading models only to discover port conflicts later.
reserved_socket = _prebind_listening_socket(server_args.host, server_args.port)
multi_tokenizer_args_shm = None
try:
# Parse info got from the schedulers # Parse info got from the schedulers
remote_instance_transfer_engine_info = ( remote_instance_transfer_engine_info = (
parse_remote_instance_transfer_engine_info_from_scheduler_infos( parse_remote_instance_transfer_engine_info_from_scheduler_infos(scheduler_infos)
scheduler_infos
)
) )
# Set global states # Set global states
@@ -2036,13 +2028,10 @@ def _setup_and_run_http_server(
port_args, server_args, scheduler_infos[0] port_args, server_args, scheduler_infos[0]
) )
try:
# Update logging configs # Update logging configs
set_uvicorn_logging_configs(server_args) set_uvicorn_logging_configs(server_args)
# Delay listen() until uvicorn startup to avoid accepting probe traffic
# while model/subprocess initialization is still in progress.
reserved_socket.listen(128)
if server_args.ssl_certfile: if server_args.ssl_certfile:
logger.info( logger.info(
f"SSL enabled: certfile={server_args.ssl_certfile}, " f"SSL enabled: certfile={server_args.ssl_certfile}, "
@@ -2055,7 +2044,8 @@ def _setup_and_run_http_server(
# Use Config/Server API for access to the SSLContext. # Use Config/Server API for access to the SSLContext.
config = uvicorn.Config( config = uvicorn.Config(
app, app,
fd=reserved_socket.fileno(), host=server_args.host,
port=server_args.port,
root_path=server_args.fastapi_root_path, root_path=server_args.fastapi_root_path,
log_level=server_args.log_level_http or server_args.log_level, log_level=server_args.log_level_http or server_args.log_level,
timeout_keep_alive=envs.SGLANG_TIMEOUT_KEEP_ALIVE.get(), timeout_keep_alive=envs.SGLANG_TIMEOUT_KEEP_ALIVE.get(),
@@ -2091,7 +2081,8 @@ def _setup_and_run_http_server(
# Default case, one tokenizer process # Default case, one tokenizer process
uvicorn.run( uvicorn.run(
app, app,
fd=reserved_socket.fileno(), host=server_args.host,
port=server_args.port,
root_path=server_args.fastapi_root_path, root_path=server_args.fastapi_root_path,
log_level=server_args.log_level_http or server_args.log_level, log_level=server_args.log_level_http or server_args.log_level,
timeout_keep_alive=envs.SGLANG_TIMEOUT_KEEP_ALIVE.get(), timeout_keep_alive=envs.SGLANG_TIMEOUT_KEEP_ALIVE.get(),
@@ -2121,7 +2112,8 @@ def _setup_and_run_http_server(
uvicorn.run( uvicorn.run(
"sglang.srt.entrypoints.http_server:app", "sglang.srt.entrypoints.http_server:app",
fd=reserved_socket.fileno(), host=server_args.host,
port=server_args.port,
root_path=server_args.fastapi_root_path, root_path=server_args.fastapi_root_path,
log_level=server_args.log_level_http or server_args.log_level, log_level=server_args.log_level_http or server_args.log_level,
timeout_keep_alive=envs.SGLANG_TIMEOUT_KEEP_ALIVE.get(), timeout_keep_alive=envs.SGLANG_TIMEOUT_KEEP_ALIVE.get(),
@@ -2133,10 +2125,6 @@ def _setup_and_run_http_server(
ssl_keyfile_password=server_args.ssl_keyfile_password, ssl_keyfile_password=server_args.ssl_keyfile_password,
) )
finally: finally:
# Close the reserved socket after uvicorn exits or on any error
# This ensures the port is released even if initialization fails
reserved_socket.close()
if server_args.tokenizer_worker_num > 1: if server_args.tokenizer_worker_num > 1:
if multi_tokenizer_args_shm is not None: if multi_tokenizer_args_shm is not None:
multi_tokenizer_args_shm.unlink() multi_tokenizer_args_shm.unlink()
-55
View File
@@ -409,61 +409,6 @@ def reserve_port(host, start=30000, end=40000):
raise RuntimeError("No free port available.") raise RuntimeError("No free port available.")
def _prebind_listening_socket(host: str, port: int) -> socket.socket:
"""
Create and bind a server socket to reserve the port before model loading.
This prevents port conflicts that could occur if another process (e.g., a
subprocess spawned during model loading) binds to the same port before the
HTTP server starts. By binding early, we ensure the port is available and
reserved for the HTTP server. The socket is intentionally not put into
listening mode here to avoid accepting probe connections before uvicorn is
ready to serve requests.
Args:
host: The host address to bind to.
port: The port number to bind to.
Returns:
A bound socket object.
Raises:
RuntimeError: If the port is already in use or permission is denied.
"""
import errno
from sglang.srt.utils.network import NetworkAddress
# Determine socket family based on address type
na = NetworkAddress(host or "0.0.0.0", port)
sock = socket.socket(family=na.family, type=socket.SOCK_STREAM)
# SO_REUSEADDR: Allows binding to a port in TIME_WAIT state
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind(na.to_bind_tuple())
sock.set_inheritable(True)
logger.info(f"Successfully reserved port {port} on host '{na.host}'")
except OSError as e:
sock.close()
if e.errno == errno.EADDRINUSE:
raise RuntimeError(
f"Port {port} is already in use on host '{na.host}'. "
f"Please choose a different port with --port or stop the process using this port. "
f"You can find the process using: lsof -i :{port} or netstat -tlnp | grep {port}"
) from e
elif e.errno == errno.EACCES:
raise RuntimeError(
f"Permission denied when trying to bind to port {port}. "
f"Ports below 1024 require root/sudo privileges. "
f"Consider using a port >= 1024 (e.g., --port 8000) or run with sudo."
) from e
else:
raise RuntimeError(f"Failed to bind to {na}: {e}") from e
return sock
def release_port(lock_socket): def release_port(lock_socket):
""" """
Release the reserved port by closing the lock socket. Release the reserved port by closing the lock socket.