From 0ba339e56c72acdb0dea2a528d511b1348fd4a59 Mon Sep 17 00:00:00 2001
From: Matthias Alphart <farmio@alphart.net>
Date: Wed, 12 Apr 2023 09:58:27 +0200
Subject: [PATCH] Run `socket.gethostbyname` in executor in Obihai and Sonos
 (#91190)

* Run  in executor in Obihai and Sonos

* fix Sonos test

* fix sonos test differently (review)
---
 homeassistant/components/obihai/config_flow.py |  6 ++++--
 homeassistant/components/sonos/__init__.py     | 14 ++++----------
 homeassistant/components/sonos/helpers.py      |  7 +++++++
 tests/components/sonos/test_init.py            | 12 ++++++------
 4 files changed, 21 insertions(+), 18 deletions(-)

diff --git a/homeassistant/components/obihai/config_flow.py b/homeassistant/components/obihai/config_flow.py
index 1a7d29fadba..6216fe0b973 100644
--- a/homeassistant/components/obihai/config_flow.py
+++ b/homeassistant/components/obihai/config_flow.py
@@ -66,7 +66,9 @@ class ObihaiFlowHandler(ConfigFlow, domain=DOMAIN):
 
         if user_input is not None:
             try:
-                ip = gethostbyname(user_input[CONF_HOST])
+                ip = await self.hass.async_add_executor_job(
+                    gethostbyname, user_input[CONF_HOST]
+                )
             except gaierror:
                 errors["base"] = "cannot_connect"
 
@@ -139,7 +141,7 @@ class ObihaiFlowHandler(ConfigFlow, domain=DOMAIN):
         """Handle a flow initialized by importing a config."""
 
         try:
-            _ = gethostbyname(config[CONF_HOST])
+            _ = await self.hass.async_add_executor_job(gethostbyname, config[CONF_HOST])
         except gaierror:
             return self.async_abort(reason="cannot_connect")
 
diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py
index 4b68030f843..c17e4b00829 100644
--- a/homeassistant/components/sonos/__init__.py
+++ b/homeassistant/components/sonos/__init__.py
@@ -48,6 +48,7 @@ from .const import (
 )
 from .exception import SonosUpdateError
 from .favorites import SonosFavorites
+from .helpers import sync_get_visible_zones
 from .speaker import SonosSpeaker
 
 _LOGGER = logging.getLogger(__name__)
@@ -338,19 +339,12 @@ class SonosDiscoveryManager:
         self, now: datetime.datetime | None = None
     ) -> None:
         """Add and maintain Sonos devices from a manual configuration."""
-
-        def get_sync_attributes(soco: SoCo) -> set[SoCo]:
-            """Ensure I/O attributes are cached and return visible zones."""
-            _ = soco.household_id
-            _ = soco.uid
-            return soco.visible_zones
-
         for host in self.hosts:
-            ip_addr = socket.gethostbyname(host)
+            ip_addr = await self.hass.async_add_executor_job(socket.gethostbyname, host)
             soco = SoCo(ip_addr)
             try:
                 visible_zones = await self.hass.async_add_executor_job(
-                    get_sync_attributes,
+                    sync_get_visible_zones,
                     soco,
                 )
             except (OSError, SoCoException, Timeout) as ex:
@@ -382,7 +376,7 @@ class SonosDiscoveryManager:
                 break
 
         for host in self.hosts.copy():
-            ip_addr = socket.gethostbyname(host)
+            ip_addr = await self.hass.async_add_executor_job(socket.gethostbyname, host)
             if self.is_device_invisible(ip_addr):
                 _LOGGER.debug("Discarding %s from manual hosts", ip_addr)
                 self.hosts.discard(ip_addr)
diff --git a/homeassistant/components/sonos/helpers.py b/homeassistant/components/sonos/helpers.py
index 5f44b9bae6f..1005b6c7d6a 100644
--- a/homeassistant/components/sonos/helpers.py
+++ b/homeassistant/components/sonos/helpers.py
@@ -117,3 +117,10 @@ def hostname_to_uid(hostname: str) -> str:
     else:
         raise ValueError(f"{hostname} is not a sonos device.")
     return f"{UID_PREFIX}{baseuid}{UID_POSTFIX}"
+
+
+def sync_get_visible_zones(soco: SoCo) -> set[SoCo]:
+    """Ensure I/O attributes are cached and return visible zones."""
+    _ = soco.household_id
+    _ = soco.uid
+    return soco.visible_zones
diff --git a/tests/components/sonos/test_init.py b/tests/components/sonos/test_init.py
index 6cc79e1b2f0..596946e9f8b 100644
--- a/tests/components/sonos/test_init.py
+++ b/tests/components/sonos/test_init.py
@@ -1,6 +1,6 @@
 """Tests for the Sonos config flow."""
 import logging
-from unittest.mock import AsyncMock, patch
+from unittest.mock import patch
 
 import pytest
 
@@ -86,16 +86,16 @@ async def test_async_poll_manual_hosts_warnings(
         manager, "_async_handle_discovery_message"
     ), patch("homeassistant.components.sonos.async_call_later"), patch(
         "homeassistant.components.sonos.async_dispatcher_send"
-    ), patch.object(
-        hass, "async_add_executor_job", new=AsyncMock()
-    ) as mock_async_add_executor_job:
-        mock_async_add_executor_job.side_effect = [
+    ), patch(
+        "homeassistant.components.sonos.sync_get_visible_zones",
+        side_effect=[
             OSError(),
             OSError(),
             [],
             [],
             OSError(),
-        ]
+        ],
+    ):
         # First call fails, it should be logged as a WARNING message
         caplog.clear()
         await manager.async_poll_manual_hosts()
-- 
GitLab