From 29e9a5f70162997d622dd999de8dfe478ab07487 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Tue, 31 Mar 2026 12:35:35 -0700 Subject: [PATCH] Be more resilient in noise floor gathering --- app/services/radio_noise_floor.py | 13 +++++++++-- tests/test_radio_noise_floor.py | 37 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 tests/test_radio_noise_floor.py 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()