From 57ff88b9492beb2a0bd577327bcdc9780e616d5e Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Mon, 6 May 2024 15:42:29 -0400 Subject: [PATCH] fix(api): clear ChangeNotifier's internal event immediately after waiting After the event flag is set(), the waiter no longer waits. If the event flag is not cleared immediately, this creates a potential scenario in which the notify() needs to set the event flag again before the waiter gets around to clearing it. This causes some events to be dropped. Simply clear the event flag immediately after waiting. --- api/src/opentrons/util/change_notifier.py | 2 +- .../opentrons/util/test_change_notifier.py | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/api/src/opentrons/util/change_notifier.py b/api/src/opentrons/util/change_notifier.py index e3ae1622c4c8..e05608cde45b 100644 --- a/api/src/opentrons/util/change_notifier.py +++ b/api/src/opentrons/util/change_notifier.py @@ -15,8 +15,8 @@ def notify(self) -> None: async def wait(self) -> None: """Wait until the next state change notification.""" - self._event.clear() await self._event.wait() + self._event.clear() class ChangeNotifier_ts(ChangeNotifier): diff --git a/api/tests/opentrons/util/test_change_notifier.py b/api/tests/opentrons/util/test_change_notifier.py index 7ab9f3d90135..bbaefbb2350a 100644 --- a/api/tests/opentrons/util/test_change_notifier.py +++ b/api/tests/opentrons/util/test_change_notifier.py @@ -54,3 +54,30 @@ async def _do_task_3() -> None: await asyncio.gather(task_1, task_2, task_3) assert results == [1, 2, 3] + + +async def test_notify_while_busy() -> None: + """Test that waiters process a new notify() after they are done being busy.""" + subject = ChangeNotifier() + results = [] + + async def _do_task() -> None: + results.append("TEST") + await asyncio.sleep(0.2) # Simulate being busy + + async def do_task() -> None: + while True: + await subject.wait() + await _do_task() + + task = asyncio.create_task(do_task()) + + subject.notify() + await asyncio.sleep(0.0) + + subject.notify() + await asyncio.sleep(0.5) + + assert results == ["TEST", "TEST"] + + task.cancel()