Fix deadlock at shutdown with python 3.9 (#52613)

pull/52627/head
J. Nick Koston 2021-07-07 02:23:24 -05:00 committed by Franck Nijhof
parent a7ee86730c
commit a794c09a0f
No known key found for this signature in database
GPG Key ID: D62583BA8AB11CA3
3 changed files with 7 additions and 17 deletions

View File

@ -73,16 +73,6 @@ class HassEventLoopPolicy(asyncio.DefaultEventLoopPolicy): # type: ignore[valid
loop.set_default_executor = warn_use( # type: ignore loop.set_default_executor = warn_use( # type: ignore
loop.set_default_executor, "sets default executor on the event loop" loop.set_default_executor, "sets default executor on the event loop"
) )
# Shut down executor when we shut down loop
orig_close = loop.close
def close() -> None:
executor.logged_shutdown()
orig_close()
loop.close = close # type: ignore
return loop return loop

View File

@ -62,7 +62,7 @@ def join_or_interrupt_threads(
class InterruptibleThreadPoolExecutor(ThreadPoolExecutor): class InterruptibleThreadPoolExecutor(ThreadPoolExecutor):
"""A ThreadPoolExecutor instance that will not deadlock on shutdown.""" """A ThreadPoolExecutor instance that will not deadlock on shutdown."""
def logged_shutdown(self) -> None: def shutdown(self, *args, **kwargs) -> None: # type: ignore
"""Shutdown backport from cpython 3.9 with interrupt support added.""" """Shutdown backport from cpython 3.9 with interrupt support added."""
with self._shutdown_lock: # type: ignore[attr-defined] with self._shutdown_lock: # type: ignore[attr-defined]
self._shutdown = True self._shutdown = True

View File

@ -24,7 +24,7 @@ async def test_executor_shutdown_can_interrupt_threads(caplog):
for _ in range(100): for _ in range(100):
sleep_futures.append(iexecutor.submit(_loop_sleep_in_executor)) sleep_futures.append(iexecutor.submit(_loop_sleep_in_executor))
iexecutor.logged_shutdown() iexecutor.shutdown()
for future in sleep_futures: for future in sleep_futures:
with pytest.raises((concurrent.futures.CancelledError, SystemExit)): with pytest.raises((concurrent.futures.CancelledError, SystemExit)):
@ -45,13 +45,13 @@ async def test_executor_shutdown_only_logs_max_attempts(caplog):
iexecutor.submit(_loop_sleep_in_executor) iexecutor.submit(_loop_sleep_in_executor)
with patch.object(executor, "EXECUTOR_SHUTDOWN_TIMEOUT", 0.3): with patch.object(executor, "EXECUTOR_SHUTDOWN_TIMEOUT", 0.3):
iexecutor.logged_shutdown() iexecutor.shutdown()
assert "time.sleep(0.2)" in caplog.text assert "time.sleep(0.2)" in caplog.text
assert ( assert (
caplog.text.count("is still running at shutdown") == executor.MAX_LOG_ATTEMPTS caplog.text.count("is still running at shutdown") == executor.MAX_LOG_ATTEMPTS
) )
iexecutor.logged_shutdown() iexecutor.shutdown()
async def test_executor_shutdown_does_not_log_shutdown_on_first_attempt(caplog): async def test_executor_shutdown_does_not_log_shutdown_on_first_attempt(caplog):
@ -65,7 +65,7 @@ async def test_executor_shutdown_does_not_log_shutdown_on_first_attempt(caplog):
for _ in range(5): for _ in range(5):
iexecutor.submit(_do_nothing) iexecutor.submit(_do_nothing)
iexecutor.logged_shutdown() iexecutor.shutdown()
assert "is still running at shutdown" not in caplog.text assert "is still running at shutdown" not in caplog.text
@ -83,9 +83,9 @@ async def test_overall_timeout_reached(caplog):
start = time.monotonic() start = time.monotonic()
with patch.object(executor, "EXECUTOR_SHUTDOWN_TIMEOUT", 0.5): with patch.object(executor, "EXECUTOR_SHUTDOWN_TIMEOUT", 0.5):
iexecutor.logged_shutdown() iexecutor.shutdown()
finish = time.monotonic() finish = time.monotonic()
assert finish - start < 1 assert finish - start < 1
iexecutor.logged_shutdown() iexecutor.shutdown()