ASGI lifespan failure on exception (#2627)
This commit is contained in:
parent
95ee518aec
commit
a3ff0c13b7
|
@ -9,7 +9,7 @@ from sanic.compat import Header
|
||||||
from sanic.exceptions import ServerError
|
from sanic.exceptions import ServerError
|
||||||
from sanic.helpers import Default
|
from sanic.helpers import Default
|
||||||
from sanic.http import Stage
|
from sanic.http import Stage
|
||||||
from sanic.log import logger
|
from sanic.log import error_logger, logger
|
||||||
from sanic.models.asgi import ASGIReceive, ASGIScope, ASGISend, MockTransport
|
from sanic.models.asgi import ASGIReceive, ASGIScope, ASGISend, MockTransport
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
from sanic.response import BaseHTTPResponse
|
from sanic.response import BaseHTTPResponse
|
||||||
|
@ -85,13 +85,27 @@ class Lifespan:
|
||||||
) -> None:
|
) -> None:
|
||||||
message = await receive()
|
message = await receive()
|
||||||
if message["type"] == "lifespan.startup":
|
if message["type"] == "lifespan.startup":
|
||||||
await self.startup()
|
try:
|
||||||
await send({"type": "lifespan.startup.complete"})
|
await self.startup()
|
||||||
|
except Exception as e:
|
||||||
|
error_logger.exception(e)
|
||||||
|
await send(
|
||||||
|
{"type": "lifespan.startup.failed", "message": str(e)}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await send({"type": "lifespan.startup.complete"})
|
||||||
|
|
||||||
message = await receive()
|
message = await receive()
|
||||||
if message["type"] == "lifespan.shutdown":
|
if message["type"] == "lifespan.shutdown":
|
||||||
await self.shutdown()
|
try:
|
||||||
await send({"type": "lifespan.shutdown.complete"})
|
await self.shutdown()
|
||||||
|
except Exception as e:
|
||||||
|
error_logger.exception(e)
|
||||||
|
await send(
|
||||||
|
{"type": "lifespan.shutdown.failed", "message": str(e)}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await send({"type": "lifespan.shutdown.complete"})
|
||||||
|
|
||||||
|
|
||||||
class ASGIApp:
|
class ASGIApp:
|
||||||
|
|
|
@ -8,7 +8,7 @@ import uvicorn
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.application.state import Mode
|
from sanic.application.state import Mode
|
||||||
from sanic.asgi import MockTransport
|
from sanic.asgi import ASGIApp, MockTransport
|
||||||
from sanic.exceptions import BadRequest, Forbidden, ServiceUnavailable
|
from sanic.exceptions import BadRequest, Forbidden, ServiceUnavailable
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
from sanic.response import json, text
|
from sanic.response import json, text
|
||||||
|
@ -16,6 +16,12 @@ from sanic.server.websockets.connection import WebSocketConnection
|
||||||
from sanic.signals import RESERVED_NAMESPACES
|
from sanic.signals import RESERVED_NAMESPACES
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from unittest.mock import AsyncMock
|
||||||
|
except ImportError:
|
||||||
|
from tests.asyncmock import AsyncMock # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def message_stack():
|
def message_stack():
|
||||||
return deque()
|
return deque()
|
||||||
|
@ -558,3 +564,39 @@ async def test_asgi_serve_location(app):
|
||||||
|
|
||||||
_, response = await app.asgi_client.get("/")
|
_, response = await app.asgi_client.get("/")
|
||||||
assert response.text == "http://<ASGI>"
|
assert response.text == "http://<ASGI>"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_error_on_lifespan_exception_start(app, caplog):
|
||||||
|
@app.before_server_start
|
||||||
|
async def before_server_start(_):
|
||||||
|
1 / 0
|
||||||
|
|
||||||
|
recv = AsyncMock(return_value={"type": "lifespan.startup"})
|
||||||
|
send = AsyncMock()
|
||||||
|
app.asgi = True
|
||||||
|
|
||||||
|
with caplog.at_level(logging.ERROR):
|
||||||
|
await ASGIApp.create(app, {"type": "lifespan"}, recv, send)
|
||||||
|
|
||||||
|
send.assert_awaited_once_with(
|
||||||
|
{"type": "lifespan.startup.failed", "message": "division by zero"}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_error_on_lifespan_exception_stop(app: Sanic):
|
||||||
|
@app.before_server_stop
|
||||||
|
async def before_server_stop(_):
|
||||||
|
1 / 0
|
||||||
|
|
||||||
|
recv = AsyncMock(return_value={"type": "lifespan.shutdown"})
|
||||||
|
send = AsyncMock()
|
||||||
|
app.asgi = True
|
||||||
|
await app._startup()
|
||||||
|
|
||||||
|
await ASGIApp.create(app, {"type": "lifespan"}, recv, send)
|
||||||
|
|
||||||
|
send.assert_awaited_once_with(
|
||||||
|
{"type": "lifespan.shutdown.failed", "message": "division by zero"}
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user