Skip to content
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

Merged
merged 15 commits into from Sep 19, 2018
Merged

Conversation

smurfix
Copy link
Contributor

@smurfix smurfix commented Sep 14, 2018

Description:

This patch changes Home Assistant's startup routine so that it will run under asyncio.run. It also changes the SIGINT handler to start the shutdown sequence.

Advantages:

  • This allows people to embed Home Assistant in larger async programs.
  • SIGINT aka ^C no longer disrupts a random part of Home Assistant's code.

Disadvantages:

  • If Home Assistant's main thread is wedged (which basically should not happen), you may have to press ^C twice to get out.

Checklist:

  • The code change is tested and works locally.
  • Local tests pass with tox. Your PR cannot be merged unless tests pass

@@ -17,7 +17,9 @@ 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 shutdown code, re-instate default handler."""

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long (88 > 79 characters)


await self.async_start()
if attach_signals:
from homeassistant.helpers.signal import async_register_signal_handling

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long (83 > 79 characters)

* Use asyncio.run (or our own implementation on Python <3.7)
* hass.start is only used by tests
* setup_and_run_hass() is now async
* Add "main" async hass.run method
* move SIGINT handling to helpers/signal.py
  * add flag to .run to disable hass's signal handlers
* Teach async_start and async_stop to not step on each other
  (more than necessary)
@@ -21,8 +21,6 @@

def attempt_use_uvloop() -> None:
"""Attempt to use uvloop."""
import asyncio
Copy link
Member

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?

Copy link
Contributor Author

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.

if self.state == CoreState.not_running: # just ignore
return
if self.state == CoreState.stopping:
_LOGGER.info("async_stop called twice: ignored")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a warning?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a better idea. Will change.

@@ -30,6 +30,15 @@
}


try:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this to util.async_

* queue shutdown code
* re-instate default handler
"""
hass.loop.remove_signal_handler(signal.SIGTERM)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we do this ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check

asyncio_run = asyncio.run
except AttributeError:
def asyncio_run(main, *, debug=False):
loop = asyncio.new_event_loop()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined name 'asyncio'

@@ -12,6 +12,15 @@
_LOGGER = logging.getLogger(__name__)


try:
asyncio_run = asyncio.run

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined name 'asyncio'

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:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long (83 > 79 characters)

only useful for testing
Required for keeping requirements_test_all.txt in sync, where it is in
turn required to prevent auto-downgrading "attrs" during "pip install"
asyncio_run = asyncio.run # type: ignore
except AttributeError:
_T = TypeVar('_T')
def asyncio_run(main: Awaitable[_T], *, debug: bool = False) -> _T:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expected 1 blank line, found 0

@smurfix
Copy link
Contributor Author

smurfix commented Sep 15, 2018

Current tests break with undefined "mock_signal" fixture.
I have no idea where that is supposed to come from …

@smurfix
Copy link
Contributor Author

smurfix commented Sep 15, 2018

Meh. Finally. (Had a broken local installation.)
Ready to merge IMHO.

finally:
self.loop.close()
return self.exit_code

async def run(self, *, attach_signals: bool = True) -> int:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is an async method, we should call it async_run to be in line with the rest of our functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My rationale for not naming it async_run is that it's never called from within Home Assistant, but I'll change it if you want me to, no problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah let's change it.

@@ -2,6 +2,7 @@
# make new things fail. Manually update these pins when pulling in a
# new version
asynctest==0.12.2
attrs==18.2.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be necessary, it's already part of the core dependencies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem: when I do a pip install -r requirements_test_all.txt without this entry, attrs is downgraded to some lower version, probably because of some sub-module's requirements.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the constraints file like Tox does: pip install -r requirements_test_all.txt -c homeassistant/package_constraints.txt

Copy link
Member

@balloob balloob left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok to merge after run renamed to async_run

@smurfix
Copy link
Contributor Author

smurfix commented Sep 17, 2018

All done, please merge.

@balloob balloob merged commit 0121e3c into home-assistant:dev Sep 19, 2018
@ghost ghost removed the in progress label Sep 19, 2018
@awarecan
Copy link
Contributor

Did it tested on Windows?

I run on Windows Python 3.6.3 got some errors

Traceback (most recent call last):
  File "c:\awarecan\home-assistant\homeassistant\__main__.py", line 376, in <module>
    sys.exit(main())
  File "c:\awarecan\home-assistant\homeassistant\__main__.py", line 368, in main
    exit_code = asyncio_run(setup_and_run_hass(config_dir, args))
  File "c:\awarecan\home-assistant\homeassistant\util\async_.py", line 29, in asyncio_run
    return loop.run_until_complete(main)
  File "C:\Python\Python36\lib\asyncio\base_events.py", line 467, in run_until_complete
    return future.result()
  File "c:\awarecan\home-assistant\homeassistant\__main__.py", line 275, in setup_and_run_hass
    log_no_color=args.log_no_color)
  File "c:\awarecan\home-assistant\homeassistant\bootstrap.py", line 215, in async_from_config_file
    conf_util.load_yaml_config_file, config_path)
RuntimeError: Task <Task pending coro=<setup_and_run_hass() running at c:\awarecan\home-assistant\homeassistant\__main__.py:275> cb=[_run_until_complete_cb() at C:\Python\Python36\lib\asyncio\base_events.py:176]> got Future <Future pending cb=[_chain_future.<locals>._call_check_cancel() at C:\Python\Python36\lib\asyncio\futures.py:408]> attached to a different loop

@balloob
Copy link
Member

balloob commented Sep 20, 2018

@balloob balloob mentioned this pull request Sep 20, 2018
@balloob
Copy link
Member

balloob commented Sep 20, 2018

This should do it: #16737

@balloob balloob mentioned this pull request Sep 28, 2018
@fabaff fabaff mentioned this pull request Dec 10, 2018
@home-assistant home-assistant locked and limited conversation to collaborators Feb 5, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants