Skip to content

Add a no-op LVGL guard hook to DisplayApp::Refresh (companion to InfiniSim thread-race fix)#2447

Open
faisal-shah wants to merge 2 commits into
InfiniTimeOrg:mainfrom
faisal-shah:lvgl-guard-seam
Open

Add a no-op LVGL guard hook to DisplayApp::Refresh (companion to InfiniSim thread-race fix)#2447
faisal-shah wants to merge 2 commits into
InfiniTimeOrg:mainfrom
faisal-shah:lvgl-guard-seam

Conversation

@faisal-shah

@faisal-shah faisal-shah commented Jul 14, 2026

Copy link
Copy Markdown

Companion to InfiniTimeOrg/InfiniSim#198.

What this does

Adds src/displayapp/LvglGuard.h with a single macro, LVGL_GUARD(), and calls it around the two LVGL regions of DisplayApp::Refresh (the state switch that runs lv_task_handler, and the message-handling block). It deliberately does not wrap the blocking queue wait between them.

On hardware the macro is a no-op:

#define LVGL_GUARD() (void) 0

So this changes nothing on the watch. LVGL there has a single owner, the display task, and the compiler drops the macro entirely.

Why

The value is in the simulator. InfiniSim drives LVGL from two threads: the DisplayApp task, and its SDL main thread, which renders 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. InfiniSim shadows this header (via include-path order, the same mechanism it already uses for nrf_log.h) with a version that defines LVGL_GUARD() as a real mutex, so both threads serialize. That side of the fix, the crash reproduction, and the AddressSanitizer trace are in InfiniTimeOrg/InfiniSim#198.

The guard has to live in InfiniTime because the DisplayApp task is InfiniTime code; InfiniSim cannot serialize against it from the outside. This PR is the minimal hook that lets it.

Scope

Two files, +24/-8. No behavior change on hardware. The mutex, the try-lock on the simulator's main thread, and the verification all live in the InfiniSim PR.

InfiniSim drives LVGL from two threads (its main thread renders the
screen-off overlay and pumps lv_task_handler while the display task
sleeps). AddressSanitizer showed the wake/sleep handoff causing
heap-use-after-free: main-thread lv_obj_del of the overlay racing this
task's lv_task_handler, crashing within ~4 wake/sleep cycles.

LVGL_GUARD() is a no-op on hardware (displayapp/LvglGuard.h); the sim
shadows the header with a real recursive mutex. The guard covers the two
LVGL regions of Refresh() but deliberately not the blocking queue wait.

Verified: 300 wake/sleep stress cycles under ASan, zero reports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeA4vFDz8WACLogQhg53Kd
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

Build size and comparison to main:

Section Size Difference
text 385232B 0B
data 944B 0B
bss 22640B 0B

Run in InfiniEmu

The switch inside DisplayApp::Refresh is now one scope deeper (wrapped in
the LVGL_GUARD block), so clang-format re-indents its body. No behavior
change; view with 'git diff -w' to see the semantic change is unchanged.
@faisal-shah faisal-shah marked this pull request as ready for review July 14, 2026 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant