Track tasks only during shutdown and tests (#4428)
* Track tasks only when needed * Tweak async_block_till_donepull/4546/merge
parent
42c99b0ccb
commit
eacdce9ed9
|
@ -113,7 +113,6 @@ class HomeAssistant(object):
|
||||||
self.loop.set_default_executor(self.executor)
|
self.loop.set_default_executor(self.executor)
|
||||||
self.loop.set_exception_handler(self._async_exception_handler)
|
self.loop.set_exception_handler(self._async_exception_handler)
|
||||||
self._pending_tasks = []
|
self._pending_tasks = []
|
||||||
self._pending_sheduler = None
|
|
||||||
self.bus = EventBus(self)
|
self.bus = EventBus(self)
|
||||||
self.services = ServiceRegistry(self)
|
self.services = ServiceRegistry(self)
|
||||||
self.states = StateMachine(self.bus, self.loop)
|
self.states = StateMachine(self.bus, self.loop)
|
||||||
|
@ -185,24 +184,10 @@ class HomeAssistant(object):
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
self.loop._thread_ident = threading.get_ident()
|
self.loop._thread_ident = threading.get_ident()
|
||||||
self._async_tasks_cleanup()
|
|
||||||
_async_create_timer(self)
|
_async_create_timer(self)
|
||||||
self.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
self.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||||
self.state = CoreState.running
|
self.state = CoreState.running
|
||||||
|
|
||||||
@callback
|
|
||||||
def _async_tasks_cleanup(self):
|
|
||||||
"""Cleanup all pending tasks in a time interval.
|
|
||||||
|
|
||||||
This method must be run in the event loop.
|
|
||||||
"""
|
|
||||||
self._pending_tasks = [task for task in self._pending_tasks
|
|
||||||
if not task.done()]
|
|
||||||
|
|
||||||
# sheduled next cleanup
|
|
||||||
self._pending_sheduler = self.loop.call_later(
|
|
||||||
TIME_INTERVAL_TASKS_CLEANUP, self._async_tasks_cleanup)
|
|
||||||
|
|
||||||
def add_job(self, target: Callable[..., None], *args: Any) -> None:
|
def add_job(self, target: Callable[..., None], *args: Any) -> None:
|
||||||
"""Add job to the executor pool.
|
"""Add job to the executor pool.
|
||||||
|
|
||||||
|
@ -212,7 +197,28 @@ class HomeAssistant(object):
|
||||||
self.loop.call_soon_threadsafe(self.async_add_job, target, *args)
|
self.loop.call_soon_threadsafe(self.async_add_job, target, *args)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_add_job(self, target: Callable[..., None], *args: Any) -> None:
|
def _async_add_job(self, target: Callable[..., None], *args: Any) -> None:
|
||||||
|
"""Add a job from within the eventloop.
|
||||||
|
|
||||||
|
This method must be run in the event loop.
|
||||||
|
|
||||||
|
target: target to call.
|
||||||
|
args: parameters for method to call.
|
||||||
|
"""
|
||||||
|
if asyncio.iscoroutine(target):
|
||||||
|
self.loop.create_task(target)
|
||||||
|
elif is_callback(target):
|
||||||
|
self.loop.call_soon(target, *args)
|
||||||
|
elif asyncio.iscoroutinefunction(target):
|
||||||
|
self.loop.create_task(target(*args))
|
||||||
|
else:
|
||||||
|
self.loop.run_in_executor(None, target, *args)
|
||||||
|
|
||||||
|
async_add_job = _async_add_job
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_add_job_tracking(self, target: Callable[..., None],
|
||||||
|
*args: Any) -> None:
|
||||||
"""Add a job from within the eventloop.
|
"""Add a job from within the eventloop.
|
||||||
|
|
||||||
This method must be run in the event loop.
|
This method must be run in the event loop.
|
||||||
|
@ -235,6 +241,11 @@ class HomeAssistant(object):
|
||||||
if task is not None:
|
if task is not None:
|
||||||
self._pending_tasks.append(task)
|
self._pending_tasks.append(task)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_track_tasks(self):
|
||||||
|
"""Track tasks so you can wait for all tasks to be done."""
|
||||||
|
self.async_add_job = self._async_add_job_tracking
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_run_job(self, target: Callable[..., None], *args: Any) -> None:
|
def async_run_job(self, target: Callable[..., None], *args: Any) -> None:
|
||||||
"""Run a job from within the event loop.
|
"""Run a job from within the event loop.
|
||||||
|
@ -249,16 +260,6 @@ class HomeAssistant(object):
|
||||||
else:
|
else:
|
||||||
self.async_add_job(target, *args)
|
self.async_add_job(target, *args)
|
||||||
|
|
||||||
def _loop_empty(self) -> bool:
|
|
||||||
"""Python 3.4.2 empty loop compatibility function."""
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
if sys.version_info < (3, 4, 3):
|
|
||||||
return len(self.loop._scheduled) == 0 and \
|
|
||||||
len(self.loop._ready) == 0
|
|
||||||
else:
|
|
||||||
return self.loop._current_handle is None and \
|
|
||||||
len(self.loop._ready) == 0
|
|
||||||
|
|
||||||
def block_till_done(self) -> None:
|
def block_till_done(self) -> None:
|
||||||
"""Block till all pending work is done."""
|
"""Block till all pending work is done."""
|
||||||
run_coroutine_threadsafe(
|
run_coroutine_threadsafe(
|
||||||
|
@ -267,18 +268,17 @@ class HomeAssistant(object):
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_block_till_done(self):
|
def async_block_till_done(self):
|
||||||
"""Block till all pending work is done."""
|
"""Block till all pending work is done."""
|
||||||
while True:
|
# To flush out any call_soon_threadsafe
|
||||||
# Wait for the pending tasks are down
|
yield from asyncio.sleep(0, loop=self.loop)
|
||||||
|
|
||||||
|
while self._pending_tasks:
|
||||||
pending = [task for task in self._pending_tasks
|
pending = [task for task in self._pending_tasks
|
||||||
if not task.done()]
|
if not task.done()]
|
||||||
self._pending_tasks.clear()
|
self._pending_tasks.clear()
|
||||||
if len(pending) > 0:
|
if len(pending) > 0:
|
||||||
yield from asyncio.wait(pending, loop=self.loop)
|
yield from asyncio.wait(pending, loop=self.loop)
|
||||||
|
else:
|
||||||
# Verify the loop is empty
|
yield from asyncio.sleep(0, loop=self.loop)
|
||||||
ret = yield from self.loop.run_in_executor(None, self._loop_empty)
|
|
||||||
if ret and not self._pending_tasks:
|
|
||||||
break
|
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""Stop Home Assistant and shuts down all threads."""
|
"""Stop Home Assistant and shuts down all threads."""
|
||||||
|
@ -291,9 +291,8 @@ class HomeAssistant(object):
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
"""
|
"""
|
||||||
self.state = CoreState.stopping
|
self.state = CoreState.stopping
|
||||||
|
self.async_track_tasks()
|
||||||
self.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
self.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||||
if self._pending_sheduler is not None:
|
|
||||||
self._pending_sheduler.cancel()
|
|
||||||
yield from self.async_block_till_done()
|
yield from self.async_block_till_done()
|
||||||
self.executor.shutdown()
|
self.executor.shutdown()
|
||||||
if self._websession is not None:
|
if self._websession is not None:
|
||||||
|
|
|
@ -82,6 +82,7 @@ def async_test_home_assistant(loop):
|
||||||
loop._thread_ident = threading.get_ident()
|
loop._thread_ident = threading.get_ident()
|
||||||
|
|
||||||
hass = ha.HomeAssistant(loop)
|
hass = ha.HomeAssistant(loop)
|
||||||
|
hass.async_track_tasks()
|
||||||
|
|
||||||
hass.config.location_name = 'test home'
|
hass.config.location_name = 'test home'
|
||||||
hass.config.config_dir = get_test_config_dir()
|
hass.config.config_dir = get_test_config_dir()
|
||||||
|
@ -103,9 +104,8 @@ def async_test_home_assistant(loop):
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def mock_async_start():
|
def mock_async_start():
|
||||||
"""Start the mocking."""
|
"""Start the mocking."""
|
||||||
with patch.object(loop, 'add_signal_handler'),\
|
with patch.object(loop, 'add_signal_handler'), \
|
||||||
patch('homeassistant.core._async_create_timer'),\
|
patch('homeassistant.core._async_create_timer'):
|
||||||
patch.object(hass, '_async_tasks_cleanup', return_value=None):
|
|
||||||
yield from orig_start()
|
yield from orig_start()
|
||||||
|
|
||||||
hass.async_start = mock_async_start
|
hass.async_start = mock_async_start
|
||||||
|
|
|
@ -9,8 +9,7 @@ import pytz
|
||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
from homeassistant.exceptions import InvalidEntityFormatError
|
from homeassistant.exceptions import InvalidEntityFormatError
|
||||||
from homeassistant.util.async import (
|
from homeassistant.util.async import run_coroutine_threadsafe
|
||||||
run_callback_threadsafe, run_coroutine_threadsafe)
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.util.unit_system import (METRIC_SYSTEM)
|
from homeassistant.util.unit_system import (METRIC_SYSTEM)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -129,7 +128,7 @@ class TestHomeAssistant(unittest.TestCase):
|
||||||
"""Test Coro."""
|
"""Test Coro."""
|
||||||
call_count.append('call')
|
call_count.append('call')
|
||||||
|
|
||||||
for i in range(50):
|
for i in range(3):
|
||||||
self.hass.add_job(test_coro())
|
self.hass.add_job(test_coro())
|
||||||
|
|
||||||
run_coroutine_threadsafe(
|
run_coroutine_threadsafe(
|
||||||
|
@ -137,13 +136,8 @@ class TestHomeAssistant(unittest.TestCase):
|
||||||
loop=self.hass.loop
|
loop=self.hass.loop
|
||||||
).result()
|
).result()
|
||||||
|
|
||||||
with patch.object(self.hass.loop, 'call_later') as mock_later:
|
assert len(self.hass._pending_tasks) == 3
|
||||||
run_callback_threadsafe(
|
assert len(call_count) == 3
|
||||||
self.hass.loop, self.hass._async_tasks_cleanup).result()
|
|
||||||
assert mock_later.called
|
|
||||||
|
|
||||||
assert len(self.hass._pending_tasks) == 0
|
|
||||||
assert len(call_count) == 50
|
|
||||||
|
|
||||||
def test_async_add_job_pending_tasks_coro(self):
|
def test_async_add_job_pending_tasks_coro(self):
|
||||||
"""Add a coro to pending tasks."""
|
"""Add a coro to pending tasks."""
|
||||||
|
|
|
@ -61,6 +61,7 @@ def setUpModule():
|
||||||
target=loop.run_forever).start()
|
target=loop.run_forever).start()
|
||||||
|
|
||||||
slave = remote.HomeAssistant(master_api, loop=loop)
|
slave = remote.HomeAssistant(master_api, loop=loop)
|
||||||
|
slave.async_track_tasks()
|
||||||
slave.config.config_dir = get_test_config_dir()
|
slave.config.config_dir = get_test_config_dir()
|
||||||
slave.config.skip_pip = True
|
slave.config.skip_pip = True
|
||||||
bootstrap.setup_component(
|
bootstrap.setup_component(
|
||||||
|
|
Loading…
Reference in New Issue