gpt2api / api /app.py
jiayi.xie
Make blocking threadpool size configurable to lift image concurrency
a33013a
Raw
History Blame Contribute Delete
2.95 kB
from __future__ import annotations
from contextlib import asynccontextmanager
from threading import Event
import anyio
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from api import accounts, ai, image_tasks, openai_keys, register, system
from api.support import resolve_web_asset, start_limited_account_watcher
from services.backup_service import backup_service
from services.config import config
from utils.log import logger
def _apply_blocking_threadpool_size() -> None:
# 抬高 anyio run_in_threadpool 线程池上限(默认 40),解除生图/文本接口的同时并发硬顶。
# 必须在事件循环内调用(limiter 绑定当前 async backend),故放在 lifespan 启动里。
size = config.blocking_threadpool_size
try:
limiter = anyio.to_thread.current_default_thread_limiter()
if limiter.total_tokens != size:
limiter.total_tokens = size
logger.info({"event": "blocking_threadpool_configured", "total_tokens": limiter.total_tokens})
except Exception as exc:
logger.warning({"event": "blocking_threadpool_config_failed", "error": str(exc)})
def create_app() -> FastAPI:
app_version = config.app_version
@asynccontextmanager
async def lifespan(_: FastAPI):
_apply_blocking_threadpool_size()
stop_event = Event()
thread = start_limited_account_watcher(stop_event)
backup_service.start()
config.cleanup_old_images()
try:
yield
finally:
stop_event.set()
thread.join(timeout=1)
backup_service.stop()
app = FastAPI(title="chatgpt2api", version=app_version, lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(ai.create_router())
app.include_router(accounts.create_router())
app.include_router(openai_keys.create_router())
app.include_router(image_tasks.create_router())
app.include_router(register.create_router())
app.include_router(system.create_router(app_version))
if config.images_dir.exists():
app.mount("/images", StaticFiles(directory=str(config.images_dir)), name="images")
@app.get("/{full_path:path}", include_in_schema=False)
async def serve_web(full_path: str):
asset = resolve_web_asset(full_path)
if asset is not None:
return FileResponse(asset)
if full_path.strip("/").startswith("_next/"):
raise HTTPException(status_code=404, detail="Not Found")
fallback = resolve_web_asset("")
if fallback is None:
raise HTTPException(status_code=404, detail="Not Found")
return FileResponse(fallback)
return app