Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blocking Import when loading custom component (pod_point) #121284

Closed
jamesonuk opened this issue Jul 5, 2024 · 22 comments
Closed

Blocking Import when loading custom component (pod_point) #121284

jamesonuk opened this issue Jul 5, 2024 · 22 comments

Comments

@jamesonuk
Copy link

jamesonuk commented Jul 5, 2024

The problem

Logs report an issue with blocking import when loading custom component (https://github.com/mattrayner/pod-point-home-assistant-component).
This integration is not using importlib to import dynamically which appears to be the intended check according to the docs

Where importlib being used in an integration you get a report like
Detected blocking call to import_module with args ('custom_components.better_thermostat.adapters.mqtt',) inside the event loop by custom integration 'better_thermostat' at custom_components/better_thermostat/adapters/delegate.py, line 16: self.adapter = import_module( (offender: /config/custom_components/better_thermostat/adapters/delegate.py, line 16: self.adapter = import_module(), please create a bug report at https://github.com/KartoffelToby/better_thermostat/issues For developers, please see https://developers.home-assistant.io/docs/asyncio_blocking_operations/#import_module Traceback (most recent call last):
but see logs below this is reported in core and there is no dynamic import in the integration

What version of Home Assistant Core has the issue?

core-2024.7.0

What was the last working version of Home Assistant Core?

No response

What type of installation are you running?

Home Assistant Container

Integration causing the issue

Core

Link to integration documentation on our website

No response

Diagnostics information

No response

Example YAML snippet

No response

Anything in the logs that might be useful for us?

Logger: homeassistant.util.loop
Source: util/loop.py:77
First occurred: 09:01:33 (1 occurrences)
Last logged: 09:01:33

Detected blocking call to import_module with args ('custom_components.pod_point',) in /usr/src/homeassistant/homeassistant/loader.py, line 1050: ComponentProtocol, importlib.import_module(self.pkg_path) inside the event loop; This is causing stability issues. Please create a bug report at https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue For developers, please see https://developers.home-assistant.io/docs/asyncio_blocking_operations/#import_module
Traceback (most recent call last):
 File "<frozen runpy>", line 198, in _run_module_as_main
 File "<frozen runpy>", line 88, in _run_code
 File "/usr/src/homeassistant/homeassistant/__main__.py", line 223, in <module> sys.exit(main())
 File "/usr/src/homeassistant/homeassistant/__main__.py", line 209, in main exit_code = runner.run(runtime_conf)
 File "/usr/src/homeassistant/homeassistant/runner.py", line 190, in run return loop.run_until_complete(setup_and_run_hass(runtime_config))
 File "/usr/local/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete self.run_forever()
 File "/usr/local/lib/python3.12/asyncio/base_events.py", line 641, in run_forever self._run_once()
 File "/usr/local/lib/python3.12/asyncio/base_events.py", line 1990, in _run_once handle._run()
 File "/usr/local/lib/python3.12/asyncio/events.py", line 88, in _run self._context.run(self._callback, *self._args)
 File "/usr/src/homeassistant/homeassistant/setup.py", line 167, in async_setup_component result = await _async_setup_component(hass, domain, config)
 File "/usr/src/homeassistant/homeassistant/setup.py", line 322, in _async_setup_component component = await integration.async_get_component()
 File "/usr/src/homeassistant/homeassistant/loader.py", line 1002, in async_get_component comp = self._get_component()
 File "/usr/src/homeassistant/homeassistant/loader.py", line 1050, in _get_component ComponentProtocol, importlib.import_module(self.pkg_path)

Additional information

No response

@elupus
Copy link
Contributor

elupus commented Jul 5, 2024

@jamesonuk
Copy link
Author

It is doing some file io here: https://github.com/mattrayner/pod-point-home-assistant-component/blob/4320be5f60abea50776fba7511c2c6d1cae0643e/custom_components/pod_point/__init__.py#L90

Is it though? It is using 'pathlib' not 'os' and I thought that parent was essentially string manipulation? (and __FILE__ is not blocking either?)

Indeed I did attempt to replace pathlib with aiopath and you can't await parent.

The error does explicitly mention that it is call to import_module in loader.py that it is complaining about

ComponentProtocol, importlib.import_module(self.pkg_path)

I haven't looked at how HA is detecting blocking IO but I would have thought that dynamic importlib imports in custom components would be picked up when initialising the component not when trying to actually import it?

@elupus
Copy link
Contributor

elupus commented Jul 6, 2024

Path() does io. It may do listdirs and other stuff to find paths. PurePath() does not do io.

Just moving the calculation to a constant at module level would solve that.

But it is unclear if we catch those cases yet, so not sure that is what it is loving here.

@jamesonuk
Copy link
Author

It is not my component, just looking to put in a PR to get these warnings out of my logs :)
`
More for my info but is it doing IO? If I do

from pathlib import Path
x = Path("/some_other_path/test/xxx")
print (x.parent)

I get /some_other_path/test output as expected and that path definitely doesn't exist.

@elupus
Copy link
Contributor

elupus commented Jul 6, 2024

From docs on PurePath

You want to make sure that your code only manipulates paths without actually accessing the OS. In this case, instantiating one of the pure classes may be useful since those simply don’t have any OS-accessing operations

Standard path may do os level calls. Which may be blocking. But it os not guaranteed it is. Still a bad thing to use in async code.

But like i said, I'm not sure that is what we detect here.

@elupus
Copy link
Contributor

elupus commented Jul 6, 2024

Ps. Dont over complicate things (no need for aiofiles). Move stuff like this to module top level next to imports as a constant.

@jamesonuk
Copy link
Author

As I said it is not my integration and looking to do the fewest changes.
Did swap to PurePath and still get the warning so appears this is not the issue.

Might try and dig into the new blocking check code and see if I can see where this is actually trigger from

@Floriszz
Copy link

Floriszz commented Jul 7, 2024

This, update from 2024.6.4 to 2024.7.1, caused 3 of my integration to stop working because this issue with the 'import_module'.
Besides the above mentioned error. The integration page just says orange exclamation mark with 'not loaded', and no option to reload. Only delete.
These integrations are;

  • Google Nest thermostat with DMS API (link)
  • HACS - OJ Microline thermostat (link
  • HACS - Omnik Convertor (Solar panel power convertor) Github link

After restore of version 2024.6.4 the integrations worked right away. Is not a solution but workaround for the time being.

@elupus elupus changed the title Blocking Import when loading custom component Blocking Import when loading custom component (pod_point) Jul 7, 2024
@elupus
Copy link
Contributor

elupus commented Jul 7, 2024

@Floriszz you need to report that to the developers of those components. This issue is about pod_point.

@Floriszz
Copy link

Floriszz commented Jul 8, 2024

@elupus , thanks I'll. I get the same error. So they all share the same problem. I will point them to here for a resolution direction.

@BobC76
Copy link

BobC76 commented Aug 27, 2024

+1 for seeing same issue with pod point integration here, and also #121868

@issue-triage-workflows
Copy link

There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates.
Please make sure to update to the latest Home Assistant version and check if that solves the issue. Let us know if that works for you by adding a comment 👍
This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions.

@jamesonuk
Copy link
Author

just a nudge as I am struggling to see where this comes from and logs are no real help.
working through it there are no importlib references in the project (which is what this error normally shows and that identifies where in the integration the blocking IO is taking place but this only references core)

@elupus
Copy link
Contributor

elupus commented Dec 25, 2024

Do you have an updated warning log. I just noticed the report at the top of the issue is about a different custom component.

@elupus
Copy link
Contributor

elupus commented Dec 25, 2024

Ps. This is till invalid: https://github.com/mattrayner/pod-point-home-assistant-component/blob/e1b4aa13831e65d2fd94fca3069b0cfed650d901/custom_components/pod_point/__init__.py#L91

Just calculate the static path at module level instead of in the async function.

@jamesonuk
Copy link
Author

jamesonuk commented Dec 25, 2024

Do you have an updated warning log. I just noticed the report at the top of the issue is about a different custom component.

Custom component is still the same one as I reported. This is possibly a case of the logging not being detailed enough but every other component I have seen this in shows code form the actual integration as the source of the blocking IO. This is picking up core as doing the blocking IO when importing the module but I am struggling to see why.

(I know the Path bit is still there but even if you replace that with a literal, the issue still gets logged)

Logger: homeassistant.util.loop
Source: util/loop.py:77
First occurred: 24 December 2024 at 17:11:03 (1 occurrences)
Last logged: 24 December 2024 at 17:11:03

Detected blocking call to import_module with args ('custom_components.pod_point',) 
in /usr/src/homeassistant/homeassistant/loader.py, line 1074: ComponentProtocol, importlib.import_module(self.pkg_path) inside the event loop; This is causing stability issues. Please create a bug report at https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue For developers, please see https://developers.home-assistant.io/docs/asyncio_blocking_operations/#import_module Traceback (most recent call last): 
 File "<frozen runpy>", line 198, in _run_module_as_main 
 File "<frozen runpy>", line 88, in _run_code 
 File "/usr/src/homeassistant/homeassistant/__main__.py", line 227, in <module> sys.exit(main()) 
 File "/usr/src/homeassistant/homeassistant/__main__.py", line 213, in main exit_code = runner.run(runtime_conf) 
 File "/usr/src/homeassistant/homeassistant/runner.py", line 154, in run return loop.run_until_complete(setup_and_run_hass(runtime_config)) 
 File "/usr/local/lib/python3.13/asyncio/base_events.py", line 708, in run_until_complete self.run_forever() 
 File "/usr/local/lib/python3.13/asyncio/base_events.py", line 679, in run_forever self._run_once() 
 File "/usr/local/lib/python3.13/asyncio/base_events.py", line 2027, in _run_once handle._run() 
 File "/usr/local/lib/python3.13/asyncio/events.py", line 89, in _run self._context.run(self._callback, *self._args) 
 File "/usr/src/homeassistant/homeassistant/setup.py", line 165, in async_setup_component result = await _async_setup_component(hass, domain, config) 
 File "/usr/src/homeassistant/homeassistant/setup.py", line 334, in _async_setup_component component = await integration.async_get_component() 
 File "/usr/src/homeassistant/homeassistant/loader.py", line 1026, in async_get_component comp = self._get_component() 
 File "/usr/src/homeassistant/homeassistant/loader.py", line 1074, in _get_component ComponentProtocol, importlib.import_module(self.pkg_path)

@elupus
Copy link
Contributor

elupus commented Dec 25, 2024

This is in the original post "custom_components/better_thermostat/adapters/delegate.py". That would be better thermostat custom component.

This log looks more reasonable. I guess you have a config section in your configuration.yaml for pod_point?

@jamesonuk
Copy link
Author

jamesonuk commented Dec 25, 2024

This is in the original post "custom_components/better_thermostat/adapters/delegate.py". That would be better thermostat custom component.

This log looks more reasonable. I guess you have a config section in your configuration.yaml for pod_point?

Sorry that is me confusing things.... The Better Thermostat bit is showing that normally it logs the actual issue against the integration where it called import_module (The log at the bottom and title are for pod_point) but this is not referencing the integration only core.

No yaml config for this, all setup from the UI

@elupus
Copy link
Contributor

elupus commented Dec 25, 2024

Ok. The import is failing when run inside the executor. Turn on devug logging in home assistant globally, then the log should show a better error. There should be a line something like : "Failed to import pod_point in executor" with some exception information in it.

@jamesonuk
Copy link
Author

OK ran in full debug logs and it did throw up the issue.

2024-12-26 11:48:07.128 DEBUG (MainThread) [homeassistant.loader] Failed to import pod_point in executor
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/loader.py", line 1074, in _get_component
    ComponentProtocol, importlib.import_module(self.pkg_path)
                       ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/util/loop.py", line 200, in protected_loop_func
    return func(*args, **kwargs)
  File "/usr/local/lib/python3.13/importlib/__init__.py", line 88, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 1022, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "/config/custom_components/pod_point/__init__.py", line 22, in <module>
    from podpointclient.client import PodPointClient
  File "/usr/local/lib/python3.13/site-packages/podpointclient/client.py", line 31, in <module>
    class PodPointClient:
    ...<370 lines>...
            return json                                                                                                                       File "/usr/local/lib/python3.13/site-packages/podpointclient/client.py", line 38, in PodPointClient                                           session: aiohttp.ClientSession = aiohttp.ClientSession(),                                                                                                                    ~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/local/lib/python3.13/site-packages/aiohttp/client.py", line 310, in __init__
    loop = loop or asyncio.get_running_loop()
                   ~~~~~~~~~~~~~~~~~~~~~~~~^^
RuntimeError: no running event loop

but I am just struggling a bit with Python to know the details of what runs where.

It is actually moaning about a default for a parameter in __init__ in the library the integration imports but doing it at the point of importing the module rather than at runtime? I thought __init__ wouldn't be executed until you actually created the object and the integration actually passes through the session object to bypass the default anyway

    session = async_get_clientsession(hass)

    client = PodPointClient(
        username=email, password=password, session=session, http_debug=http_debug
    )

The fix is obviously to remove the default but would be interested in what the actual issue is here (for my understanding) and why __init__ is being executed at import time ??

@elupus
Copy link
Contributor

elupus commented Dec 26, 2024

Defaults set in the class are evaluated when the class structure is evaluated. Defaulta set in python arguments is also evaluated at parse time

In this case on import of the file. So just move the default into the init function. And let the default value in the function be None.

See: https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments

@jamesonuk
Copy link
Author

I have raised PR against underlying library
mattrayner/podpointclient#18

@github-actions github-actions bot locked and limited conversation to collaborators Jan 25, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants