diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index fbd6d9b0806..0e301f2a87c 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -7,6 +7,7 @@ to query this database. For more details about this component, please refer to the documentation at https://home-assistant.io/components/recorder/ """ +import asyncio import logging import queue import threading @@ -20,7 +21,7 @@ import voluptuous as vol from homeassistant.core import HomeAssistant, callback, split_entity_id from homeassistant.const import ( ATTR_ENTITY_ID, CONF_ENTITIES, CONF_EXCLUDE, CONF_DOMAINS, - CONF_INCLUDE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + CONF_INCLUDE, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv @@ -83,7 +84,18 @@ def session_scope(): session.close() -def get_instance() -> None: +@asyncio.coroutine +def async_get_instance(): + """Throw error if recorder not initialized.""" + if _INSTANCE is None: + raise RuntimeError("Recorder not initialized.") + + yield from _INSTANCE.async_db_ready.wait() + + return _INSTANCE + + +def get_instance(): """Throw error if recorder not initialized.""" if _INSTANCE is None: raise RuntimeError("Recorder not initialized.") @@ -200,7 +212,7 @@ class Recorder(threading.Thread): self.recording_start = dt_util.utcnow() self.db_url = uri self.db_ready = threading.Event() - self.start_recording = threading.Event() + self.async_db_ready = asyncio.Event(loop=hass.loop) self.engine = None # type: Any self._run = None # type: Any @@ -209,11 +221,6 @@ class Recorder(threading.Thread): self.exclude = exclude.get(CONF_ENTITIES, []) + \ exclude.get(CONF_DOMAINS, []) - def start_recording(event): - """Start recording.""" - self.start_recording.set() - - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_recording) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.shutdown) hass.bus.listen(MATCH_ALL, self.event_listener) @@ -229,6 +236,7 @@ class Recorder(threading.Thread): self._setup_connection() self._setup_run() self.db_ready.set() + self.async_db_ready.set() break except SQLAlchemyError as err: _LOGGER.error("Error during connection setup: %s (retrying " @@ -239,8 +247,6 @@ class Recorder(threading.Thread): async_track_time_interval( self.hass, self._purge_old_data, timedelta(days=2)) - _wait(self.start_recording, "Waiting to start recording", 90) - while True: event = self.queue.get() @@ -297,9 +303,6 @@ class Recorder(threading.Thread): """Tell the recorder to shut down.""" global _INSTANCE # pylint: disable=global-statement self.queue.put(None) - if not self.start_recording.is_set(): - _LOGGER.warning("Recorder never started correctly") - self.start_recording.set() self.join() _INSTANCE = None @@ -499,13 +502,13 @@ class Recorder(threading.Thread): return False -def _wait(event, message, interval=15): +def _wait(event, message): """Event wait helper.""" - for mult in range(1, 4): - event.wait(interval) + for retry in (10, 20, 30): + event.wait(10) if event.is_set(): return - msg = "{} ({} seconds)".format(message, interval*mult) + msg = "{} ({} seconds)".format(message, retry) _LOGGER.warning(msg) if not event.is_set(): raise HomeAssistantError(msg) diff --git a/homeassistant/helpers/restore_state.py b/homeassistant/helpers/restore_state.py index 1e463d316d4..86cd3e7037f 100644 --- a/homeassistant/helpers/restore_state.py +++ b/homeassistant/helpers/restore_state.py @@ -6,7 +6,8 @@ from datetime import timedelta from homeassistant.core import HomeAssistant, CoreState, callback from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.components.history import get_states, last_recorder_run -from homeassistant.components.recorder import DOMAIN as _RECORDER +from homeassistant.components.recorder import ( + async_get_instance, DOMAIN as _RECORDER) import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -57,6 +58,8 @@ def async_get_last_state(hass, entity_id: str): hass.state) return None + yield from async_get_instance() # Ensure recorder ready + if _LOCK not in hass.data: hass.data[_LOCK] = asyncio.Lock(loop=hass.loop) diff --git a/tests/helpers/test_restore_state.py b/tests/helpers/test_restore_state.py index d411ef2073a..3a4c058f853 100644 --- a/tests/helpers/test_restore_state.py +++ b/tests/helpers/test_restore_state.py @@ -11,7 +11,8 @@ from homeassistant.components import input_boolean, recorder from homeassistant.helpers.restore_state import ( async_get_last_state, DATA_RESTORE_CACHE) -from tests.common import get_test_home_assistant, init_recorder_component +from tests.common import ( + get_test_home_assistant, mock_coro, init_recorder_component) @asyncio.coroutine @@ -29,7 +30,9 @@ def test_caching_data(hass): with patch('homeassistant.helpers.restore_state.last_recorder_run', return_value=MagicMock(end=dt_util.utcnow())), \ patch('homeassistant.helpers.restore_state.get_states', - return_value=states): + return_value=states), \ + patch('homeassistant.helpers.restore_state.async_get_instance', + return_value=mock_coro()): state = yield from async_get_last_state(hass, 'input_boolean.b1') assert DATA_RESTORE_CACHE in hass.data