diff --git a/api_drivers/common_api_drivers/frozen/other/task_handler.py b/api_drivers/common_api_drivers/frozen/other/task_handler.py index 8a19877e..d160cd89 100644 --- a/api_drivers/common_api_drivers/frozen/other/task_handler.py +++ b/api_drivers/common_api_drivers/frozen/other/task_handler.py @@ -57,7 +57,11 @@ def __init__( self._task_handler_ref = self._task_handler self.max_scheduled = max_scheduled - self._start_time = time.ticks_ms() # NOQA + self._last_tick = time.ticks_ms() # NOQA + # Fairness: how long the last handler pass took and when it ended, + # so the timer can leave the main thread a share of the CPU. + self._last_run_ms = 0 + self._last_end = self._last_tick self._timer.init( mode=Timer.PERIODIC, period=self.duration, @@ -102,6 +106,7 @@ def is_running(cls): return cls._current_instance is not None def _task_handler(self, _): + run_start = time.ticks_ms() # NOQA try: self._scheduled -= 1 @@ -128,12 +133,6 @@ def _task_handler(self, _): print(f"TaskHandler callback {cb} threw exception, disabling it") self.remove_event_cb(cb) - stop_time = time.ticks_ms() # NOQA - - ticks_diff = time.ticks_diff(stop_time, self._start_time) # NOQA - self._start_time = stop_time - lv.tick_inc(ticks_diff) - if run_update: try: lv.task_handler() @@ -142,8 +141,6 @@ def _task_handler(self, _): sys.print_exception(e) # LVGL UI still hangs - start_time = time.ticks_ms() # NOQA - for cb, evt, data in self._callbacks: if not evt & TASK_HANDLER_FINISHED: continue @@ -159,10 +156,6 @@ def _task_handler(self, _): else: sys.print_exception(err) # NOQA - stop_time = time.ticks_ms() # NOQA - ticks_diff = time.ticks_diff(stop_time, start_time) # NOQA - lv.tick_inc(ticks_diff) - self._running = False except Exception as e: @@ -171,11 +164,31 @@ def _task_handler(self, _): if self.exception_hook: self.exception_hook(e) + finally: + end = time.ticks_ms() # NOQA + self._last_run_ms = time.ticks_diff(end, run_start) # NOQA + self._last_end = end + def _timer_cb(self, _): - lv.tick_inc(self.duration) + # The only place LVGL time advances. Feed it the real elapsed time + # rather than the nominal period: timer callbacks are delivered via the + # scheduler and coalesce or drop under load, and _task_handler must not + # add ticks of its own or LVGL time runs ~2x faster than wall clock. + now = time.ticks_ms() # NOQA + lv.tick_inc(time.ticks_diff(now, self._last_tick)) # NOQA + self._last_tick = now if self._running: return + # Fairness: a handler pass that outlasts the timer period would + # otherwise be rescheduled back-to-back, starving everything else on + # the main thread (the REPL, touch and app tasks). Wait until at + # least a quarter of the last pass's duration has elapsed since it + # ended, so the main thread always keeps ~20% of the CPU. + gap = self._last_run_ms >> 2 + if gap > self.duration and time.ticks_diff(now, self._last_end) < gap: # NOQA + return + if self._scheduled < self.max_scheduled: try: micropython.schedule(self._task_handler_ref, 0)