Files
lmcache--lmcache/lmcache/v1/internal_api_server/api_server.py
T
2026-07-13 12:24:33 +08:00

135 lines
4.6 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# Standard
from typing import TYPE_CHECKING
import asyncio
import os
import threading
# Third Party
from fastapi import FastAPI
import uvicorn
# First Party
from lmcache.logging import init_logger
# Local
from .api_registry import APIRegistry
if TYPE_CHECKING:
# First Party
from lmcache.v1.manager import LMCacheManager
logger = init_logger(__name__)
def _build_app() -> FastAPI:
"""Create a fresh FastAPI app with all internal API routes registered."""
new_app = FastAPI()
APIRegistry(new_app).register_all_apis()
return new_app
# Module-level app kept for backward compatibility with existing tests that
# import `app` directly. Production code no longer relies on this shared
# singleton; each InternalAPIServer owns its own FastAPI app so that
# multiple instances in the same process (e.g. scheduler + worker in TP=1
# non-MP mode) do not overwrite each other's app.state.lmcache_adapter.
app = _build_app()
class InternalAPIServer:
def __init__(self, lmcache_manager: "LMCacheManager"):
lmcache_engine = lmcache_manager.lmcache_engine
# Check if lmcache_engine is None and handle accordingly
if lmcache_engine is None:
# Use manager's config directly when engine is not available
config = lmcache_manager.config
port_offset = 0 # Default for scheduler mode
else:
config = lmcache_engine.config
# 0 for scheduler, 1 for worker 0, 2 for worker 1, ...
port_offset = 1 + lmcache_engine.metadata.worker_id
self.port = config.internal_api_server_port_start + port_offset
self.socket_path_prefix = config.internal_api_server_socket_path_prefix
self.socket_path = (
f"{self.socket_path_prefix}_{self.port}"
if self.socket_path_prefix
else None
)
include_index_list = config.internal_api_server_include_index_list
self.enable = True
if not config.internal_api_server_enabled or (
include_index_list and port_offset not in include_index_list
):
logger.info(
"Internal API server disabled. internal_api_server_enabled=%s, "
"port_offset=%s, port=%s, socket_path=%s, include_index_list=%s",
config.internal_api_server_enabled,
port_offset,
self.port,
self.socket_path,
include_index_list,
)
self.enable = False
return
self.app = _build_app()
uvicorn_config = {
"app": self.app,
"host": config.internal_api_server_host,
"loop": "uvloop",
"http": "httptools",
"access_log": config.get_extra_config_value(
"internal_api_server_access_log", True
),
"log_level": config.get_extra_config_value(
"internal_api_server_log_level", "warning"
),
}
if self.socket_path:
self.server_log_info = f"socket {self.socket_path}"
logger.info("Init internal API server on %s", self.server_log_info)
uvicorn_config["uds"] = self.socket_path
# Ensure socket directory exists
os.makedirs(os.path.dirname(self.socket_path), exist_ok=True)
# Remove existing socket file if exists
if os.path.exists(self.socket_path):
os.unlink(self.socket_path)
else:
self.server_log_info = f"port {self.port}"
logger.info("Init internal API server on %s", self.server_log_info)
uvicorn_config["port"] = self.port
self.server = uvicorn.Server(uvicorn.Config(**uvicorn_config))
self.app.state.lmcache_adapter = lmcache_manager
async def run(self):
logger.info("Running LMCache internal API server on %s", self.server_log_info)
if self.server:
await self.server.serve()
def start(self):
if not self.enable:
return
logger.info("Starting LMCache internal API server on %s", self.server_log_info)
threading.Thread(
target=asyncio.run,
args=(self.run(),),
daemon=True,
name="api-server-thread",
).start()
def stop(self):
if not self.enable:
return
logger.info("Stopping LMCache internal API server")
if self.server:
self.server.should_exit = True
if self.socket_path and os.path.exists(self.socket_path):
os.unlink(self.socket_path)