Skip to content

FPSLimit (with VSync disabled) can freeze the UI permanently #98

Description

@szb78

FPSLimit (with VSync disabled) can freeze the UI permanently — unsigned underflow in draw_screen() frame limiter

Summary

When VSync=false and FPSLimit is set, the manual frame-limiter in draw_screen() can compute a wildly wrong sleep duration through an unsigned-integer underflow, causing SDL_Delay() to block for up to ~49 days — i.e. a permanent UI freeze. It triggers whenever a single iteration takes longer than the frame budget (1000 / FPSLimit ms), which routinely happens while icon textures are (re)loaded during navigation, page changes, or when returning to the launcher after a launched application exits.

Affected version

  • v2.2 (latest release).
  • The identical code is present on master (src/launcher.c), so it is not yet fixed.
  • Reproduced on Linux (Ubuntu Server 24.04, SDL 2.30) driving a 1080p60 display, but the logic is platform-independent.

Root cause

src/launcher.c, in draw_screen() (lines ~813–818):

SDL_RenderPresent(renderer);
if (!config.vsync) {
    Uint32 sleep_time = refresh_period - (SDL_GetTicks() - ticks.main);
    if (sleep_time > 0)
        SDL_Delay(sleep_time);
}
  • ticks.main is captured at the top of each main-loop iteration (ticks.main = SDL_GetTicks();, line ~1357), so SDL_GetTicks() - ticks.main is the full elapsed time of the iteration (event handling + texture loading + rendering).
  • refresh_period is 1000 / FPSLimit (computed at line ~234).
  • sleep_time is declared Uint32 (unsigned). When the elapsed time exceeds refresh_period, the expression refresh_period - elapsed is negative, but because the result is unsigned it wraps to a value near UINT32_MAX (~4.29 × 10⁹).
  • The guard if (sleep_time > 0) cannot catch this: an unsigned value is > 0 for everything except exactly 0. So SDL_Delay(~4.29e9 ms) executes and the program hangs for ~49 days. SDL_Delay is uninterruptible, so the launcher must be force-killed.

A single frame easily exceeds the budget because lazy texture loading (icons via SDL_image) during navigation, page changes, or returning from a launched app can take tens to hundreds of ms — far more than e.g. the 66 ms budget at FPSLimit=15. The lower the FPSLimit (the whole point of the setting — reducing idle GPU/CPU), the more likely the freeze.

Steps to reproduce

  1. In config.ini under [General]: set VSync=false and FPSLimit=15 (any low cap).
  2. Launch flex-launcher and navigate between entries/pages, or launch an app and return to the launcher.
  3. On the first iteration whose load time exceeds 1000 / FPSLimit ms (typically a texture load), the UI freezes permanently.

This is not specific to submenu navigation. It has been reproduced with all entries flattened into a single menu (no :submenu entries at all), confirming it fires on any texture-loading frame — including returning to the launcher after a launched application exits — not just submenu transitions.

Impact

A hard, unrecoverable UI freeze — the process is alive but stuck in SDL_Delay. On an appliance / kiosk / HTPC with no keyboard, recovery requires a power-cycle. This makes FPSLimit effectively unusable for its intended purpose (lowering idle GPU/CPU usage).

Suggested fix

Compute the elapsed time first and only sleep if time remains, avoiding the unsigned subtraction entirely:

if (!config.vsync) {
    Uint32 elapsed = SDL_GetTicks() - ticks.main;
    if (elapsed < refresh_period)
        SDL_Delay(refresh_period - elapsed);
}

(Equivalently: compute sleep_time as a signed int32_t and keep the > 0 check.) Either way, a frame that overruns its budget simply proceeds to the next frame immediately instead of sleeping for ~49 days.


Disclaimer: this report was written by Claude Code on behalf of the reporter. The root-cause analysis was verified directly against src/launcher.c at tag v2.2 and on master, and the freeze was reproduced on the reporter's hardware (HP T630 thin client, Ubuntu Server 24.04, SDL 2.30).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions