Files
Octop/tests/unit/infra/utils/test_accept_errors.py

77 lines
2.5 KiB
Python

"""Resource exhaustion must not emit a traceback for every accept attempt."""
from __future__ import annotations
import asyncio
import errno
import logging
import pytest
from octop.infra.utils.accept_errors import coalesce_accept_errors
@pytest.mark.asyncio
async def test_accept_burst_keeps_first_traceback_and_flushes_count(caplog):
loop = asyncio.get_running_loop()
previous = loop.get_exception_handler()
context = {
"message": "socket.accept() out of system resource",
"exception": OSError(errno.EMFILE, "Too many open files"),
}
with caplog.at_level(logging.WARNING), coalesce_accept_errors(loop):
for _ in range(2048):
loop.call_exception_handler(context)
assert len(caplog.records) == 1
assert caplog.records[0].exc_info[1] is context["exception"]
assert loop.get_exception_handler() is previous
assert len(caplog.records) == 2
assert "2047" in caplog.records[-1].getMessage()
@pytest.mark.asyncio
async def test_other_exceptions_reach_existing_handler():
loop = asyncio.get_running_loop()
previous = loop.get_exception_handler()
seen = []
def handler(_loop, context):
seen.append(context)
loop.set_exception_handler(handler)
contexts = [
{"message": "task failed", "exception": OSError(errno.EMFILE, "file open failed")},
{
"message": "socket.accept() out of system resource",
"exception": OSError(errno.EBADF, "bad fd"),
},
]
try:
with coalesce_accept_errors(loop):
for context in contexts:
loop.call_exception_handler(context)
assert seen == contexts
assert loop.get_exception_handler() is handler
finally:
loop.set_exception_handler(previous)
@pytest.mark.asyncio
async def test_window_flushes_while_the_server_keeps_running(monkeypatch, caplog):
from octop.infra.utils import accept_errors
monkeypatch.setattr(accept_errors, "ACCEPT_ERROR_WINDOW_SECONDS", 0)
loop = asyncio.get_running_loop()
context = {
"message": "socket.accept() out of system resource",
"exception": OSError(errno.ENFILE, "file table overflow"),
}
with caplog.at_level(logging.WARNING), coalesce_accept_errors(loop):
for _ in range(3):
loop.call_exception_handler(context)
await asyncio.sleep(0.001)
assert len(caplog.records) == 2
assert "2 additional" in caplog.records[-1].getMessage()
loop.call_exception_handler(context)
assert len(caplog.records) == 3