From a992654a8b7247f2d4c6ce33501fdcbdd13890dd Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Fri, 14 Jun 2024 00:47:38 +0200 Subject: [PATCH] Fix blocking IO calls in mqtt client setup (#119647) --- homeassistant/components/mqtt/async_client.py | 2 +- homeassistant/components/mqtt/client.py | 29 +++++++++++++++---- homeassistant/components/mqtt/config_flow.py | 4 ++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/mqtt/async_client.py b/homeassistant/components/mqtt/async_client.py index c0b847f35a1503..882e910d7e8e11 100644 --- a/homeassistant/components/mqtt/async_client.py +++ b/homeassistant/components/mqtt/async_client.py @@ -44,7 +44,7 @@ class AsyncMQTTClient(MQTTClient): that is not needed since we are running in an async event loop. """ - def async_setup(self) -> None: + def setup(self) -> None: """Set up the client. All the threading locks are replaced with NullLock diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index 13f33c4404744a..ace2293e7a622f 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -277,9 +277,22 @@ class Subscription: class MqttClientSetup: """Helper class to setup the paho mqtt client from config.""" + _client: AsyncMQTTClient + def __init__(self, config: ConfigType) -> None: - """Initialize the MQTT client setup helper.""" + """Initialize the MQTT client setup helper. + + self.setup must be run in an executor job. + """ + self._config = config + + def setup(self) -> None: + """Set up the MQTT client. + + The setup of the MQTT client should be run in an executor job, + because it accesses files, so it does IO. + """ # We don't import on the top because some integrations # should be able to optionally rely on MQTT. import paho.mqtt.client as mqtt # pylint: disable=import-outside-toplevel @@ -287,6 +300,7 @@ def __init__(self, config: ConfigType) -> None: # pylint: disable-next=import-outside-toplevel from .async_client import AsyncMQTTClient + config = self._config if (protocol := config.get(CONF_PROTOCOL, DEFAULT_PROTOCOL)) == PROTOCOL_31: proto = mqtt.MQTTv31 elif protocol == PROTOCOL_5: @@ -298,11 +312,14 @@ def __init__(self, config: ConfigType) -> None: # PAHO MQTT relies on the MQTT server to generate random client IDs. # However, that feature is not mandatory so we generate our own. client_id = mqtt.base62(uuid.uuid4().int, padding=22) - transport = config.get(CONF_TRANSPORT, DEFAULT_TRANSPORT) + transport: str = config.get(CONF_TRANSPORT, DEFAULT_TRANSPORT) self._client = AsyncMQTTClient( - client_id, protocol=proto, transport=transport, reconnect_on_failure=False + client_id, + protocol=proto, + transport=transport, + reconnect_on_failure=False, ) - self._client.async_setup() + self._client.setup() # Enable logging self._client.enable_logger() @@ -544,7 +561,9 @@ async def async_init_client(self) -> None: self.hass, "homeassistant.components.mqtt.async_client" ) - mqttc = MqttClientSetup(self.conf).client + mqttc_setup = MqttClientSetup(self.conf) + await self.hass.async_add_executor_job(mqttc_setup.setup) + mqttc = mqttc_setup.client # on_socket_unregister_write and _async_on_socket_close # are only ever called in the event loop mqttc.on_socket_close = self._async_on_socket_close diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 2c5d921e1db7dc..17dfc6512b3076 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -834,7 +834,9 @@ def try_connection( # should be able to optionally rely on MQTT. import paho.mqtt.client as mqtt # pylint: disable=import-outside-toplevel - client = MqttClientSetup(user_input).client + mqtt_client_setup = MqttClientSetup(user_input) + mqtt_client_setup.setup() + client = mqtt_client_setup.client result: queue.Queue[bool] = queue.Queue(maxsize=1)