diff --git a/app/services/radio_noise_floor.py b/app/services/radio_noise_floor.py index d928821..beb9ce9 100644 --- a/app/services/radio_noise_floor.py +++ b/app/services/radio_noise_floor.py @@ -60,8 +60,17 @@ async def sample_noise_floor_once(*, blocking: bool = False) -> None: async def _noise_floor_sampling_loop() -> None: while True: - await sample_noise_floor_once() - await asyncio.sleep(NOISE_FLOOR_SAMPLE_INTERVAL_SECONDS) + try: + await sample_noise_floor_once() + except asyncio.CancelledError: + raise + except Exception: + logger.exception("Noise floor sampling loop crashed during sample") + + try: + await asyncio.sleep(NOISE_FLOOR_SAMPLE_INTERVAL_SECONDS) + except asyncio.CancelledError: + raise async def start_noise_floor_sampling() -> None: diff --git a/tests/test_radio_noise_floor.py b/tests/test_radio_noise_floor.py new file mode 100644 index 0000000..33d1c2b --- /dev/null +++ b/tests/test_radio_noise_floor.py @@ -0,0 +1,37 @@ +import asyncio +from unittest.mock import patch + +import pytest + +from app.services import radio_noise_floor + + +class TestNoiseFloorSamplingLoop: + @pytest.mark.asyncio + async def test_logs_and_continues_after_unexpected_sample_exception(self): + sample_calls = 0 + sleep_calls = 0 + + async def fake_sample() -> None: + nonlocal sample_calls + sample_calls += 1 + if sample_calls == 1: + raise RuntimeError("boom") + + async def fake_sleep(_seconds: int) -> None: + nonlocal sleep_calls + sleep_calls += 1 + if sleep_calls >= 2: + raise asyncio.CancelledError() + + with ( + patch.object(radio_noise_floor, "sample_noise_floor_once", side_effect=fake_sample), + patch.object(radio_noise_floor.asyncio, "sleep", side_effect=fake_sleep), + patch.object(radio_noise_floor.logger, "exception") as mock_exception, + ): + with pytest.raises(asyncio.CancelledError): + await radio_noise_floor._noise_floor_sampling_loop() + + assert sample_calls == 2 + assert sleep_calls == 2 + mock_exception.assert_called_once()