-
-
Notifications
You must be signed in to change notification settings - Fork 31.4k
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
Fix automations listening to HOMEASSISTANT_START #6936
Changes from all commits
81eebe4
99a6b7a
3fe28bc
94ef182
604ceb0
48e2750
deee087
3b46e44
22ec6df
bbe33a3
aeda4fe
650dfa6
d758871
293d575
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 |
---|---|---|
@@ -0,0 +1,55 @@ | ||
""" | ||
Offer Home Assistant core automation rules. | ||
|
||
For more details about this automation rule, please refer to the documentation | ||
at https://home-assistant.io/components/automation/#homeassistant-trigger | ||
""" | ||
import asyncio | ||
import logging | ||
|
||
import voluptuous as vol | ||
|
||
from homeassistant.core import callback, CoreState | ||
from homeassistant.const import ( | ||
CONF_PLATFORM, CONF_EVENT, EVENT_HOMEASSISTANT_STOP) | ||
|
||
EVENT_START = 'start' | ||
EVENT_SHUTDOWN = 'shutdown' | ||
_LOGGER = logging.getLogger(__name__) | ||
|
||
TRIGGER_SCHEMA = vol.Schema({ | ||
vol.Required(CONF_PLATFORM): 'homeassistant', | ||
vol.Required(CONF_EVENT): vol.Any(EVENT_START, EVENT_SHUTDOWN), | ||
}) | ||
|
||
|
||
@asyncio.coroutine | ||
def async_trigger(hass, config, action): | ||
"""Listen for events based on configuration.""" | ||
event = config.get(CONF_EVENT) | ||
|
||
if event == EVENT_SHUTDOWN: | ||
@callback | ||
def hass_shutdown(event): | ||
"""Called when Home Assistant is shutting down.""" | ||
hass.async_run_job(action, { | ||
'trigger': { | ||
'platform': 'homeassistant', | ||
'event': event, | ||
}, | ||
}) | ||
|
||
return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, | ||
hass_shutdown) | ||
|
||
# Automation are enabled while hass is starting up, fire right away | ||
# Check state because a config reload shouldn't trigger it. | ||
elif hass.state == CoreState.starting: | ||
hass.async_run_job(action, { | ||
'trigger': { | ||
'platform': 'homeassistant', | ||
'event': event, | ||
}, | ||
}) | ||
|
||
return lambda: None |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,20 +29,14 @@ | |
EVENT_TIME_CHANGED, MATCH_ALL, EVENT_HOMEASSISTANT_CLOSE, | ||
EVENT_SERVICE_REMOVED, __version__) | ||
from homeassistant.exceptions import ( | ||
HomeAssistantError, InvalidEntityFormatError, ShuttingDown) | ||
HomeAssistantError, InvalidEntityFormatError) | ||
from homeassistant.util.async import ( | ||
run_coroutine_threadsafe, run_callback_threadsafe) | ||
import homeassistant.util as util | ||
import homeassistant.util.dt as dt_util | ||
import homeassistant.util.location as location | ||
from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA | ||
|
||
try: | ||
import uvloop | ||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) | ||
except ImportError: | ||
pass | ||
|
||
DOMAIN = 'homeassistant' | ||
|
||
# How long we wait for the result of a service call | ||
|
@@ -86,10 +80,6 @@ def async_loop_exception_handler(loop, context): | |
kwargs = {} | ||
exception = context.get('exception') | ||
if exception: | ||
# Do not report on shutting down exceptions. | ||
if isinstance(exception, ShuttingDown): | ||
return | ||
|
||
kwargs['exc_info'] = (type(exception), exception, | ||
exception.__traceback__) | ||
|
||
|
@@ -123,7 +113,7 @@ def __init__(self, loop=None): | |
self.loop.set_default_executor(self.executor) | ||
self.loop.set_exception_handler(async_loop_exception_handler) | ||
self._pending_tasks = [] | ||
self._track_task = False | ||
self._track_task = True | ||
self.bus = EventBus(self) | ||
self.services = ServiceRegistry(self) | ||
self.states = StateMachine(self.bus, self.loop) | ||
|
@@ -148,6 +138,7 @@ def start(self) -> None: | |
# Block until stopped | ||
_LOGGER.info("Starting Home Assistant core loop") | ||
self.loop.run_forever() | ||
return self.exit_code | ||
except KeyboardInterrupt: | ||
self.loop.create_task(self.async_stop()) | ||
self.loop.run_forever() | ||
|
@@ -165,9 +156,10 @@ def async_start(self): | |
|
||
# pylint: disable=protected-access | ||
self.loop._thread_ident = threading.get_ident() | ||
_async_create_timer(self) | ||
self.bus.async_fire(EVENT_HOMEASSISTANT_START) | ||
yield from self.async_stop_track_tasks() | ||
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. So this fixes the fact that However, this has the downside that it is impossible for components/platforms to start a permanent async job as a result from EVENT_HOMEASSISTANT_START. (note that things like MQTT server rely on 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. Maybe add a timeout to 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. I think that should not be needed to add a timeout |
||
self.state = CoreState.running | ||
_async_create_timer(self) | ||
|
||
def add_job(self, target: Callable[..., None], *args: Any) -> None: | ||
"""Add job to the executor pool. | ||
|
@@ -238,6 +230,8 @@ def block_till_done(self) -> None: | |
@asyncio.coroutine | ||
def async_block_till_done(self): | ||
"""Block till all pending work is done.""" | ||
assert self._track_task, 'Not tracking tasks' | ||
|
||
# To flush out any call_soon_threadsafe | ||
yield from asyncio.sleep(0, loop=self.loop) | ||
|
||
|
@@ -252,7 +246,8 @@ def async_block_till_done(self): | |
|
||
def stop(self) -> None: | ||
"""Stop Home Assistant and shuts down all threads.""" | ||
run_coroutine_threadsafe(self.async_stop(), self.loop) | ||
self.loop.call_soon_threadsafe( | ||
self.loop.create_task, self.async_stop()) | ||
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 not only call 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 that's not threadsafe 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. You are sure? I found no comments in doc that will be not threadsafe. 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. https://docs.python.org/3/library/asyncio-dev.html#concurrency-and-multithreading
Also, the tests won't pass if I just call create_task. 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. (note, we include the 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.
|
||
|
||
@asyncio.coroutine | ||
def async_stop(self, exit_code=0) -> None: | ||
|
@@ -368,10 +363,6 @@ def async_fire(self, event_type: str, event_data=None, | |
|
||
This method must be run in the event loop. | ||
""" | ||
if event_type != EVENT_HOMEASSISTANT_STOP and \ | ||
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. This was silently (because exception suppressed) killing shutdown tasks (noticed it when adding shutdown event in automation). It should be fine to just wait till all is done. Timer is already stopped so nothing new should come in. |
||
self._hass.state == CoreState.stopping: | ||
raise ShuttingDown("Home Assistant is shutting down") | ||
|
||
listeners = self._listeners.get(event_type, []) | ||
|
||
# EVENT_HOMEASSISTANT_CLOSE should go only to his listeners | ||
|
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.
Track tasks is now enabled by default. It's what we want during startup.