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
Wrapping the menu_and_tabs example into a @ui.page("/") causes continuous 100% CPU load #2482
Comments
Wow @pplno, that's very strange. |
I'm on Windows 11 with Chrome 121.0.6167.140 and Edge 121.0.2277.83. Both browser show the same behaviour. In the meantime the routing / paging itself turned out to be the culprit, as I can reproduce the behaviour even without any tabs on the page (see minimal code below). As mentioned above, the tabs itself without paging also show no increased CPU load. from nicegui import ui
@ui.page('/')
def index_page():
ui.label('Test')
ui.run() However, by continously refreshing the page for n times I can bring down the CPU load to 0%, just to make it pop again after n+1. I cannot see any pattern to the number of times n that it takes though. No difference between hard and soft refreshes. Also, my initial comment that there are "innocent" tabs was wrong, as I tried to reproduce it and the "innocent" tab actually change erratically. I guess it's just cycling through refreshes until the magic n-th refresh makes a tab act innocent. Edit: third machine (Ryzen 9 5900x), Windows 10, fresh .venv with nicegui==1.4.13 and Python 3.12.1 with Chrome 121.0.6167.140, completely different, now more or less open home network: same behaviour. Just to clarify: with 100% CPU load I'm referring to 100% of a single core the process is running on, e.g. ~8% of my 12-core Ryzen 9 5900x - but still enough to make my fans spin on all 3 machines. |
No, this should not happen. Can anyone else reproduce this issue? |
I stumbled over the same or a very similar problem. I was trying to create a page with many cards (~200) that consisted of an image loaded from an url and a title. When the initial request was done and the all the elements were visible (except the images which are loaded lazily) the page froze completely. Even Firefox showed a popup notifying me about a performance issue with the page and advised to stop the process. Similar to the described issue one CPU core is maxed out for the duration of the freeze (~30s). I wrote this simple showcase to see if it made a difference if the mentioned from nicegui import ui
def spam():
with ui.header().classes(replace='row items-center') as header:
ui.button(on_click=lambda: left_drawer.toggle(), icon='menu').props('flat color=white')
with ui.tabs() as tabs:
ui.tab('A')
ui.tab('B')
ui.tab('C')
with ui.link(target="/foo"):
ui.button("Foo")
with ui.row():
for i in range(200):
with ui.link(target=f"/doc/{i}"):
with ui.card().tight().classes("w-72"):
ui.image(f"https://picsum.photos/300/200?{i}").classes("h-full")
# element.get_img_ui(7)
with ui.card_section():
ui.label(f"Title {i}")
@ui.page("/foo")
def foo():
spam()
spam()
ui.run() And indeed there is a significant difference: I've conducted the tests on Linux Mint 21 (uses Ubuntu 22.04 LTS as base) with Firefox 121 and an AMD Ryzen 5 5600G. I've used the Firefox Profiler for making a recording of the calls during the loading when the problem was appearing (different code than the one above). I disabled |
@rodja An additional nugget of info: |
I'm also seeing the issue of the original poster. (Windows 10, Python 3.12, NiceGUI 1.4.19). Upon loading a @ui.page() generated page, the process randomly ends up in either a normal state (using ~0.1% of my CPU) or a high-CPU state (maxing out one CPU core). Strangely, whichever state it ends up in, displaying a dialog box causes it to toggle into the other state. I reproduced it with Firefox, Chrome, and Native-mode. After using VizTracer to capture a trace of the program in both normal and max-CPU states. I discovered that the CPU hogging code was in the Outbox.loop() function in outbox.py. There were two separate tasks running this loop, consuming the entire CPU core. After putting in some code to instrument the function, I determined the following line is the culprit: await asyncio.sleep(0.01) (from Outbox.loop() in outbox.py) There seems to be a bug in the asyncio event loop or sleep function where it gets into a state where sleep times are not respected. This results in the two tasks running this loop being resumed approximately every 50μs instead of 10ms as intended. Consequently, these two tasks take turns being executed on every iteration of the event loop, consuming all spare cycles of the core while allowing the interface to stay responsive. A workaround I've found is to set the sleep duration on the above line to 0.016 (16ms). I could easily trigger the bug with all sleep times 15ms or less, but never for sleeps of 16ms or greater. I assume this has something to do with the standard Windows timer resolution of ~15.6ms. I think this is probably a Windows only issue. If changing the sleep duration is unacceptable for all platforms, perhaps it could be changed on startup or installation for Windows users only. If anyone is interested, I can provide the captured traces of both normal and CPU-maxing behavior. Also, the issue in #2482 (comment) seems to be unrelated. |
Wow, thanks for pointing into the direction of Windows timer resolution. I found the following:
|
Yah, I saw those when trying to figure this out. This seems to be a more serious bug though as I'm seeing a complete collapse in the functioning of asyncio.sleep. It goes from returning in 10-11ms as specified in the call to returning in < 50μs and the task resuming in the next asyncio loop iteration. Thats 10,000μs vs 50. A factor of 200 isn't just an inaccuracy in the Windows clock. In this case, it's the difference between <1% CPU usage and 100%. And there's also the issue of it toggling between completely normal behavior and non-functional behavior at random. await asyncio.sleep(0.01) to await asyncio.sleep(0.016) in the Outbox.loop() function of outbox.py. This completely resolved the issue for me. Love this project so just wanted to do my part. It would be interesting to know if this solution worked for @pplno or @oidex. I have a feeling it's affecting many others without them realizing it as you'd have to be monitoring your CPU usage, notice your fans spinning up, or your battery draining because the NiceGUI interface does continue to function. |
@falkoschindler I see you assigned this issue to yourself. You might want to hold off doing anything as I'm going to submit a small PR with a better solution than what I outlined above. It'll take me a few days because I need to get the test suite up and running. |
@afullerx Ah, thanks for giving me a heads up!
What do you have in mind? |
@falkoschindler Wow, Number 1 is exactly what I was going to submit. It's done, just need to fully test. It has the added benefit that it drops CPU usage when idle by 80% on my system. Then 3 would not be necessary but may help avoid future issues with the broken asyncio.sleep method on Windows. |
Great! I'm looking forward to your pull request. You can focus on number 1 and I'll add 2 and maybe 3 later. 🙂 |
For number 2 (removing the |
Newer CPython issue link (after migration to GitHub): python/cpython#75720 |
…2482) (#2867) * Add asyncio events to outbox * Moved event clearing as a minor optimization * clean up timeout argument * Change timeout duration * Handle connection timeout * Add log import * remove extraneous whitespace * code review and improvement * Add asyncio.TimeoutError for Python < 3.11 * Fix test hangs --------- Co-authored-by: Falko Schindler <falko@zauberzeug.com>
Description
When I take the menu_and_tabs example and wrap it into a
@ui.page("/")
like below, the process then runs with continous 100% CPU load when Tab A or Tab C are active. The CPU load drops to 0% when Tab B is active. The issue doesn't persist for the original menu_and_tabs example.I reproduced the issue in a fresh .venv with nicegui==1.4.13 under Python 3.10 and 3.12
edit: in the meantime I have reproduced the issue on a second machine.
The smallest code to reproduce the problem appears to be the ui.tabs themselves
The text was updated successfully, but these errors were encountered: