Skip to content

perf(updater): the update chip pulses forever and keeps an idle app at ~30% of a CPU core #1554

Closed

Description

@ZhuchkaTriplesix

Problem

While the "vX.Y.Z available" chip is shown (UpdateAvailableBadge, title bar), the app animates it forever with _pulse.repeat(reverse: true) (lib/features/updater/update_available_badge.dart). The animation never stops, so the engine produces a frame at the display rate for as long as the window is open, and an otherwise idle app burns about a third of a CPU core.

This is every user who has a newer release available and has not pressed "Remind me later" (the chip stays until the update is installed or dismissed).

Measurements

Profile build of current dev (0.4.22+5), Linux, 120 Hz display, window 2056x2616. CPU time of all threads of the process from /proc/<pid>/task/*/stat, SQL workspace open, nothing happening:

State CPU, % of one core Frames in 8 s
no update pending 0.0-0.1% 0
update pending, chip pulsing 29-31% 475-480 (~59 fps)

Thread split with the chip: main thread ~1.8-2.0 s of CPU per 8 s (~3.8-4.2 ms per frame), raster ~0.32-0.35 s, GL / compositor threads ~0.1 s.

The same effect showed up when I first measured the real app on 0.4.18, where an update to 0.4.19 existed: 30-38% of a core on the welcome screen with nothing happening, 0.0% after updating.

Wrapping the chip in a RepaintBoundary (tried, reverted) did not help: 29.4% vs 31.4%. The cost is the per-frame pipeline of the whole app (~4 ms main thread per frame), not repainting the chip, so the fix is to produce fewer frames, not to shrink the repaint.

Solution (any of these, ordered by effect)

  1. Finite pulse. Pulse a few cycles when the chip appears (e.g. 3 x 1.4 s) and then settle to the static "active" colors. After that the app is idle again (0%).
  2. Pause when the window is not visible. No pulse in the background / minimized (AppLifecycleState != resumed, or WidgetsBindingObserver), restart on focus.
  3. Optionally re-pulse once per day or on hover, instead of continuously.

Reduced motion already stops it (QueryaMotionLevel.off / disableAnimations); the default Full level should not be a permanent animation either.

Acceptance

  • With an update pending and the window idle, CPU drops to ~0% after the initial pulse (measure with the same /proc sampling; add a cpu_idle_update_badge scenario to benchmark/app_perf_bench.dart).
  • Existing badge tests (isPulseAnimating, pulseDuration) updated for the finite pulse.
  • Chip still catches the eye when it first appears.

Related: #987 (120 Hz epic).

Activity

  1. added
    bugSomething isn't working
    performanceTheme parser epic label: performance
    uiUser interface components and widgets
    P2Medium priority / Parity & Refactoring
    on Oct 12, 2026
  2. added a commit that references this issue on Oct 15, 2026
  3. ZhuchkaTriplesix commented on Oct 25, 2026

    @ZhuchkaTriplesix
    MemberAuthor

    Fixed on dev by f6e2f04: perf(updater): pulse the update chip a few times instead of forever. It reaches main with the next dev to main merge (closed after checking the code and its tests).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium priority / Parity & RefactoringbugSomething isn't workingperformanceTheme parser epic label: performanceuiUser interface components and widgets

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions