Fix race in script stop that could cause async_stop to hang forever (#113089)

This commit is contained in:
J. Nick Koston 2024-03-11 10:48:11 -10:00 committed by GitHub
parent eff0aac586
commit 324266a4e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 26 additions and 2 deletions

View File

@ -392,6 +392,7 @@ class _ScriptRun:
self._context = context
self._log_exceptions = log_exceptions
self._step = -1
self._started = False
self._stop = asyncio.Event()
self._stopped = asyncio.Event()
self._conversation_response: str | None | UndefinedType = UNDEFINED
@ -420,6 +421,7 @@ class _ScriptRun:
async def async_run(self) -> ScriptRunResult | None:
"""Run script."""
self._started = True
# Push the script to the script execution stack
if (script_stack := script_stack_cv.get()) is None:
script_stack = []
@ -501,7 +503,12 @@ class _ScriptRun:
async def async_stop(self) -> None:
"""Stop script run."""
self._stop.set()
await self._stopped.wait()
# If the script was never started
# the stopped event will never be
# set because the script will never
# start running
if self._started:
await self._stopped.wait()
def _handle_exception(
self, exception: Exception, continue_on_error: bool, log_exceptions: bool

View File

@ -4344,7 +4344,7 @@ async def test_script_mode_queued_cancel(hass: HomeAssistant) -> None:
await task2
assert script_obj.is_running
assert script_obj.runs == 1
assert script_obj.runs == 2
with pytest.raises(asyncio.CancelledError):
task1.cancel()
@ -5621,3 +5621,20 @@ async def test_conversation_response_not_set_subscript_if(
"1/if/condition/0": [{"result": {"result": var == 1, "entities": []}}],
}
assert_action_trace(expected_trace)
async def test_stopping_run_before_starting(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test stopping a script run before its started."""
sequence = cv.SCRIPT_SCHEMA(
[
{"wait_template": "{{ 'on' == 'off' }}"},
]
)
script_obj = script.Script(hass, sequence, "Test Name", "test_domain")
# Tested directly because we are checking for a race in the internals
# where the script is stopped before it is started. Previously this
# would hang indefinitely.
run = script._ScriptRun(hass, script_obj, {}, None, True)
await run.async_stop()