Speedup core controller startup
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 23 Feb 2026 00:23:04 +0000 (01:23 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Mon, 23 Feb 2026 00:23:04 +0000 (01:23 +0100)
music_assistant/controllers/metadata.py
music_assistant/mass.py
music_assistant/models/core_controller.py

index ac6d00656e30c8e1362543b1fec8baf02e0d6be3..39295885309afe4167862cc17f854f26b27b638c 100644 (file)
@@ -174,6 +174,10 @@ class MetaDataController(CoreController):
 
     async def setup(self, config: CoreConfig) -> None:
         """Async initialize of module."""
+        # wait for dependencies to be ready (streams and music)
+        await self.mass.streams.initialized.wait()
+        await self.mass.music.initialized.wait()
+
         self.config = config
         if not self.logger.isEnabledFor(VERBOSE_LOG_LEVEL):
             # silence PIL logger
index b4c984629e98c169c190ddd1998ebc0395e9cc88..00ccf9a8c73a8bebf1d12d18ce72084418879394 100644 (file)
@@ -195,28 +195,35 @@ class MusicAssistant:
         for controller_name in CONFIGURABLE_CORE_CONTROLLERS:
             controller: CoreController = getattr(self, controller_name)
             self._provider_manifests[controller.domain] = controller.manifest
-        # load webserver/api first so the api/frontend is available as soon as possible,
-        # other controllers are not yet available while we're starting (or performing migrations)
+
+        # setup all core controllers in parallel
+        async def setup_controller(controller: CoreController) -> None:
+            await controller.setup(await self.config.get_core_config(controller.domain))
+            controller.initialized.set()
+
+        async with asyncio.TaskGroup() as tg:
+            tg.create_task(setup_controller(self.cache))
+            tg.create_task(setup_controller(self.streams))
+            tg.create_task(setup_controller(self.music))
+            tg.create_task(setup_controller(self.metadata))
+            tg.create_task(setup_controller(self.players))
+            tg.create_task(setup_controller(self.player_queues))
+
+        # load webserver/api now that the core controllers are setup and ready to be used
         self._register_api_commands()
         await self.webserver.setup(await self.config.get_core_config("webserver"))
-        await self.cache.setup(await self.config.get_core_config("cache"))
-        await self.streams.setup(await self.config.get_core_config("streams"))
-        await self.music.setup(await self.config.get_core_config("music"))
-        await self.metadata.setup(await self.config.get_core_config("metadata"))
-        await self.players.setup(await self.config.get_core_config("players"))
-        await self.player_queues.setup(await self.config.get_core_config("player_queues"))
         # load builtin providers (always needed, also in safe mode)
         await self._load_builtin_providers()
         # setup discovery
         await self._setup_discovery()
-        # at this point we are fully up and running,
-        # set state to running to signal we're ready
-        self._state = CoreState.RUNNING
         # load regular providers (skip when in safe mode)
         # providers are loaded in background tasks so they won't block
         # the startup if they fail or take a long time to load
         if not self.safe_mode:
             await self._load_providers()
+        # at this point we are fully up and running,
+        # set state to running to signal we're ready
+        self._state = CoreState.RUNNING
 
     async def stop(self) -> None:
         """Stop running the music assistant server."""
@@ -840,9 +847,9 @@ class MusicAssistant:
         ]
 
         # load builtin providers and wait for them to complete
-        await asyncio.gather(
-            *[self.load_provider(conf.instance_id, allow_retry=True) for conf in builtin_configs]
-        )
+        async with asyncio.TaskGroup() as tg:
+            for conf in builtin_configs:
+                tg.create_task(self.load_provider(conf.instance_id, allow_retry=True))
 
     async def _load_providers(self) -> None:
         """
index e8b937e015516cafe56a7202554aeee6a008fa94..cd0f2ebdfcfe0cc5e15c61270b4f73b2c45b8f52 100644 (file)
@@ -2,6 +2,7 @@
 
 from __future__ import annotations
 
+import asyncio
 import logging
 from typing import TYPE_CHECKING
 
@@ -23,8 +24,9 @@ class CoreController:
     manifest: ProviderManifest  # some info for the UI only
 
     def __init__(self, mass: MusicAssistant) -> None:
-        """Initialize MusicProvider."""
+        """Initialize core controller."""
         self.mass = mass
+        self.initialized = asyncio.Event()
         self._set_logger()
         self.manifest = ProviderManifest(
             type=ProviderType.CORE,