Fix race when restarting script (#49247)
This commit is contained in:
parent
564e7fa53c
commit
2c8b7c56f5
2 changed files with 23 additions and 14 deletions
|
@ -1144,10 +1144,7 @@ class Script:
|
||||||
self._log("Already running", level=LOGSEVERITY[self._max_exceeded])
|
self._log("Already running", level=LOGSEVERITY[self._max_exceeded])
|
||||||
script_execution_set("failed_single")
|
script_execution_set("failed_single")
|
||||||
return
|
return
|
||||||
if self.script_mode == SCRIPT_MODE_RESTART:
|
if self.script_mode != SCRIPT_MODE_RESTART and self.runs == self.max_runs:
|
||||||
self._log("Restarting")
|
|
||||||
await self.async_stop(update_state=False)
|
|
||||||
elif len(self._runs) == self.max_runs:
|
|
||||||
if self._max_exceeded != "SILENT":
|
if self._max_exceeded != "SILENT":
|
||||||
self._log(
|
self._log(
|
||||||
"Maximum number of runs exceeded",
|
"Maximum number of runs exceeded",
|
||||||
|
@ -1186,6 +1183,14 @@ class Script:
|
||||||
self._hass, self, cast(dict, variables), context, self._log_exceptions
|
self._hass, self, cast(dict, variables), context, self._log_exceptions
|
||||||
)
|
)
|
||||||
self._runs.append(run)
|
self._runs.append(run)
|
||||||
|
if self.script_mode == SCRIPT_MODE_RESTART:
|
||||||
|
# When script mode is SCRIPT_MODE_RESTART, first add the new run and then
|
||||||
|
# stop any other runs. If we stop other runs first, self.is_running will
|
||||||
|
# return false after the other script runs were stopped until our task
|
||||||
|
# resumes running.
|
||||||
|
self._log("Restarting")
|
||||||
|
await self.async_stop(update_state=False, spare=run)
|
||||||
|
|
||||||
if started_action:
|
if started_action:
|
||||||
self._hass.async_run_job(started_action)
|
self._hass.async_run_job(started_action)
|
||||||
self.last_triggered = utcnow()
|
self.last_triggered = utcnow()
|
||||||
|
@ -1198,17 +1203,21 @@ class Script:
|
||||||
self._changed()
|
self._changed()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def _async_stop(self, update_state):
|
async def _async_stop(self, update_state, spare=None):
|
||||||
aws = [asyncio.create_task(run.async_stop()) for run in self._runs]
|
aws = [
|
||||||
|
asyncio.create_task(run.async_stop()) for run in self._runs if run != spare
|
||||||
|
]
|
||||||
if not aws:
|
if not aws:
|
||||||
return
|
return
|
||||||
await asyncio.wait(aws)
|
await asyncio.wait(aws)
|
||||||
if update_state:
|
if update_state:
|
||||||
self._changed()
|
self._changed()
|
||||||
|
|
||||||
async def async_stop(self, update_state: bool = True) -> None:
|
async def async_stop(
|
||||||
|
self, update_state: bool = True, spare: _ScriptRun | None = None
|
||||||
|
) -> None:
|
||||||
"""Stop running script."""
|
"""Stop running script."""
|
||||||
await asyncio.shield(self._async_stop(update_state))
|
await asyncio.shield(self._async_stop(update_state, spare))
|
||||||
|
|
||||||
async def _async_get_condition(self, config):
|
async def _async_get_condition(self, config):
|
||||||
if isinstance(config, template.Template):
|
if isinstance(config, template.Template):
|
||||||
|
|
|
@ -156,8 +156,8 @@ async def test_motion_light(hass):
|
||||||
# Turn on motion
|
# Turn on motion
|
||||||
hass.states.async_set("binary_sensor.kitchen", "on")
|
hass.states.async_set("binary_sensor.kitchen", "on")
|
||||||
# Can't block till done because delay is active
|
# Can't block till done because delay is active
|
||||||
# So wait 5 event loop iterations to process script
|
# So wait 10 event loop iterations to process script
|
||||||
for _ in range(5):
|
for _ in range(10):
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
assert len(turn_on_calls) == 1
|
assert len(turn_on_calls) == 1
|
||||||
|
@ -165,7 +165,7 @@ async def test_motion_light(hass):
|
||||||
# Test light doesn't turn off if motion stays
|
# Test light doesn't turn off if motion stays
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=200))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=200))
|
||||||
|
|
||||||
for _ in range(5):
|
for _ in range(10):
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
assert len(turn_off_calls) == 0
|
assert len(turn_off_calls) == 0
|
||||||
|
@ -173,7 +173,7 @@ async def test_motion_light(hass):
|
||||||
# Test light turns off off 120s after last motion
|
# Test light turns off off 120s after last motion
|
||||||
hass.states.async_set("binary_sensor.kitchen", "off")
|
hass.states.async_set("binary_sensor.kitchen", "off")
|
||||||
|
|
||||||
for _ in range(5):
|
for _ in range(10):
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=120))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=120))
|
||||||
|
@ -184,7 +184,7 @@ async def test_motion_light(hass):
|
||||||
# Test restarting the script
|
# Test restarting the script
|
||||||
hass.states.async_set("binary_sensor.kitchen", "on")
|
hass.states.async_set("binary_sensor.kitchen", "on")
|
||||||
|
|
||||||
for _ in range(5):
|
for _ in range(10):
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
assert len(turn_on_calls) == 2
|
assert len(turn_on_calls) == 2
|
||||||
|
@ -192,7 +192,7 @@ async def test_motion_light(hass):
|
||||||
|
|
||||||
hass.states.async_set("binary_sensor.kitchen", "off")
|
hass.states.async_set("binary_sensor.kitchen", "off")
|
||||||
|
|
||||||
for _ in range(5):
|
for _ in range(10):
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
hass.states.async_set("binary_sensor.kitchen", "on")
|
hass.states.async_set("binary_sensor.kitchen", "on")
|
||||||
|
|
Loading…
Add table
Reference in a new issue