| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
LVGL is not thread-safe, but the simulator drives it from two threads: the DisplayApp task, and the SDL main thread whose refresh_screen() creates/deletes the "Screen is OFF" overlay and runs lv_task_handler while the display task sleeps. The wake/sleep handoff between them corrupts the LVGL heap. AddressSanitizer shows a heap-use-after-free: main-thread lv_obj_del(screen_off_bg) racing the display task's lv_task_handler (alloc/free in refresh_screen on T0, use in _lv_disp_refr_task on the displayapp thread). A wake/sleep stress loop (right-click toggle, ~600ms period) crashes an ASan build within ~4 cycles. This adds sim/displayapp/LvglGuard.h, a recursive mutex behind an LVGL_GUARD() macro, and takes it in refresh_screen(). The header is placed so it shadows a matching no-op header in InfiniTime, whose DisplayApp::Refresh takes the same guard around its two LVGL regions (companion change, see PR description); on hardware the macro stays a no-op. With both sides guarded, 300 stress cycles run clean under ASan. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EeA4vFDz8WACLogQhg53Kd
The main loop also pumps SDL input, so parking it on a lock held by the DisplayApp task would turn any stall inside that task into a whole-process wedge (input dead) rather than a frozen watch screen. Observed once during long-running use: the process was alive with the main thread in futex_wait. The main thread now try-locks and skips the overlay frame if the display task holds the lock (it runs again ~30 ms later), so it can never be part of a deadlock cycle. Mutual exclusion is unchanged: LVGL is still only touched under the lock, and the 300-cycle ASan stress stays clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EeA4vFDz8WACLogQhg53Kd
| Back | FazBrowse Home | New Git URL |
The complete fix needs a small companion change in InfiniTime: InfiniTimeOrg/InfiniTime#2447.
The bug
LVGL is not thread-safe, but InfiniSim drives it from two threads:
The wake/sleep handoff between the two corrupts the LVGL heap. It reproduces easily. Toggle sleep/wake with the right mouse button every ~600 ms, and an AddressSanitizer build crashes within about 4 cycles:
ERROR: AddressSanitizer: heap-use-after-free READ of size 8 ... in get_property_index lvgl/src/lv_core/lv_style.c:996 ... _lv_disp_refr_task <- lv_task_handler <- DisplayApp::Refresh (thread "displayapp") freed by thread T0 (main) here: ... lv_obj_del <- Framework::refresh_screen <- Framework::refresh <- main previously allocated by thread T0 here: ... lv_obj_set_style_local_bg_color <- Framework::refresh_screen (screen_off_bg)In a normal build the same corruption surfaces later as a SIGSEGV in the style walk during refresh (_lv_style_get_int reading a freed style map). The existing screen_off_delete_cb double-free guard covers one symptom but not the underlying race.
The fix
A std::recursive_mutex behind LVGL_GUARD() (sim/displayapp/LvglGuard.h), with the two sides locking differently on purpose:
The other half of the race is the DisplayApp task itself, which is InfiniTime code. InfiniTimeOrg/InfiniTime#2447 adds a no-op displayapp/LvglGuard.h to InfiniTime and takes LVGL_GUARD() around the two LVGL regions of DisplayApp::Refresh, not across the blocking queue wait. On hardware the macro compiles to nothing, since LVGL has a single owner there. In the sim, this repo's header shadows it via include-path order (the same mechanism the sim already uses for nrf_log.h), so both threads serialize.
Without the InfiniTime side, this change alone is safe but only partial: it serializes the overlay operations against the sim's own off-screen lv_task_handler, not against the display task.
Verification