Add ability to shutdown a Debouncer (#91439)

* Add ability to shutdown a Debouncer

* Use async_create_task
This commit is contained in:
epenet 2023-04-15 03:01:21 +02:00 committed by GitHub
parent 698345e88b
commit 19a6530c3c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 52 additions and 4 deletions

View file

@ -44,6 +44,7 @@ class Debouncer(Generic[_R_co]):
function, f"debouncer cooldown={cooldown}, immediate={immediate}"
)
)
self._shutdown_requested = False
@property
def function(self) -> Callable[[], _R_co] | None:
@ -62,6 +63,8 @@ class Debouncer(Generic[_R_co]):
async def async_call(self) -> None:
"""Call the function."""
if self._shutdown_requested:
raise RuntimeError("Debouncer has been shutdown.")
assert self._job is not None
if self._timer_task:
@ -115,6 +118,12 @@ class Debouncer(Generic[_R_co]):
# Schedule a new timer to prevent new runs during cooldown
self._schedule_timer()
@callback
def async_shutdown(self) -> None:
"""Cancel any scheduled call, and prevent new runs."""
self._shutdown_requested = True
self.async_cancel()
@callback
def async_cancel(self) -> None:
"""Cancel any scheduled call."""
@ -137,4 +146,7 @@ class Debouncer(Generic[_R_co]):
@callback
def _schedule_timer(self) -> None:
"""Schedule a timer."""
self._timer_task = self.hass.loop.call_later(self.cooldown, self._on_debounce)
if not self._shutdown_requested:
self._timer_task = self.hass.loop.call_later(
self.cooldown, self._on_debounce
)

View file

@ -1,20 +1,26 @@
"""Tests for debounce."""
import asyncio
from datetime import timedelta
import logging
from unittest.mock import AsyncMock
import pytest
from homeassistant.core import HomeAssistant
from homeassistant.helpers import debounce
from homeassistant.util.dt import utcnow
from ..common import async_fire_time_changed
_LOGGER = logging.getLogger(__name__)
async def test_immediate_works(hass: HomeAssistant) -> None:
"""Test immediate works."""
calls = []
debouncer = debounce.Debouncer(
hass,
None,
_LOGGER,
cooldown=0.01,
immediate=True,
function=AsyncMock(side_effect=lambda: calls.append(None)),
@ -68,7 +74,7 @@ async def test_not_immediate_works(hass: HomeAssistant) -> None:
calls = []
debouncer = debounce.Debouncer(
hass,
None,
_LOGGER,
cooldown=0.01,
immediate=False,
function=AsyncMock(side_effect=lambda: calls.append(None)),
@ -123,7 +129,7 @@ async def test_immediate_works_with_function_swapped(hass: HomeAssistant) -> Non
debouncer = debounce.Debouncer(
hass,
None,
_LOGGER,
cooldown=0.01,
immediate=True,
function=one_function,
@ -174,3 +180,33 @@ async def test_immediate_works_with_function_swapped(hass: HomeAssistant) -> Non
assert debouncer._execute_at_end_of_timer is False
debouncer._execute_lock.release()
assert debouncer._job.target == debouncer.function
async def test_shutdown(hass: HomeAssistant) -> None:
"""Test shutdown."""
calls = []
future = asyncio.Future()
async def _func() -> None:
await future
calls.append(None)
debouncer = debounce.Debouncer(
hass,
_LOGGER,
cooldown=0.01,
immediate=False,
function=_func,
)
# Ensure shutdown during a run doesn't create a cooldown timer
hass.async_create_task(debouncer.async_call())
await asyncio.sleep(0.01)
debouncer.async_shutdown()
future.set_result(True)
await hass.async_block_till_done()
assert len(calls) == 1
assert debouncer._timer_task is None
with pytest.raises(RuntimeError, match="Debouncer has been shutdown"):
await debouncer.async_call()