New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove usage of "run_until_complete" #16617
Changes from 14 commits
5023ed1
79161c6
0f0d5c2
84ab2d9
1377fbd
6b56341
4caa189
80007b0
edce113
c33c2f7
efb8679
6eb1cf9
595c4c7
f0d8a58
019a0ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -154,30 +154,54 @@ def __init__( | |
self.state = CoreState.not_running | ||
self.exit_code = 0 # type: int | ||
self.config_entries = None # type: Optional[ConfigEntries] | ||
# If not None, use to signal end-of-loop | ||
self._stopped = None # type: Optional[asyncio.Event] | ||
|
||
@property | ||
def is_running(self) -> bool: | ||
"""Return if Home Assistant is running.""" | ||
return self.state in (CoreState.starting, CoreState.running) | ||
|
||
def start(self) -> int: | ||
"""Start home assistant.""" | ||
"""Start home assistant. | ||
|
||
Note: This function is only used for testing. | ||
For regular use, use "await hass.run()". | ||
""" | ||
# Register the async start | ||
fire_coroutine_threadsafe(self.async_start(), self.loop) | ||
|
||
# Run forever and catch keyboard interrupt | ||
# Run forever | ||
try: | ||
# Block until stopped | ||
_LOGGER.info("Starting Home Assistant core loop") | ||
self.loop.run_forever() | ||
except KeyboardInterrupt: | ||
self.loop.call_soon_threadsafe( | ||
self.loop.create_task, self.async_stop()) | ||
self.loop.run_forever() | ||
finally: | ||
self.loop.close() | ||
return self.exit_code | ||
|
||
async def run(self, *, attach_signals: bool = True) -> int: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is an async method, we should call it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My rationale for not naming it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah let's change it. |
||
"""Home Assistant main entry point. | ||
|
||
Start Home Assistant and block until stopped. | ||
|
||
This method is a coroutine. | ||
""" | ||
if self.state != CoreState.not_running: | ||
raise RuntimeError("HASS is already running") | ||
|
||
# _async_stop will set this instead of stopping the loop | ||
self._stopped = asyncio.Event() | ||
|
||
await self.async_start() | ||
if attach_signals: | ||
from homeassistant.helpers.signal \ | ||
import async_register_signal_handling | ||
async_register_signal_handling(self) | ||
|
||
await self._stopped.wait() | ||
return self.exit_code | ||
|
||
async def async_start(self) -> None: | ||
"""Finalize startup from inside the event loop. | ||
|
||
|
@@ -203,6 +227,13 @@ async def async_start(self) -> None: | |
|
||
# Allow automations to set up the start triggers before changing state | ||
await asyncio.sleep(0) | ||
|
||
if self.state != CoreState.starting: | ||
_LOGGER.warning( | ||
'Home Assistant startup has been interrupted. ' | ||
'Its state may be inconsistent.') | ||
return | ||
|
||
self.state = CoreState.running | ||
_async_create_timer(self) | ||
|
||
|
@@ -321,13 +352,32 @@ async def async_block_till_done(self) -> None: | |
|
||
def stop(self) -> None: | ||
"""Stop Home Assistant and shuts down all threads.""" | ||
if self.state == CoreState.not_running: # just ignore | ||
return | ||
fire_coroutine_threadsafe(self.async_stop(), self.loop) | ||
|
||
async def async_stop(self, exit_code: int = 0) -> None: | ||
async def async_stop(self, exit_code: int = 0, *, | ||
force: bool = False) -> None: | ||
"""Stop Home Assistant and shuts down all threads. | ||
|
||
The "force" flag commands async_stop to proceed regardless of | ||
Home Assistan't current state. You should not set this flag | ||
unless you're testing. | ||
|
||
This method is a coroutine. | ||
""" | ||
if not force: | ||
# Some tests require async_stop to run, | ||
# regardless of the state of the loop. | ||
if self.state == CoreState.not_running: # just ignore | ||
return | ||
if self.state == CoreState.stopping: | ||
_LOGGER.info("async_stop called twice: ignored") | ||
return | ||
if self.state == CoreState.starting: | ||
# This may not work | ||
_LOGGER.warning("async_stop called before startup is complete") | ||
|
||
# stage 1 | ||
self.state = CoreState.stopping | ||
self.async_track_tasks() | ||
|
@@ -341,7 +391,11 @@ async def async_stop(self, exit_code: int = 0) -> None: | |
self.executor.shutdown() | ||
|
||
self.exit_code = exit_code | ||
self.loop.stop() | ||
|
||
if self._stopped is not None: | ||
self._stopped.set() | ||
else: | ||
self.loop.stop() | ||
|
||
|
||
@attr.s(slots=True, frozen=True) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,13 @@ def async_register_signal_handling(hass: HomeAssistant) -> None: | |
if sys.platform != 'win32': | ||
@callback | ||
def async_signal_handle(exit_code): | ||
"""Wrap signal handling.""" | ||
"""Wrap signal handling. | ||
|
||
* queue call to shutdown task | ||
* re-instate default handler | ||
""" | ||
hass.loop.remove_signal_handler(signal.SIGTERM) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would we do this ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the signal handler has initiated an orderly shutdown. Its job is now done; if we get the signal again, the user is impatient / the main loop is stuck, and standard Python signal handling should take over. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check |
||
hass.loop.remove_signal_handler(signal.SIGINT) | ||
hass.async_create_task(hass.async_stop(exit_code)) | ||
|
||
try: | ||
|
@@ -26,6 +32,12 @@ def async_signal_handle(exit_code): | |
except ValueError: | ||
_LOGGER.warning("Could not bind to SIGTERM") | ||
|
||
try: | ||
hass.loop.add_signal_handler( | ||
signal.SIGINT, async_signal_handle, 0) | ||
except ValueError: | ||
_LOGGER.warning("Could not bind to SIGINT") | ||
|
||
try: | ||
hass.loop.add_signal_handler( | ||
signal.SIGHUP, async_signal_handle, RESTART_EXIT_CODE) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,12 +6,32 @@ | |
from asyncio.events import AbstractEventLoop | ||
from asyncio.futures import Future | ||
|
||
import asyncio | ||
from asyncio import ensure_future | ||
from typing import Any, Union, Coroutine, Callable, Generator | ||
from typing import Any, Union, Coroutine, Callable, Generator, TypeVar, \ | ||
Awaitable | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
try: | ||
# pylint: disable=invalid-name | ||
asyncio_run = asyncio.run # type: ignore | ||
except AttributeError: | ||
_T = TypeVar('_T') | ||
|
||
def asyncio_run(main: Awaitable[_T], *, debug: bool = False) -> _T: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expected 1 blank line, found 0 |
||
"""Minimal re-implementation of asyncio.run (since 3.7).""" | ||
loop = asyncio.new_event_loop() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. undefined name 'asyncio' |
||
asyncio.set_event_loop(loop) | ||
loop.set_debug(debug) | ||
try: | ||
return loop.run_until_complete(main) | ||
finally: | ||
asyncio.set_event_loop(None) # type: ignore # not a bug | ||
loop.close() | ||
|
||
|
||
def _set_result_unless_cancelled(fut: Future, result: Any) -> None: | ||
"""Set the result only if the Future was not cancelled.""" | ||
if fut.cancelled(): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now the import is missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh. Me being blind. Will fix.