Bug 325185
| Summary: | [GTK][WPE][Skia] Web process spins at 100% CPU forever in SkFontMgr_fontconfig GetFamilyNames() when fontconfig lists a font without a family | ||
|---|---|---|---|
| Product: | WebKit | Reporter: | vasiliji.markin |
| Component: | WebKitGTK | Assignee: | Nobody <webkit-unassigned> |
| Status: | NEW | ||
| Severity: | Normal | CC: | bjorn.regnell, bugs-noreply, froehlich.sven, mcatanzaro, mgorse, nekohayo, orisgo111 |
| Priority: | P2 | ||
| Version: | Other | ||
| Hardware: | PC | ||
| OS: | Linux | ||
| See Also: |
https://bugzilla.redhat.com/show_bug.cgi?id=2542946 https://bugs.webkit.org/show_bug.cgi?id=315135 https://bugzilla.redhat.com/show_bug.cgi?id=2542395 https://bugs.webkit.org/show_bug.cgi?id=326509 |
||
vasiliji.markin
With libwebkit2gtk-4.1-0 2.52.6 (Ubuntu 26.04) both WebKitWebProcess processes of an app (Handy 0.9.7, a Tauri app) used one full core each from the start, forever: 5 h 31 min of CPU time in 5 h 32 min, no voluntary context switches while watched. The loop runs inside the WebPage constructor, so the pages never finish loading. The same code is in WebKit main (Source/ThirdParty/skia) and in Skia main.
Stack (perf, symbols from debuginfod.ubuntu.com):
FcPatternGetString
SkFontMgr_fontconfig::GetFamilyNames(FcConfig*) SkFontMgr_fontconfig.cpp:648
SkFontMgr_New_FontConfig SkFontMgr_fontconfig.cpp:993
WebCore::FontCache::fontManager() const FontCacheSkia.cpp:70
WebCore::FontCache::createFontPlatformData
WebCore::FontCache::cachedFontPlatformData
WebCore::FontCache::fontForFamily
WebCore::CSSFontSelector::fontRangesForFamily
WebCore::FontCascadeFonts::realizeFallbackRangesAt / primaryFont
WebCore::Style::Resolver::initialize
WebCore::Document::resolveStyle <- Document::finishedParsing
WebCore::DocumentLoader::maybeLoadEmpty
WebKit::WebPage::WebPage <- WebKit::WebProcess::createWebPage
Cause: GetFamilyNames() walks every pattern with
for (int id = 0; ; ++id) {
FcResult result = FcPatternGetString(current, FC_FAMILY, id, &fcFamilyName);
if (FcResultNoId == result) break;
if (FcResultMatch != result) continue;
For a pattern without any FC_FAMILY element, fontconfig returns FcResultNoMatch for every id (FcResultNoId only when the element exists and id is past its last value), so the loop never ends. FontContainsCharacter() has the same loop over FC_CHARSET. (AnyStringMatching() already caps its loop at 65536 ids.)
Checked with a small program against fontconfig 2.17.1: for a pattern with only "file" and "fontwrapper", FcPatternGetString(FC_FAMILY) returns NoMatch for id 0, 1 and 1000; the loop above does not exit within 10 million iterations; the loop in the patch below exits at once and gives the same result as before for normal fonts.
Such patterns exist in practice: fontconfig's fontations backend stores a placeholder with only "file" and "fontwrapper" for every WOFF/WOFF2 file, and these reach other fontconfig builds through shared caches. Chrome 154 bundles such a fontconfig and links its cache for the system fontconfig (https://gitlab.freedesktop.org/fontconfig/fontconfig/-/work_items/565), and the font cache in the Kubuntu 26.04.1 image had them too. Here they were the 40 .woff/.woff2 files of fonts-katex.
Suggested fix (Source/ThirdParty/skia/src/ports/SkFontMgr_fontconfig.cpp, the same in Skia upstream):
--- a/src/ports/SkFontMgr_fontconfig.cpp
+++ b/src/ports/SkFontMgr_fontconfig.cpp
@@ static sk_sp<SkDataTable> GetFamilyNames(FcConfig* fcconfig) {
FcResult result = FcPatternGetString(current, FC_FAMILY, id, &fcFamilyName);
- if (FcResultNoId == result) {
- break;
- }
- if (FcResultMatch != result) {
+ if (FcResultTypeMismatch == result) {
continue;
}
+ if (FcResultMatch != result) {
+ // NoId: no more values; NoMatch: the font has no family at all.
+ break;
+ }
@@ static bool FontContainsCharacter(FcPattern* font, uint32_t character) {
result = FcPatternGetCharSet(font, FC_CHARSET, charSetId, &matchCharSet);
- if (FcResultNoId == result) {
- break;
- }
- if (FcResultMatch != result) {
+ if (FcResultTypeMismatch == result) {
continue;
}
+ if (FcResultMatch != result) {
+ break;
+ }
Workaround for users: a fontconfig <rejectfont> rule for *.woff and *.woff2.
(Found with the help of an AI assistant, Claude; the profile, stack and test are from my machine.)
| Attachments | ||
|---|---|---|
| Add attachment proposed patch, testcase, etc. |
Netmonroots
Confirmed on Zorin OS 18.1 with WebKitGTK 2.52.6.
Symptoms:
- Proton Authenticator: completely white WebView
- Minimal GTK3/WebKit2 4.1 WebView: completely white
- WebKitWebProcess/NetworkProcess started, but the page did not render.
fc-match -v selected:
file: "/usr/share/fonts/woff/opendyslexic/OpenDyslexic-Bold.woff"
fontwrapper: "WOFF"
Installed WOFF fonts were:
/usr/share/fonts/woff/opendyslexic/OpenDyslexic-Bold.woff
/usr/share/fonts/woff/opendyslexic/OpenDyslexic-Italic.woff
/usr/share/fonts/woff/opendyslexic/OpenDyslexic-Regular.woff
/usr/share/fonts/woff/opendyslexic/OpenDyslexic-BoldItalic.woff
Adding a per-user Fontconfig reject rule for *.woff and *.woff2 and
rebuilding the Fontconfig cache immediately restored WebKit rendering.
After reboot, Proton Authenticator and the minimal WebKitGTK test
continued to work normally.
Interestingly, Aloha Browser (Chromium/Blink), which had been crashing
in RemoteFontFaceSource::CreateLoadingFallbackFontData(), also started
working normally after the same Fontconfig change. I am mentioning this
only as an observation; I cannot confirm that the Blink issue has the
same underlying bug.
Michael Catanzaro
*** Bug 326509 has been marked as a duplicate of this bug. ***