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
parent 9c6f166600
commit f5a4a5429f
2 changed files with 63 additions and 130 deletions

View File

@@ -173,7 +173,7 @@ from sglang.srt.utils import (
set_uvicorn_logging_configs,
)
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__
logger = logging.getLogger(__name__)
@@ -1971,78 +1971,67 @@ def _setup_and_run_http_server(
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
# Parse info got from the schedulers
remote_instance_transfer_engine_info = (
parse_remote_instance_transfer_engine_info_from_scheduler_infos(scheduler_infos)
)
# Set global states
set_global_state(
_GlobalState(
tokenizer_manager=tokenizer_manager,
template_manager=template_manager,
scheduler_info=scheduler_infos[0],
remote_instance_transfer_engine_info=remote_instance_transfer_engine_info,
)
)
if server_args.enable_metrics:
add_prometheus_track_response_middleware(app)
# Pass additional arguments to the lifespan function.
# They will be used for additional initialization setups.
if server_args.tokenizer_worker_num == 1:
# If it is single tokenizer mode, we can pass the arguments by attributes of the app object.
app.is_single_tokenizer_mode = True
app.server_args = server_args
app.warmup_thread_kwargs = dict(
server_args=server_args,
launch_callback=launch_callback,
execute_warmup_func=execute_warmup_func,
)
# Add api key authorization
# This is only supported in single tokenizer mode.
#
# Backward compatibility:
# - api_key only: behavior matches legacy (all endpoints require api_key)
# - no keys: legacy had no restriction; ADMIN_FORCE endpoints must still be rejected when
# admin_api_key is not configured.
if (
server_args.api_key
or server_args.admin_api_key
or app_has_admin_force_endpoints(app)
):
from sglang.srt.utils.auth import add_api_key_middleware
add_api_key_middleware(
app,
api_key=server_args.api_key,
admin_api_key=server_args.admin_api_key,
)
else:
# If it is multi-tokenizer mode, we need to write the arguments to shared memory
# for other worker processes to read.
app.is_single_tokenizer_mode = False
multi_tokenizer_args_shm = write_data_for_multi_tokenizer(
port_args, server_args, scheduler_infos[0]
)
try:
# Parse info got from the schedulers
remote_instance_transfer_engine_info = (
parse_remote_instance_transfer_engine_info_from_scheduler_infos(
scheduler_infos
)
)
# Set global states
set_global_state(
_GlobalState(
tokenizer_manager=tokenizer_manager,
template_manager=template_manager,
scheduler_info=scheduler_infos[0],
remote_instance_transfer_engine_info=remote_instance_transfer_engine_info,
)
)
if server_args.enable_metrics:
add_prometheus_track_response_middleware(app)
# Pass additional arguments to the lifespan function.
# They will be used for additional initialization setups.
if server_args.tokenizer_worker_num == 1:
# If it is single tokenizer mode, we can pass the arguments by attributes of the app object.
app.is_single_tokenizer_mode = True
app.server_args = server_args
app.warmup_thread_kwargs = dict(
server_args=server_args,
launch_callback=launch_callback,
execute_warmup_func=execute_warmup_func,
)
# Add api key authorization
# This is only supported in single tokenizer mode.
#
# Backward compatibility:
# - api_key only: behavior matches legacy (all endpoints require api_key)
# - no keys: legacy had no restriction; ADMIN_FORCE endpoints must still be rejected when
# admin_api_key is not configured.
if (
server_args.api_key
or server_args.admin_api_key
or app_has_admin_force_endpoints(app)
):
from sglang.srt.utils.auth import add_api_key_middleware
add_api_key_middleware(
app,
api_key=server_args.api_key,
admin_api_key=server_args.admin_api_key,
)
else:
# If it is multi-tokenizer mode, we need to write the arguments to shared memory
# for other worker processes to read.
app.is_single_tokenizer_mode = False
multi_tokenizer_args_shm = write_data_for_multi_tokenizer(
port_args, server_args, scheduler_infos[0]
)
# Update logging configs
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:
logger.info(
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.
config = uvicorn.Config(
app,
fd=reserved_socket.fileno(),
host=server_args.host,
port=server_args.port,
root_path=server_args.fastapi_root_path,
log_level=server_args.log_level_http or server_args.log_level,
timeout_keep_alive=envs.SGLANG_TIMEOUT_KEEP_ALIVE.get(),
@@ -2091,7 +2081,8 @@ def _setup_and_run_http_server(
# Default case, one tokenizer process
uvicorn.run(
app,
fd=reserved_socket.fileno(),
host=server_args.host,
port=server_args.port,
root_path=server_args.fastapi_root_path,
log_level=server_args.log_level_http or server_args.log_level,
timeout_keep_alive=envs.SGLANG_TIMEOUT_KEEP_ALIVE.get(),
@@ -2121,7 +2112,8 @@ def _setup_and_run_http_server(
uvicorn.run(
"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,
log_level=server_args.log_level_http or server_args.log_level,
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,
)
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 multi_tokenizer_args_shm is not None:
multi_tokenizer_args_shm.unlink()

View File

@@ -409,61 +409,6 @@ def reserve_port(host, start=30000, end=40000):
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):
"""
Release the reserved port by closing the lock socket.