Visitar URL original
Linux AppImage burns a full CPU core at idle on NVIDIA — bundled WebKitGTK × NVIDIA driver interaction · Issue #8614 · phase-rs/phase · GitHub
Skip to content

Linux AppImage burns a full CPU core at idle on NVIDIA — bundled WebKitGTK × NVIDIA driver interaction #8614

Open

Description

@alicewonderland-dev

Revised 2026-09-06. The original body blamed the bundled WebKitGTK build alone. That was wrong: it is an interaction with the NVIDIA driver. A contributor pushed back that this was a graphics issue and was right. Rewritten; the correction is explained in the comments.

Summary

The Linux AppImage pegs a full CPU core while idle on the home screen — on NVIDIA. It is an interaction between the WebKitGTK build bundled into the AppImage and the NVIDIA proprietary driver. Neither factor alone explains it. On the AMD integrated GPU of the same machine, the same AppImage costs ~23% and is fine.

This is not caused by #8348. That PR fixed the boot hang from #6744 and made a pre-existing cost reachable for the first time — before it, the app never got past the splash screen.

Environment

  • Phase-Desktop-Linux-x86_64.appimage, shell-v1.0.12; web app v0.75.0–v0.76.0
  • Nobara / Fedora 44, KWin Wayland (the app runs under XWayland — AppRun forces GDK_BACKEND=x11)
  • Two GPUs, which is what made the test possible: NVIDIA RTX 3090 (driver 595.91.07) and an AMD Raphael iGPU (Mesa)
  • Bundled WebKitGTK 2.52.6; host WebKitGTK 2.52.5

The measurement that matters

Four cells, same machine, same app build, same AppDir harness, same 3840×2049 window, same web-app version, run back to back, each screenshot-verified rendering the same home screen:

NVIDIA 595.91.07 AMD iGPU (Mesa)
Bundled Ubuntu WebKit 2.52.6 98.7% 22.6%
Host Fedora WebKit 2.52.5 35.5% 23.8%

Reading it:

  • On AMD the WebKit build is irrelevant — 22.6% vs 23.8%.
  • On NVIDIA it is decisive — 98.7% vs 35.5%.
  • NVIDIA costs ~12 points more than the iGPU even with the good build, which is a separate and much smaller effect.

So the defect lives in the diagonal: Ubuntu's WebKitGTK build hits a pathological path on the NVIDIA driver.

Symptom profile (all on NVIDIA)

  • WebKitWebProcess sits at 92–100% of one core indefinitely, across 11+ runs
  • Flat from 700×500 to 3840×2049 — cost does not respond to viewport area, which is not how paint cost behaves
  • Unchanged when the window is minimized
  • Main-thread stack samples: 100% in WebCore, 0% in libc (the thread never reaches the event loop), 0% in JavaScriptCore. Four addresses account for ~88% of samples.
  • By contrast, in the fast configurations the main thread idles ~58% of the time and most of its work is JS.

Ruled out

Everything below was measured on NVIDIA, which held the GPU constant — that is precisely why the real cause stayed invisible until a second GPU was brought in. These eliminations still stand.

Hypothesis Evidence against
WebKit version Transplanting v1.0.11's 2.52.3 into the current AppDir: 99.3%, identical. Not an upstream regression.
Painting / rasterization Flat 700×500 → 3840×2049; the particle canvas has a working resize handler, so the page genuinely reflowed
Window visibility 98.5% minimized vs 98.6% visible
Audio / GStreamer Reproducing the identical 14 AAC decode failures on the fast build: 17.9%, vs 18.2% healthy
JIT 1 GB JIT reservation present; negative control JSC_useJIT=0 → 0 KB validates the check. FTL/B3/DFG in both.
Missing WebKitGPUProcess A logging wrapper at the exact helper path recorded zero spawn attempts
Different app build Same BuildID 4bd8285653d0bd1956d30bec1538128fd25bd9c3
Bundled text/graphics libs Removed so host versions load: 98.8%
Bundled GLib/GTK layer Host GLib 2.88.3 + GTK 3.24.32 loaded: 99.0%
AppRun hook environment Full AppRun env on host libs: 48.3%, with a positive control confirming the env took effect
Skia vs Cairo, Skia GPU, allocator Statically identical between the two builds
Frontend animation work Shrinking the particle canvas to 1×1 saves nothing; neutering all requestAnimationFrame saves 7 of 38 points; CSS animations 2 points
WEBKIT_DISABLE_DMABUF_RENDERER=1 98.6% — no help
WEBKIT_DISABLE_COMPOSITING_MODE=1 98.8% — no help
__NV_DISABLE_EXPLICIT_SYNC=1 99.2% — no help
All three of the above together 99.0% — no help
XWayland vs native Wayland 98.6% native Wayland vs 98.7% forced X11. Verified the backend actually changed (GDK_BACKEND=wayland in the web process env, 2 Wayland fds), since AppRun forces X11 unconditionally and silently overrides the variable.

Cross-checked against Tauri's Linux graphics guidance

Tauri's Linux graphics debugging page documents a known NVIDIA class of problems. This is not that class.

Its symptoms are blank/white windows, flicker on resize, death on resize, AcceleratedSurfaceDMABuf was unable to construct a complete framebuffer, and Gdk-Message: Error 71 (Protocol error). None of those signatures appear in any of 48 captured run logs, and the app renders correctly throughout — the symptom here is purely idle CPU.

The one documented fault that is real on this hardware — Failed to create GBM buffer ... Invalid argument — appeared only in a bare WebView run without WEBKIT_DMABUF_RENDERER_FORCE_SHM. The app sets that unconditionally on Linux (#6293), so it is already mitigated and is not the symptom being reported here.

All four documented workarounds are accounted for:

Tauri's suggestion Result here
nvidia_drm.modeset=1 Already active — nvidia_drm loaded with live DRM connectors, and this is a Wayland session on NVIDIA. The doc scopes this to drivers older than 545; this host runs 595.91.07.
__NV_DISABLE_EXPLICIT_SYNC=1 99.2% — no help
WEBKIT_DISABLE_DMABUF_RENDERER=1 98.6% — no help
WEBKIT_DISABLE_COMPOSITING_MODE=1 98.8% — no help

So there is no known environment-variable mitigation for this one. It is a distinct NVIDIA fault from the documented dmabuf class.

A second, smaller defect found along the way

The stock AppImage cannot run on Mesa at all. Forcing the Mesa EGL vendor aborts before the web process starts:

Could not create default EGL display: EGL_BAD_PARAMETER. Aborting...

The bundled libwayland-* and libxcb-* shadow the host's and break Mesa's EGL initialisation. I had to unbundle them to run the AMD cells at all. This blocks "just render on the iGPU" as a user-facing workaround, and is worth fixing independently.

Workarounds today

  • NVIDIA users: the distro package / .deb path costs ~35% versus the AppImage's ~99%.
  • Non-NVIDIA users are likely unaffected — inferred from one AMD iGPU, not broadly measured.

Open questions

  • Why Ubuntu's build specifically. Every static dimension I can check is equivalent — version, Skia, Skia GPU, JIT tiers, libpas, assertions. Naming the hot function needs debug symbols on the Ubuntu build under NVIDIA; the stack profile has four addresses to start from.
  • Intel GPUs are untested. So are other NVIDIA driver versions and other distros' WebKitGTK builds beyond Fedora's.
  • Note this codebase already carries an NVIDIA workaround in exactly this area — WEBKIT_DMABUF_RENDERER_FORCE_SHM=1, added in fix(tauri): Set WEBKIT_DMABUF_RENDERER_FORCE_SHM=1 to avoid nvidia graphics bug at no performance cost #6293. Same neighbourhood, and it is already active in these runs.
  • Whether a Flatpak (which would use the runtime's WebKit) avoids this is an assumption, not a measurement.

Related


Diagnosed with Claude Code.

Activity

  1. alicewonderland-dev commented on Sep 6, 2026

    @alicewonderland-dev
    ContributorAuthor

    Ran the upstream-regression test: it is not a regression. Correcting the suggested fix above.

    The issue body proposed transplanting an older AppImage's libwebkit2gtk into the current AppDir, on the theory that Ubuntu's 2.52.6 might carry a regression. That theory is now dead.

    Versions bundled by past releases

    Release Built Bundled WebKitGTK
    shell-v1.0.3 2026-07-24 2.52.3
    shell-v1.0.11 2026-08-29 2.52.3
    shell-v1.0.12 2026-09-04 2.52.6
    Fedora 44 host (fast) — 2.52.5

    Ubuntu did bump 2.52.3 → 2.52.6 inside the window, so the test was worth running.

    Result

    Transplanting v1.0.11's 2.52.3 (library, WebKitWebProcess, WebKitNetworkProcess and injected bundle, all version-matched) into the current v1.0.12 AppDir, everything else untouched:

    WebKit in the v1.0.12 AppDir CPU Main-thread samples
    2.52.6 (as shipped) 99.0% 100% WebCore, 0% idle
    2.52.3 (transplanted) 99.3% 96% WebCore, 3% idle
    2.52.5 (Fedora build) 44.9% —

    Verified: the process loaded the transplanted library from the test AppDir, that file reports 2.52.3, the pristine AppDir was not modified, and the run was screenshot-confirmed rendering the same home screen at 3840×2049.

    What this means

    The split is Ubuntu's build vs Fedora's build, not old vs new. Ubuntu's WebKitGTK is ~2.2× the CPU of Fedora's for this app across two different upstream versions six weeks apart.

    So the AppImage has always been this slow. It was invisible until #8348, because before that the app never got past the splash screen. Nothing regressed — a pre-existing cost became reachable.

    Pinning or downgrading the runner's WebKit will not help. Please disregard that suggestion in the issue body.

    Remaining options, roughly in order of tractability

    1. Reduce the app's own continuous main-thread work. This is the only lever fully within the project's control, and it helps every platform. Even on the fast WebKit the shell never settles: the .deb idles only 58% of the time, versus 18–20% total CPU for the bare page in a plain WebView. Something renders continuously at rest — the requestAnimationFrame particle canvas in SceneParticles (client/src/components/menu/MenuParticles.tsx) is the obvious first suspect, alongside any always-running CSS animations. If the home screen were genuinely idle at rest, both WebKit builds would be fine. Not yet quantified — I have not measured what share of the cost is the canvas.
    2. Ship a Flatpak, which uses the runtime's WebKit rather than a bundled Ubuntu one.
    3. Chase the build-config difference between Ubuntu's and Fedora's WebKitGTK packaging. Every static dimension I can check is equivalent (version, Skia, Skia GPU, JIT tiers, libpas, assertions), so this needs debug symbols or a diff of the two distros' build flags.

    Diagnosed with Claude Code.

  2. alicewonderland-dev commented on Sep 6, 2026

    @alicewonderland-dev
    ContributorAuthor

    Correction: the frontend lever is small, and my earlier bare-page comparison was confounded

    I suggested above that reducing the app's continuous main-thread work — naming the SceneParticles requestAnimationFrame canvas — was the most tractable lever. I measured it, and it isn't. Retracting that.

    Measurements

    Live page in a plain WebKitGTK WebView on the host (fast) build, window 3840×2049, JS injected 25s after load:

    Mode CPU Δ
    baseline 38.2% —
    particle canvas shrunk to 1×1 39.5% none
    all requestAnimationFrame neutered 30.9% −7.3
    all CSS animations + transitions disabled 36.2% −2.0

    Positive control, so the null result is trustworthy — probing the live page returned:

    {"canvases":["2560x1366(css 2560x1366)"], "animatedEls":1, "dom":309, "raf":"function"}

    One canvas, full size, so the selector matched. One CSS-animated element. A 309-element DOM.

    Removing every animation on the page recovers under 20% of its cost. The canvas rasterization specifically is worth nothing.

    Also correcting: the shell adds ~nothing

    I earlier contrasted "18–20% for the bare page" against the shell's 36–51%, implying the desktop shell roughly doubles the cost. That comparison was confounded by window size — the bare-page figure was measured at 1600×1000 and the shell at 3840×2049. At a matched 3840×2049 the bare page is 38.2%, versus 36–51% for the .deb shell. The shell adds essentially nothing; it is the page, at that size.

    What the residual actually is, and why it sharpens the diagnosis

    With every animation off, ~31% remains, and on the host build it scales with window size (~19% at 1600×1000 → ~38% at 3840×2049). That is ordinary paint cost for a 4K window.

    The AppImage build behaves qualitatively differently on exactly this axis: it is flat at 92–100% from 700×500 all the way to 3840×2049. A build whose cost does not respond to viewport area is not "slower painting" — it is doing something else, which is consistent with the stack profile showing the main thread never reaching the event loop.

    So option 1 in my earlier comment is withdrawn. The remaining options are 2 (ship a Flatpak, using the runtime's WebKit) and 3 (chase the Ubuntu-vs-Fedora WebKitGTK build-config difference), and 3 now looks like the one that actually matters.


    Diagnosed with Claude Code.

  3. changed the title [-]Linux AppImage burns a full CPU core at idle — the bundled WebKitGTK build costs 2.2x the distro's[/-] [+]Linux AppImage burns a full CPU core at idle on NVIDIA — bundled WebKitGTK × NVIDIA driver interaction[/+] on Sep 6, 2026
  4. alicewonderland-dev commented on Sep 6, 2026

    @alicewonderland-dev
    ContributorAuthor

    Correction: "this is a graphics card issue" was right. Body rewritten.

    A contributor pushed back that this is a graphics issue. They were right, and my headline conclusion — "the bundled WebKitGTK build is the cause" — was wrong in a way worth being explicit about. I have rewritten the issue body; this comment records what changed and why I missed it.

    Why I got it wrong

    Every measurement in the original investigation ran on this machine with the NVIDIA GPU active. That held the GPU constant across the decisive A/B. A constant cannot explain a difference, so when swapping WebKit moved the number from 99% to 45%, WebKit looked like the whole answer. It was half of one — and the missing half was invisible precisely because I never varied it.

    This box happens to have a second GPU, so the claim was directly testable.

    The test

    NVIDIA 595.91.07 AMD iGPU (Mesa)
    Bundled Ubuntu WebKit 2.52.6 98.7% 22.6%
    Host Fedora WebKit 2.52.5 35.5% 23.8%

    Same machine, same app build, same AppDir harness, same 3840×2049 window, same web-app version, run back to back, all four screenshot-verified on the same home screen.

    On AMD the WebKit build makes no difference at all (22.6 vs 23.8). On NVIDIA it makes all the difference (98.7 vs 35.5). It is an interaction, not either factor alone.

    Also tried, and they do not help

    • WEBKIT_DISABLE_DMABUF_RENDERER=1 → 98.6%
    • WEBKIT_DISABLE_COMPOSITING_MODE=1 → 98.8%

    So there is no cheap environment-variable fix, and WEBKIT_DMABUF_RENDERER_FORCE_SHM=1 (already set for NVIDIA since #6293) is active throughout and does not save it.

    Found while testing this

    The stock AppImage cannot run on Mesa at all — it aborts with Could not create default EGL display: EGL_BAD_PARAMETER before the web process starts, because its bundled libwayland-*/libxcb-* shadow the host's and break Mesa's EGL init. I had to unbundle those to run the AMD cells. That blocks "render on the iGPU" as a user workaround and is probably worth its own fix.

    What still stands, and what is still open

    The eliminations in the body all hold — they were just all measured on NVIDIA. Notably it is still not an upstream WebKit regression: 2.52.3 transplanted into the current AppDir gives 99.3%.

    Still unknown: why Ubuntu's build specifically (every static dimension is equivalent; naming the hot function needs debug symbols under NVIDIA), whether Intel is affected, and whether other NVIDIA driver versions behave differently. And the Flatpak suggestion remains an assumption I have not measured.


    Diagnosed with Claude Code.

  5. alicewonderland-dev commented on Sep 6, 2026

    @alicewonderland-dev
    ContributorAuthor

    Checked against Tauri's Linux graphics guidance — this is a different NVIDIA fault, and none of its workarounds help

    Cross-referenced Tauri's Linux graphics debugging page. Folded into the body; summarising here.

    The documented symptoms do not match. That page describes blank/white windows, flicker on resize, death on resize, AcceleratedSurfaceDMABuf was unable to construct a complete framebuffer, and Gdk-Message: Error 71 (Protocol error). None of those strings appear in any of 48 captured run logs, and the app renders correctly the whole time. Our symptom is idle CPU only.

    The one documented fault that is real on this hardware is already mitigated. I did see Failed to create GBM buffer ... Invalid argument — but only in a bare WebView run without WEBKIT_DMABUF_RENDERER_FORCE_SHM. The app sets that unconditionally on Linux (#6293), so the documented dmabuf breakage never reaches it. Worth knowing that #6293 is doing real work.

    All four workarounds tested or accounted for, stock AppImage on NVIDIA:

    Suggestion Result
    nvidia_drm.modeset=1 Already active (nvidia_drm loaded, live DRM connectors, Wayland session on NVIDIA). Doc scopes it to drivers < 545; this host is 595.91.07.
    __NV_DISABLE_EXPLICIT_SYNC=1 99.2%
    WEBKIT_DISABLE_DMABUF_RENDERER=1 98.6%
    WEBKIT_DISABLE_COMPOSITING_MODE=1 98.8%
    All three env vars together 99.0%

    Bonus, since the page raises Wayland: AppRun forces GDK_BACKEND=x11 unconditionally, so passing GDK_BACKEND=wayland is silently overridden — my first attempt at this was an invalid test. Editing the hook to actually run natively on Wayland gives 98.6%, versus 98.7% forced X11, with the backend change verified (GDK_BACKEND=wayland in the web process environment, 2 Wayland fds open). XWayland is not implicated.

    Conclusion: there is no known environment-variable mitigation for this. It sits outside the documented dmabuf class, which is consistent with the stack profile — the main thread is wedged in WebCore and never reaches the event loop, rather than failing in the rendering backend.


    Diagnosed with Claude Code.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions