11# SPDX-License-Identifier: Apache-2.0
22# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
33
4- from collections . abc import Awaitable
4+ import asyncio
55
66from fastapi .responses import JSONResponse
77from starlette .types import ASGIApp , Receive , Scope , Send
@@ -23,26 +23,34 @@ def set_rejecting_requests(value: bool) -> None:
2323
2424
2525class ServiceUnavailableMiddleware :
26- """
27- Middleware that checks if the server is currently unavailable
28- (e.g., scaling or draining) and returns a 503 Service Unavailable.
29-
30- """
26+ """Returns 503 during drain; suppresses CancelledError on force-exit."""
3127
3228 def __init__ (self , app : ASGIApp ) -> None :
3329 self .app = app
3430
35- def __call__ (self , scope : Scope , receive : Receive , send : Send ) -> Awaitable [ None ] :
31+ async def __call__ (self , scope : Scope , receive : Receive , send : Send ) -> None :
3632 if scope ["type" ] != "http" :
37- return self .app (scope , receive , send )
33+ await self .app (scope , receive , send )
34+ return
3835
39- path = scope .get ("path" , "" )
40- rejecting = is_rejecting_requests ()
41- if rejecting and path not in _EXEMPT_PATHS :
36+ if is_rejecting_requests () and scope .get ("path" , "" ) not in _EXEMPT_PATHS :
4237 response = JSONResponse (
4338 content = {"error" : "Server is unavailable. Please try again later." },
4439 status_code = 503 ,
4540 )
46- return response (scope , receive , send )
47-
48- return self .app (scope , receive , send )
41+ await response (scope , receive , send )
42+ return
43+
44+ try :
45+ await self .app (scope , receive , send )
46+ except asyncio .CancelledError :
47+ if not is_rejecting_requests ():
48+ raise
49+ try :
50+ response = JSONResponse (
51+ content = {"error" : "Server is shutting down." },
52+ status_code = 503 ,
53+ )
54+ await response (scope , receive , send )
55+ except (Exception , asyncio .CancelledError ):
56+ pass
0 commit comments