Skip to content

Commit 86133f8

Browse files
mdegat01agners
andauthored
Move read_text to executor (#5688)
* Move read_text to executor * Fix issues found by coderabbit * formated to formatted * switch to async_capture_exception * Find and replace got one too many * Update patch mock to async_capture_exception * Drop Sentry capture from format_message The error handling got introduced in #2052, however, #2100 essentially makes sure there will never be a byte object passed to this function. And even if, the Sentry aiohttp plug-in will properly catch such an exception. --------- Co-authored-by: Stefan Agner <[email protected]>
1 parent 12c951f commit 86133f8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+205
-167
lines changed

supervisor/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def run_os_startup_check_cleanup() -> None:
5454
loop.set_debug(coresys.config.debug)
5555
loop.run_until_complete(coresys.core.connect())
5656

57-
bootstrap.supervisor_debugger(coresys)
57+
loop.run_until_complete(bootstrap.supervisor_debugger(coresys))
5858

5959
# Signal health startup for container
6060
run_os_startup_check_cleanup()

supervisor/addons/addon.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
from ..utils import check_port
8989
from ..utils.apparmor import adjust_profile
9090
from ..utils.json import read_json_file, write_json_file
91-
from ..utils.sentry import capture_exception
91+
from ..utils.sentry import async_capture_exception
9292
from .const import (
9393
WATCHDOG_MAX_ATTEMPTS,
9494
WATCHDOG_RETRY_SECONDS,
@@ -1530,7 +1530,7 @@ async def _restart_after_problem(self, state: ContainerState):
15301530
except AddonsError as err:
15311531
attempts = attempts + 1
15321532
_LOGGER.error("Watchdog restart of addon %s failed!", self.name)
1533-
capture_exception(err)
1533+
await async_capture_exception(err)
15341534
else:
15351535
break
15361536

supervisor/addons/manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from ..jobs.decorator import Job, JobCondition
2424
from ..resolution.const import ContextType, IssueType, SuggestionType
2525
from ..store.addon import AddonStore
26-
from ..utils.sentry import capture_exception
26+
from ..utils.sentry import async_capture_exception
2727
from .addon import Addon
2828
from .const import ADDON_UPDATE_CONDITIONS
2929
from .data import AddonsData
@@ -170,7 +170,7 @@ async def shutdown(self, stage: AddonStartup) -> None:
170170
await addon.stop()
171171
except Exception as err: # pylint: disable=broad-except
172172
_LOGGER.warning("Can't stop Add-on %s: %s", addon.slug, err)
173-
capture_exception(err)
173+
await async_capture_exception(err)
174174

175175
@Job(
176176
name="addon_manager_install",
@@ -388,7 +388,7 @@ async def sync_dns(self) -> None:
388388
reference=addon.slug,
389389
suggestions=[SuggestionType.EXECUTE_REPAIR],
390390
)
391-
capture_exception(err)
391+
await async_capture_exception(err)
392392
else:
393393
add_host_coros.append(
394394
self.sys_plugins.dns.add_host(

supervisor/addons/model.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -210,18 +210,6 @@ def description(self) -> str:
210210
"""Return description of add-on."""
211211
return self.data[ATTR_DESCRIPTON]
212212

213-
@property
214-
def long_description(self) -> str | None:
215-
"""Return README.md as long_description."""
216-
readme = Path(self.path_location, "README.md")
217-
218-
# If readme not exists
219-
if not readme.exists():
220-
return None
221-
222-
# Return data
223-
return readme.read_text(encoding="utf-8")
224-
225213
@property
226214
def repository(self) -> str:
227215
"""Return repository of add-on."""
@@ -646,6 +634,21 @@ def breaking_versions(self) -> list[AwesomeVersion]:
646634
"""Return breaking versions of addon."""
647635
return self.data[ATTR_BREAKING_VERSIONS]
648636

637+
async def long_description(self) -> str | None:
638+
"""Return README.md as long_description."""
639+
640+
def read_readme() -> str | None:
641+
readme = Path(self.path_location, "README.md")
642+
643+
# If readme not exists
644+
if not readme.exists():
645+
return None
646+
647+
# Return data
648+
return readme.read_text(encoding="utf-8")
649+
650+
return await self.sys_run_in_executor(read_readme)
651+
649652
def refresh_path_cache(self) -> Awaitable[None]:
650653
"""Refresh cache of existing paths."""
651654

supervisor/api/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from ..const import AddonState
1111
from ..coresys import CoreSys, CoreSysAttributes
1212
from ..exceptions import APIAddonNotInstalled, HostNotSupportedError
13-
from ..utils.sentry import capture_exception
13+
from ..utils.sentry import async_capture_exception
1414
from .addons import APIAddons
1515
from .audio import APIAudio
1616
from .auth import APIAuth
@@ -412,7 +412,7 @@ async def get_supervisor_logs(*args, **kwargs):
412412
if not isinstance(err, HostNotSupportedError):
413413
# No need to capture HostNotSupportedError to Sentry, the cause
414414
# is known and reported to the user using the resolution center.
415-
capture_exception(err)
415+
await async_capture_exception(err)
416416
kwargs.pop("follow", None) # Follow is not supported for Docker logs
417417
return await api_supervisor.logs(*args, **kwargs)
418418

supervisor/api/addons.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ async def info(self, request: web.Request) -> dict[str, Any]:
212212
ATTR_HOSTNAME: addon.hostname,
213213
ATTR_DNS: addon.dns,
214214
ATTR_DESCRIPTON: addon.description,
215-
ATTR_LONG_DESCRIPTION: addon.long_description,
215+
ATTR_LONG_DESCRIPTION: await addon.long_description(),
216216
ATTR_ADVANCED: addon.advanced,
217217
ATTR_STAGE: addon.stage,
218218
ATTR_REPOSITORY: addon.repository,

supervisor/api/host.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ async def info(self, request):
9898
ATTR_VIRTUALIZATION: self.sys_host.info.virtualization,
9999
ATTR_CPE: self.sys_host.info.cpe,
100100
ATTR_DEPLOYMENT: self.sys_host.info.deployment,
101-
ATTR_DISK_FREE: self.sys_host.info.free_space,
102-
ATTR_DISK_TOTAL: self.sys_host.info.total_space,
103-
ATTR_DISK_USED: self.sys_host.info.used_space,
104-
ATTR_DISK_LIFE_TIME: self.sys_host.info.disk_life_time,
101+
ATTR_DISK_FREE: await self.sys_host.info.free_space(),
102+
ATTR_DISK_TOTAL: await self.sys_host.info.total_space(),
103+
ATTR_DISK_USED: await self.sys_host.info.used_space(),
104+
ATTR_DISK_LIFE_TIME: await self.sys_host.info.disk_life_time(),
105105
ATTR_FEATURES: self.sys_host.features,
106106
ATTR_HOSTNAME: self.sys_host.info.hostname,
107107
ATTR_LLMNR_HOSTNAME: self.sys_host.info.llmnr_hostname,

supervisor/api/store.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,12 @@
6969
)
7070

7171

72-
def _read_static_file(path: Path) -> Any:
72+
def _read_static_file(path: Path, binary: bool = False) -> Any:
7373
"""Read in a static file asset for API output.
7474
7575
Must be run in executor.
7676
"""
77-
with path.open("r") as asset:
77+
with path.open("rb" if binary else "r") as asset:
7878
return asset.read()
7979

8080

@@ -109,7 +109,7 @@ def _extract_repository(self, request: web.Request) -> Repository:
109109

110110
return self.sys_store.get(repository_slug)
111111

112-
def _generate_addon_information(
112+
async def _generate_addon_information(
113113
self, addon: AddonStore, extended: bool = False
114114
) -> dict[str, Any]:
115115
"""Generate addon information."""
@@ -156,7 +156,7 @@ def _generate_addon_information(
156156
ATTR_HOST_NETWORK: addon.host_network,
157157
ATTR_HOST_PID: addon.host_pid,
158158
ATTR_INGRESS: addon.with_ingress,
159-
ATTR_LONG_DESCRIPTION: addon.long_description,
159+
ATTR_LONG_DESCRIPTION: await addon.long_description(),
160160
ATTR_RATING: rating_security(addon),
161161
ATTR_SIGNED: addon.signed,
162162
}
@@ -185,10 +185,12 @@ async def reload(self, request: web.Request) -> None:
185185
async def store_info(self, request: web.Request) -> dict[str, Any]:
186186
"""Return store information."""
187187
return {
188-
ATTR_ADDONS: [
189-
self._generate_addon_information(self.sys_addons.store[addon])
190-
for addon in self.sys_addons.store
191-
],
188+
ATTR_ADDONS: await asyncio.gather(
189+
*[
190+
self._generate_addon_information(self.sys_addons.store[addon])
191+
for addon in self.sys_addons.store
192+
]
193+
),
192194
ATTR_REPOSITORIES: [
193195
self._generate_repository_information(repository)
194196
for repository in self.sys_store.all
@@ -199,10 +201,12 @@ async def store_info(self, request: web.Request) -> dict[str, Any]:
199201
async def addons_list(self, request: web.Request) -> dict[str, Any]:
200202
"""Return all store add-ons."""
201203
return {
202-
ATTR_ADDONS: [
203-
self._generate_addon_information(self.sys_addons.store[addon])
204-
for addon in self.sys_addons.store
205-
]
204+
ATTR_ADDONS: await asyncio.gather(
205+
*[
206+
self._generate_addon_information(self.sys_addons.store[addon])
207+
for addon in self.sys_addons.store
208+
]
209+
)
206210
}
207211

208212
@api_process
@@ -234,7 +238,7 @@ async def addons_addon_info(self, request: web.Request) -> dict[str, Any]:
234238
async def addons_addon_info_wrapped(self, request: web.Request) -> dict[str, Any]:
235239
"""Return add-on information directly (not api)."""
236240
addon: AddonStore = self._extract_addon(request)
237-
return self._generate_addon_information(addon, True)
241+
return await self._generate_addon_information(addon, True)
238242

239243
@api_process_raw(CONTENT_TYPE_PNG)
240244
async def addons_addon_icon(self, request: web.Request) -> bytes:
@@ -243,7 +247,7 @@ async def addons_addon_icon(self, request: web.Request) -> bytes:
243247
if not addon.with_icon:
244248
raise APIError(f"No icon found for add-on {addon.slug}!")
245249

246-
return await self.sys_run_in_executor(_read_static_file, addon.path_icon)
250+
return await self.sys_run_in_executor(_read_static_file, addon.path_icon, True)
247251

248252
@api_process_raw(CONTENT_TYPE_PNG)
249253
async def addons_addon_logo(self, request: web.Request) -> bytes:
@@ -252,7 +256,7 @@ async def addons_addon_logo(self, request: web.Request) -> bytes:
252256
if not addon.with_logo:
253257
raise APIError(f"No logo found for add-on {addon.slug}!")
254258

255-
return await self.sys_run_in_executor(_read_static_file, addon.path_logo)
259+
return await self.sys_run_in_executor(_read_static_file, addon.path_logo, True)
256260

257261
@api_process_raw(CONTENT_TYPE_TEXT)
258262
async def addons_addon_changelog(self, request: web.Request) -> str:

supervisor/backups/manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from ..utils.common import FileConfiguration
3737
from ..utils.dt import utcnow
3838
from ..utils.sentinel import DEFAULT
39-
from ..utils.sentry import capture_exception
39+
from ..utils.sentry import async_capture_exception
4040
from .backup import Backup
4141
from .const import (
4242
DEFAULT_FREEZE_TIMEOUT,
@@ -525,7 +525,7 @@ async def _do_backup(
525525
return None
526526
except Exception as err: # pylint: disable=broad-except
527527
_LOGGER.exception("Backup %s error", backup.slug)
528-
capture_exception(err)
528+
await async_capture_exception(err)
529529
self.sys_jobs.current.capture_error(
530530
BackupError(f"Backup {backup.slug} error, see supervisor logs")
531531
)
@@ -718,7 +718,7 @@ async def _do_restore(
718718
raise
719719
except Exception as err: # pylint: disable=broad-except
720720
_LOGGER.exception("Restore %s error", backup.slug)
721-
capture_exception(err)
721+
await async_capture_exception(err)
722722
raise BackupError(
723723
f"Restore {backup.slug} error, see supervisor logs"
724724
) from err

supervisor/bootstrap.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Bootstrap Supervisor."""
22

33
# ruff: noqa: T100
4+
from importlib import import_module
45
import logging
56
import os
67
import signal
@@ -306,12 +307,12 @@ def reg_signal(loop, coresys: CoreSys) -> None:
306307
_LOGGER.warning("Could not bind to SIGINT")
307308

308309

309-
def supervisor_debugger(coresys: CoreSys) -> None:
310+
async def supervisor_debugger(coresys: CoreSys) -> None:
310311
"""Start debugger if needed."""
311312
if not coresys.config.debug:
312313
return
313-
# pylint: disable=import-outside-toplevel
314-
import debugpy
314+
315+
debugpy = await coresys.run_in_executor(import_module, "debugpy")
315316

316317
_LOGGER.info("Initializing Supervisor debugger")
317318

supervisor/core.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from .homeassistant.core import LANDINGPAGE
2727
from .resolution.const import ContextType, IssueType, SuggestionType, UnhealthyReason
2828
from .utils.dt import utcnow
29-
from .utils.sentry import capture_exception
29+
from .utils.sentry import async_capture_exception
3030
from .utils.whoami import WhoamiData, retrieve_whoami
3131

3232
_LOGGER: logging.Logger = logging.getLogger(__name__)
@@ -172,7 +172,7 @@ async def setup(self):
172172
"Fatal error happening on load Task %s: %s", setup_task, err
173173
)
174174
self.sys_resolution.unhealthy = UnhealthyReason.SETUP
175-
capture_exception(err)
175+
await async_capture_exception(err)
176176

177177
# Set OS Agent diagnostics if needed
178178
if (
@@ -189,7 +189,7 @@ async def setup(self):
189189
self.sys_config.diagnostics,
190190
err,
191191
)
192-
capture_exception(err)
192+
await async_capture_exception(err)
193193

194194
# Evaluate the system
195195
await self.sys_resolution.evaluate.evaluate_system()
@@ -246,12 +246,12 @@ async def start(self):
246246
await self.sys_homeassistant.core.start()
247247
except HomeAssistantCrashError as err:
248248
_LOGGER.error("Can't start Home Assistant Core - rebuiling")
249-
capture_exception(err)
249+
await async_capture_exception(err)
250250

251251
with suppress(HomeAssistantError):
252252
await self.sys_homeassistant.core.rebuild()
253253
except HomeAssistantError as err:
254-
capture_exception(err)
254+
await async_capture_exception(err)
255255
else:
256256
_LOGGER.info("Skipping start of Home Assistant")
257257

supervisor/dbus/network/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
HostNotSupportedError,
1616
NetworkInterfaceNotFound,
1717
)
18-
from ...utils.sentry import capture_exception
18+
from ...utils.sentry import async_capture_exception
1919
from ..const import (
2020
DBUS_ATTR_CONNECTION_ENABLED,
2121
DBUS_ATTR_DEVICES,
@@ -223,13 +223,13 @@ async def update(self, changed: dict[str, Any] | None = None) -> None:
223223
device,
224224
err,
225225
)
226-
capture_exception(err)
226+
await async_capture_exception(err)
227227
return
228228
except Exception as err: # pylint: disable=broad-except
229229
_LOGGER.exception(
230230
"Unkown error while processing %s: %s", device, err
231231
)
232-
capture_exception(err)
232+
await async_capture_exception(err)
233233
continue
234234

235235
# Skeep interface

supervisor/docker/addon.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
from ..jobs.const import JobCondition, JobExecutionLimit
4343
from ..jobs.decorator import Job
4444
from ..resolution.const import CGROUP_V2_VERSION, ContextType, IssueType, SuggestionType
45-
from ..utils.sentry import capture_exception
45+
from ..utils.sentry import async_capture_exception
4646
from .const import (
4747
ENV_TIME,
4848
ENV_TOKEN,
@@ -606,7 +606,7 @@ async def run(self) -> None:
606606
)
607607
except CoreDNSError as err:
608608
_LOGGER.warning("Can't update DNS for %s", self.name)
609-
capture_exception(err)
609+
await async_capture_exception(err)
610610

611611
# Hardware Access
612612
if self.addon.static_devices:
@@ -787,7 +787,7 @@ async def stop(self, remove_container: bool = True) -> None:
787787
await self.sys_plugins.dns.delete_host(self.addon.hostname)
788788
except CoreDNSError as err:
789789
_LOGGER.warning("Can't update DNS for %s", self.name)
790-
capture_exception(err)
790+
await async_capture_exception(err)
791791

792792
# Hardware
793793
if self._hw_listener:

0 commit comments

Comments
 (0)