我有一个 FastAPI 应用程序 (obvs),它有一个端点,它使用 Twilio 的客户端发送消息同步。这没有奏效。我制作了一个简单的 python 文件,它只构建客户端并创建消息,它工作正常,所以我不怀疑它是 twilio 本身。使用 twilio 客户端进行呼叫时,服务器挂起/冻结。它永远不会超时。如果我在此期间更改文件,重新加载器也会冻结(我假设服务器已变得无响应)。无论我是否为此路由使用同步或异步路径定义,都会发生这种情况。其他异步和同步路由似乎工作正常(我还没有抽出时间来测试它们)。
fastapi==0.104.1
twilio==8.2.0
uvicorn==0.23.2
starlette==0.27.0
我像这样在本地运行应用程序(我还直接从命令行调用 uvicorn):
if __name__ == '__main__':
uvicorn.run('app:app', reload=True, port=5002)
我在单独的文件中有一个路由器,并在应用程序的构建器函数中调用 app.include_router(
from twilio.rest import Client
...get env variables
class TwilioAPI
def __init__(self, phone_number: str):
self.client = Client(account_sid, auth_token)
self.phone_number = phone_number
def send_sms(self, body: str):
# we enter the function, but this never returns/resolves
message = self.client.messages.create(
messaging_service_sid=messaging_service_sid,
body=body,
to=self.phone_number,
)
return message.sid
所讨论的路线如下所示:
@router.post("/endpoint")
def send_message_or_whatever(input: Input):
...get data from input, construct message
...we hit our database here and this works
twilio_api_client = CreateAnInstanceOfOurTwilioClient()
twilio_api_client.send_sms(message) <--- this is where it goes sideways
return stuff
我在 twilio 自己的博客上找到的所有示例都做了类似的事情
@router.post('/endpoint')
async def do_something():
client = twilio.rest.Client() # synchronous client
client.messages.create(...create message params)
我尝试过的东西:
使用异步和同步路径定义。即使我们在同步功能中“等待”twilio,但这并不重要?我们在其他点等待数据库,这是一个没有问题的网络调用。现在我什至不关心它是否不是性能最佳的东西。客户端不会返回必须等待的未来。
使用异步时,我尝试使用
await asyncio.get_event_loop().run_in_executor(...)
无济于事,什么也没有发生
我尝试使用fastapi的后台任务。它仍然卡在
client.messages.create
(我猜这是 asyncio.to_thread
或 run_in_executor
的包装)
我到底做错了什么?
PS:我知道 twilio 客户端的 AsyncHTTPClient,但我主要感兴趣的是了解为什么这不起作用
同步调用阻塞了事件循环。这就是为什么你会看到这种行为。在线程或线程池执行器中运行它会出现问题,因为 Twilio 默认使用请求会话,这不是线程安全的。
Twilio 还指出他们的客户端不应被视为线程安全的。
线程安全问题会导致死锁等问题。您可以通过在使用客户端的线程中创建客户端来避免此问题,而不是跨线程重用同一客户端。 但是你最好的选择就是使用异步客户端。