TaskManager: without new thread works but blocks REPL

aiorepl (asyncio REPL) works but it's pretty limited

It's probably fine for production, but it means the user has to sys.exit()
in aiorepl before landing on the real interactive REPL, with asyncio tasks stopped.
This commit is contained in:
Thomas Farstrike
2025-12-11 19:02:19 +01:00
parent c0b9f68ae8
commit 7ba45e692e
4 changed files with 44 additions and 14 deletions
@@ -130,10 +130,14 @@ class AppStore(Activity):
print("Downloading icons...")
for app in self.apps:
if not self.has_foreground():
print(f"App is stopping, aborting icon downloads.")
print(f"App is stopping, aborting icon downloads.") # maybe this can continue? but then update_ui_threadsafe is needed
break
#if not app.icon_data:
app.icon_data = await self.download_url(app.icon_url)
if not app.icon_data:
try:
app.icon_data = await TaskManager.wait_for(self.download_url(app.icon_url), 5) # max 5 seconds per icon
except Exception as e:
print(f"Download of {app.icon_url} got exception: {e}")
continue
if app.icon_data:
print("download_icons has icon_data, showing it...")
image_icon_widget = None
@@ -146,7 +150,7 @@ class AppStore(Activity):
'data_size': len(app.icon_data),
'data': app.icon_data
})
self.update_ui_threadsafe_if_foreground(image_icon_widget.set_src, image_dsc) # error: 'App' object has no attribute 'image'
self.update_ui_threadsafe_if_foreground(image_icon_widget.set_src, image_dsc) # add update_ui_threadsafe() for background?
print("Finished downloading icons.")
def show_app_detail(self, app):
@@ -156,7 +160,7 @@ class AppStore(Activity):
async def download_url(self, url):
print(f"Downloading {url}")
#await TaskManager.sleep(1)
#await TaskManager.sleep(4) # test slowness
try:
async with self.aiohttp_session.get(url) as response:
if response.status >= 200 and response.status < 400:
+23 -5
View File
@@ -72,8 +72,6 @@ except Exception as e:
# This will throw an exception if there is already a "/builtin" folder present
print("main.py: WARNING: could not import/run freezefs_mount_builtin: ", e)
mpos.TaskManager()
try:
from mpos.net.wifi_service import WifiService
_thread.stack_size(mpos.apps.good_stack_size())
@@ -89,11 +87,31 @@ auto_start_app = prefs.get_string("auto_start_app", None)
if auto_start_app and launcher_app.fullname != auto_start_app:
mpos.apps.start_app(auto_start_app)
if not started_launcher:
print(f"WARNING: launcher {launcher_app} failed to start, not cancelling OTA update rollback")
else:
# Create limited aiorepl because it's better than nothing:
import aiorepl
print("Starting very limited asyncio REPL task. Use sys.exit() to stop all asyncio tasks and go to real REPL...")
mpos.TaskManager.create_task(aiorepl.task()) # only gets started when mpos.TaskManager() is created
async def ota_rollback_cancel():
try:
import ota.rollback
ota.rollback.cancel()
except Exception as e:
print("main.py: warning: could not mark this update as valid:", e)
if not started_launcher:
print(f"WARNING: launcher {launcher_app} failed to start, not cancelling OTA update rollback")
else:
mpos.TaskManager.create_task(ota_rollback_cancel()) # only gets started when mpos.TaskManager() is created
while True:
try:
mpos.TaskManager() # do this at the end because it doesn't return
except KeyboardInterrupt as k:
print(f"mpos.TaskManager() got KeyboardInterrupt, falling back to REPL shell...") # only works if no aiorepl is running
break
except Exception as e:
print(f"mpos.TaskManager() got exception: {e}")
print("Restarting mpos.TaskManager() after 10 seconds...")
import time
time.sleep(10)
+11 -4
View File
@@ -8,14 +8,17 @@ class TaskManager:
def __init__(self):
print("TaskManager starting asyncio_thread")
_thread.stack_size(mpos.apps.good_stack_size()) # tiny stack size of 1024 is fine for tasks that do nothing but for real-world usage, it needs more
_thread.start_new_thread(asyncio.run, (self._asyncio_thread(), ))
# tiny stack size of 1024 is fine for tasks that do nothing
# but for real-world usage, it needs more:
#_thread.stack_size(mpos.apps.good_stack_size())
#_thread.start_new_thread(asyncio.run, (self._asyncio_thread(100), ))
asyncio.run(self._asyncio_thread(10)) # this actually works, but it blocks the real REPL (aiorepl works, but that's limited)
async def _asyncio_thread(self):
async def _asyncio_thread(self, ms_to_sleep):
print("asyncio_thread started")
while True:
#print("asyncio_thread tick")
await asyncio.sleep_ms(100) # This delay determines how quickly new tasks can be started, so keep it below human reaction speed
await asyncio.sleep_ms(ms_to_sleep) # This delay determines how quickly new tasks can be started, so keep it below human reaction speed
print("WARNING: asyncio_thread exited, this shouldn't happen because now asyncio.create_task() won't work anymore!")
@classmethod
@@ -38,3 +41,7 @@ class TaskManager:
@staticmethod
def notify_event():
return asyncio.Event()
@staticmethod
def wait_for(awaitable, timeout):
return asyncio.wait_for(awaitable, timeout)
@@ -1,6 +1,7 @@
import lvgl as lv
import mpos.ui
import mpos.time
import mpos.battery_voltage
from .display import (get_display_width, get_display_height)
from .util import (get_foreground_app)