之前写过如何 在服务器重启的时候感知长连接,最近发现折腾复杂了。

https://github.com/encode/starlette/discussions/1776

测试代码:

import asyncio

async def async_streamer():
    try:
        while True:
            yield b"--boundary\r\nContent-Type: text/plain\r\nContent-Length: 1\r\n\r\n1\r\n"
            await asyncio.sleep(0)
    except asyncio.CancelledError:
        print("caught cancelled error")

app = Starlette(routes=[
    Route('/async', async_endpoint),
])

跑起来: uvicorn stream:app

这段代码粗一看没啥大不了的,但是神奇的地方在于,如果去掉 try ... except asyncio.CancelledError ,代码也能正常跑


async def async_streamer():
    while True:
        yield b"--boundary\r\nContent-Type: text/plain\r\nContent-Length: 1\r\n\r\n1\r\n"
        await asyncio.sleep(0)

而且不会报错!最蛋痛的是,这玩意因为是个 while True,如果你里面有打开的数据库连接,是不会中断的,也不会回收的。因为这个 coroutine 没有继续 async for 来消费,就一直挂在进程里当僵尸了!

加上 try ... except asyncio.CancelledError,能捕获出错,也能处理善后了。

真是神奇啊。不知道是 uvicorn 的特性,还是 ASGI 都这样。