我们在EC2上运行的码头容器中有一个机器学习模型。
我们使用Cortex.dev来实现自动刻度GPU。
不确定的是,在call_next
中间件中,请求将在FastAPI函数期间挂起。不幸的是,这是不可复制的。
Middleware pre-request
打印行将被记录,但是路径操作函数中的第一个print语句永远不会被记录。
我们尝试过的事情:
run
函数不带异步地运行bytes
作为image
而不是UploadFile
的参数类型运行这些更改都不能解决挂起的问题,但这是最具表现力的配置。
Dockerfile
FROM nvidia/cuda:11.4.0-runtime-ubuntu18.04
WORKDIR /usr/src/app
RUN apt-get -y update && \
apt-get install -y --fix-missing \
build-essential \
cmake \
python3 \
python3-pip \
ffmpeg \
libsm6 \
libxext6 \
&& apt-get clean && rm -rf /tmp/* /var/tmp/*
ADD ./requirements.txt ./
# install our dependencies
RUN python3 -m pip install --upgrade pip && python3 -m pip install -r requirements.txt && apt-get clean && rm -rf /tmp/* /var/tmp/*
ADD ./ ./
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
EXPOSE 8080
CMD uvicorn api:app --host 0.0.0.0 --port 8080 --workers 2
api.py
from my_predictor import PythonPredictor
from typing import Optional
from datetime import datetime
import time
from starlette.responses import Response
from fastapi import FastAPI, File, UploadFile, Form, Response, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
cortex_id = request.headers.get('x-request-id')
start_time = time.time()
print("Cortex ID: " + cortex_id + ". > Middleware pre-request. Time stamp: " + str(start_time), flush=True)
response = await call_next(request)
process_time = time.time() - start_time
print("Cortex ID: " + cortex_id + ". > Middleware post-response. Duration: " + str(process_time), flush=True)
return response
@app.post("/")
async def run(request: Request, image: UploadFile = File(...), renderFactor:Optional[int] = Form(12), requestId:Optional[str] = Form('-1'),include_header:Optional[str] = Form('bin')):
try:
cortexId = request.headers.get('x-request-id')
print("Cortex ID: " + cortexId + ". Request ID: " + requestId + " >>> Request received. Time stamp: " + str(datetime.now()))
start = time.time()
image = await image.read()
payload = {}
payload['image'] = image
payload['renderFactor'] = renderFactor
payload['requestId'] = requestId
payload['include_header'] = include_header
response = pred.predict(payload)
end = time.time()
totalTime = round(end - start, 2)
print("Cortex ID: " + cortexId + ". Request ID: " + requestId + " > Request processed. Duration: " + str(totalTime) + " seconds. Time stamp: " + str(datetime.now()))
if totalTime > 5:
print("Long request detected. Duration: " + str(totalTime))
return response
except Exception as error:
end = time.time()
print(str(error))
print("Cortex ID: " + cortexId + ". Request ID: " + requestId + " > Error. Duration: " + str(round(end - start, 2)) + " seconds . Time stamp: " + str(datetime.now()))
raise HTTPException(status_code = 500, detail = str(error))
config = {}
pred = PythonPredictor(config)
发布于 2022-04-09 20:05:02
基本信息、根本原因与咆哮
嘿,我花了相当多的时间在这个悬而未决的问题上(在我的组织中有多个自定义MDW的关键应用程序)。这基本上是因为基于@app.middleware("http")
的中间件是从Starlette的BaseHTTPMiddleware
继承而来的后端产品。因此,通过显式继承BaseHTTPMiddleware
编写的MDW也存在这个问题。原因是相当复杂的,这也是我迄今所理解的:
StreamingResponse
,这有一些问题request.json()
一次,而BaseHTTPMiddleware
也会自己创建一个请求对象(这会导致挂起问题,因为这是另一个请求)。最后一个链接还提到,也会导致挂起问题的原因是,由于StreamingResponse
;响应的读取在第一次读取时不知怎么地被耗尽了,当涉及到第二次读取时,它一直在无限期地等待它,从而导致挂起。(这里第一次和第二次阅读意味着:在ASGI应用程序中,消息通过各种类型的客户端和应用程序(如http.response.start
、http.response.body
等)发送。)
解决方案
所以,不要使用任何与BaseHTTPMiddleware
有关的东西。为了解决这一问题,我使用这里提供的ASGI规范编写了我的所有定制中间件
您可以使您的自定义中间件如下所示:
from starlette.types import ASGIApp, Receive, Send, Message
class LogProcessingTime:
def __init__(self, app: ASGIApp) -> None:
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send):
start_time = time.time()
async def send_wrapper(message: Message):
# This will capture response coming from APP Layer
# response body will be in message where the type is
# "http.response.body"
if message["type"] == "http.response.body":
process_time = time.time() - start_time
# you can log this process_time now any way you prefer
await send(message)
await self.app(scope, receive, send_wrapper)
# you can add this to your app this way:
app.add_middleware(LogProcessingTime)
发布于 2021-11-28 06:51:36
解决方案1
从您的代码中,我可以看到您使用了来自starlette.responses
包和FastAPI
的响应包的响应,这可能导致了挂起问题。
from starlette.responses import Response
# remove the Response from fastapi
from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request
解决方案2
如果你的问题仍然存在
FastAPI文档声明从fastapi
包导入的Request
最好是从Starlette
(Starlette请求文档链接)中导入
您也可以使用
from starlette.requests import Request
。FastAPI为开发人员提供了方便。但它直接来自Starlette。
用from starlette.requests import Request
替换from fastapi import Request
在官方的uvicorn <file>:app
. github问题中也有类似的问题,这个应用程序是用FastAPI运行的下面直接用starlette.requests
实现的代码块并没有产生挂起的问题,这表明这个问题是FastAPI造成的。
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
app = Starlette()
@app.middleware("http")
async def func(request: Request, call_next):
#print(await request.json())
return await call_next(request)
@app.route('/', methods=["POST"])
def homepage(request):
return JSONResponse({"Hello": "World"})
确保在代码中使用starlette.requests
和starlette.responses
,如下所示
from starlette.responses import Response
from starlette.requests import Request
# Request and Response removed from fastapi as directly referred from starlette
from fastapi import FastAPI, File, UploadFile, Form, HTTPException
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
cortex_id = request.headers.get('x-request-id')
start_time = time.time()
print("Cortex ID: " + cortex_id + ". > Middleware pre-request. Time stamp: " + str(start_time), flush=True)
response = await call_next(request)
process_time = time.time() - start_time
print("Cortex ID: " + cortex_id + ". > Middleware post-response. Duration: " + str(process_time), flush=True)
return response
https://stackoverflow.com/questions/70043665
复制相似问题