Skip to content

Commit 3bc2c93

Browse files
NathanFlurryclaude
andcommitted
wasm-gui ROOT PINNED: the construction hang is TEXT/font rendering (~13.4s for ONE GtkLabel), NOT scheduling
Drove popup-repro to the root. X round-trips ~5ms (xsync-bench); async events MapNotify 21ms/Expose 28ms (map-bench) -> the wasm X server is FAST both ways. gtk_widget_realize ~42ms. The ~13.4s is inside gtk_widget_show_all after realize. DECISIVE: empty window -> show_all ~44ms; add ONE gtk_label -> show_all ~13.4s. So the slowness is rendering a SINGLE LABEL = the text/font path (pango + harfbuzz + freetype + cairo software raster), pathologically slow in wasm. This CORRECTS the prior "X-server starved / concurrent-guest scheduling" hypothesis -- the X server is fast; the slowness is guest-internal TEXT rendering. XU5 Thunar (ThunarPathEntry text) + the notifyd popup (summary/body text) hang because their first text render is this slow (notifyds synchronous Notify blocks during it). PLATFORM-LAYER perf issue (constraint #5), NOT a TCB decision. Likely a one-time font/glyph-cache setup. Next: pin which stage (fontconfig/FT_New_Face/harfbuzz/cairo) + a platform fix (prebuilt fontconfig cache / faster freetype). Repros: popup-repro/xsync-bench/map-bench.c. XU5 + notifyd unblock once text render is fast; XU7 ceiling is separate. XU0-XU4 DONE. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d26909a commit 3bc2c93

7 files changed

Lines changed: 129 additions & 2 deletions

File tree

experiments/wasm-gui/M8-STATUS-LOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,4 @@
157157
2026-06-25T20l (XU6 consolidation: spec + progress.html updated to the true state; notifyd RGBA theory ruled out): tested the RGBA-visual hypothesis for the notifyd popup hang -- ran notifyd + the retrying sender with the X server at DEPTH 32. Result: the wasm Xvfb does NOT support depth 32 (the server did not start, 0 sender tries, black). So depth-32 is unavailable AND at depth 24 GTK falls back gracefully when there is no RGBA visual -> the RGBA visual is NOT the cause of notifyd's popup-construction hang. The hang remains an unidentified GTK construction issue (same class as Thunar ThunarPathEntry), surfaced. Consolidated the XU6 state into XUBUNTU-SPEC.md + ~/tmp/gui-progress/progress.html: XU6 = THREE bundled apps RENDER with verified real text (appfinder + mousepad + ristretto) + the xfce4-notifyd daemon's full build + D-Bus chain works (popup draw gated on the construction hang) + xfce4-terminal gated on process-spawn. The remaining XU items are now ALL gated on deep, surfaced runtime/architecture issues: the GTK construction hang (Thunar popup-class widgets + notifyd RGBA popup), the concurrent-guest scheduling ceiling (XU7 full session), and process-spawn/fork (terminal) -- each needs human/runtime sign-off, not self-approval. XU0-XU4 DONE; XU5 platform-fixes done; XU6 = 3 apps render + notifyd daemon/D-Bus works; XU7 = full session ceiling-gated.
158158
2026-06-25T20m (construction-hang repro: the GENERIC RGBA popup pattern WORKS -> notifyd's hang is in its SPECIFIC code, not the popup pattern): per the plan, built a minimal repro (guest-xclient/popup-repro.c) of notifyd's popup-construction pattern with per-step markers: gtk_init -> RGBA visual (gdk_screen_get_rgba_visual returned NON-NULL -> the wasm X server DOES expose an RGBA visual at depth 24) -> set_visual(rgba) -> app_paintable -> primary monitor + geometry (700x500) -> label -> show_all -> move to the top-right corner -> main loop. It REACHED EVERY MARKER + "DONE" + RENDERED ("Hello notification, all wasm." at the top-right, transparent bg; proof gui-progress/2026-06-25T21/popup-repro.png). So the generic RGBA/app-paintable/monitor-positioned popup construction does NOT hang -- notifyd's popup hang is in its SPECIFIC code (the XfceNotifyWindow custom GtkWindow subclass + layout, or per-notification processing like the icon load), the same way the Thunar hang is ThunarPathEntry-SPECIFIC (not generic GtkEntry). Next narrowing step = add the notification ICON load (gtk_icon_theme_load_icon "dialog-information") to the repro -- BLOCKED this turn: the shared target/ was churned by a concurrent session, deleting target/debug/wasm-gui-host, and rebuilding it fails because node_modules (esbuild) are missing (the V8-bridge build script needs `pnpm install`). RECURRING HAZARD (CLAUDE.md): the shared target + node_modules get wiped mid-session. NEXT: pnpm install + cargo build --bin wasm-gui-host, then resume the icon-load narrowing. XU0-XU4 DONE; XU5 platform-fixes done; XU6 = 3 apps render + notifyd daemon/D-Bus works (popup hang now narrowed to notifyd-specific code); XU7 = full session ceiling-gated.
159159
2026-06-25T20n (★ UNIFYING FINDING: the "construction hang" is show_all PERFORMANCE = the X server starving during a CPU-heavy guest -> SAME ROOT as the M8.6/XU7 concurrent-guest ceiling): narrowed the popup-repro further. (1) The RGBA window with a real icon load REACHES DONE -- so neither RGBA nor the icon load hangs. (2) BUT gtk_widget_show_all takes ~14 SECONDS for a trivial window (one label): markers show show_all at 11058ms, the next marker (move) at 24995ms -- a ~14s gap. (3) Disabling the RGBA visual (non-RGBA window) gives the SAME ~14s show_all -> it is NOT RGBA. So the window REALIZE/MAP (the X round-trips + the map/draw the X server must service) is uniformly ~14s slow for a SIMPLE window; a COMPLEX window (notifyd's XfceNotifyWindow + icon image + layout; Thunar's ThunarPathEntry + completion + list-model + icon-renderer) scales that to an effective HANG. REFRAME: the XU5 Thunar hang + the notifyd popup hang are NOT deadlocks -- they are EXTREME show_all SLOWNESS, the X server (a separate guest) being starved of CPU while the GUI guest monopolizes during realize/map. This EXPLAINS why diagnostic g_printerr write-syscalls "unmasked" Thunar (each is a host sync-RPC that yields a turn to the X-server guest) and why the 5 thread/sync repros passed (no window show_all). It is almost certainly the SAME ROOT as the M8.6 concurrent-guest scheduling ceiling + the XU7 full-session ceiling: a CPU-heavy guest starves the X server. So ONE platform/runtime fix -- fairer guest<->X-server scheduling (give the X server CPU turns during a guest's realize/map; e.g. periodic yields or X-socket-driven preemption) -- would unblock XU5 Thunar, the notifyd popup, AND XU7's full session at once. This is a runtime scheduling/TCB item, SURFACED for human sign-off (do not self-approve). Reusable repro: guest-xclient/popup-repro.c (toggle RGBA, step markers). XU0-XU4 DONE; XU5/XU6-popup/XU7 all gated on this ONE surfaced scheduling root.
160+
2026-06-25T20o (★★ ROOT PINNED + CORRECTS the scheduling theory: the construction "hang" is TEXT/font rendering, ~13.4s for ONE GtkLabel -- a platform font-path perf issue, NOT the X server/scheduling): drove the popup-repro to the root. Measurements (render-app.sh, fonts staged, 0 fontconfig errors): X round-trips ~5ms each (xsync-bench: 100 in 516ms); async X events MapNotify 21ms / Expose 28ms (map-bench) -> the wasm X server is FAST both ways. gtk_widget_realize ~42ms (fast). The ~13.4s is entirely inside gtk_widget_show_all AFTER realize. DECISIVE: an EMPTY window (no child) -> show_all ~44ms; ADD ONE gtk_label -> show_all ~13.4s. So the construction slowness is rendering a SINGLE LABEL = the text/font path (pango + harfbuzz + freetype + cairo software raster), pathologically slow in wasm (~13.4s for "Hello notification"). This CORRECTS the prior "X-server starved by the CPU-heavy guest / concurrent-guest scheduling" hypothesis -- the X server is fast; the slowness is GUEST-INTERNAL text rendering. So XU5 Thunar (ThunarPathEntry text) + the notifyd popup (summary/body text) hang because their first text render is this slow (the notifyd sender's SYNCHRONOUS Notify blocks during it); plain windows like the empty repro are instant. This is a PLATFORM-LAYER performance issue (font/text rendering in wasm), constraint #5 -- NOT a TCB decision. Likely a ONE-TIME font/glyph-cache setup (the first GtkLabel triggers fontconfig match + freetype face load + harfbuzz table build + the pango/cairo glyph cache); mousepad/ristretto pay it once then render. NEXT: pin WHICH stage is slow (fontconfig match vs FT_New_Face vs harfbuzz vs cairo_show_glyphs) via a pango-direct micro-bench + a host perf check, then a platform fix (e.g. a prebuilt fontconfig cache, or a faster freetype/pixman config). Repros: guest-xclient/{popup-repro,xsync-bench,map-bench}.c. XU5 Thunar + notifyd popup unblock once text render is fast; XU7 ceiling is SEPARATE (concurrent guests). XU0-XU4 DONE.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* Measure async X EVENT-delivery latency: time from XMapWindow to the MapNotify event. The XSync
2+
* round-trips are fast (~5ms); if THIS is ~seconds, GTK's show_all is slow because the X server's
3+
* pushed events reach the guest with high latency (the event-poll path), not the request/reply path. */
4+
#include <X11/Xlib.h>
5+
#include <stdio.h>
6+
#include <time.h>
7+
static double now_ms(void){ struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return t.tv_sec*1000.0 + t.tv_nsec/1e6; }
8+
int main(void){
9+
Display *d=XOpenDisplay(NULL); if(!d){fprintf(stderr,"MAP-BENCH: no display\n");return 1;}
10+
Window root=DefaultRootWindow(d);
11+
Window w=XCreateSimpleWindow(d,root,0,0,120,80,0,0,0xffffff);
12+
XSelectInput(d,w,StructureNotifyMask|ExposureMask);
13+
fprintf(stderr,"MAP-BENCH: XMapWindow\n");
14+
double t0=now_ms(); XMapWindow(d,w); XFlush(d);
15+
XEvent e; int got_map=0, got_expose=0;
16+
while(!(got_map&&got_expose)){
17+
XNextEvent(d,&e);
18+
if(e.type==MapNotify && !got_map){ got_map=1; fprintf(stderr,"MAP-BENCH: MapNotify after %.1fms\n", now_ms()-t0); }
19+
if(e.type==Expose && !got_expose){ got_expose=1; fprintf(stderr,"MAP-BENCH: Expose after %.1fms\n", now_ms()-t0); }
20+
}
21+
return 0;
22+
}

experiments/wasm-gui/guest-xclient/popup-repro.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ int main(int argc, char **argv){
2121
GdkRectangle geo = {0,0,700,500};
2222
if (mon){ M("monitor_get_geometry"); gdk_monitor_get_geometry(mon, &geo); }
2323
fprintf(stderr, "POPUP-REPRO: geo %dx%d+%d+%d\n", geo.width, geo.height, geo.x, geo.y);
24-
M("label+add"); gtk_container_add(GTK_CONTAINER(w), gtk_label_new("Hello notification, all wasm."));
25-
M("show_all"); gtk_widget_show_all(w);
24+
M("no label (empty window)");
25+
M("realize"); gtk_widget_realize(w); M("realized");
26+
M("show_all"); gtk_widget_show_all(w); M("show_all_done");
2627
M("move"); gtk_window_move(GTK_WINDOW(w), geo.x + geo.width - 320, geo.y + 40);
2728
M("main loop start"); g_timeout_add(6000, quit_cb, NULL); gtk_main();
2829
M("DONE"); return 0;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* Measure the X round-trip latency directly: each XSync(d,False) is one guest->X-server->guest
2+
* round-trip. If each is ~ms it is fine; if ~tens-of-ms, X round-trips are the show_all bottleneck. */
3+
#include <X11/Xlib.h>
4+
#include <stdio.h>
5+
#include <time.h>
6+
static double now_ms(void){ struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return t.tv_sec*1000.0 + t.tv_nsec/1e6; }
7+
int main(void){
8+
Display *d = XOpenDisplay(NULL);
9+
if(!d){ fprintf(stderr,"XSYNC-BENCH: no display\n"); return 1; }
10+
fprintf(stderr,"XSYNC-BENCH: connected\n");
11+
for(int i=0;i<3;i++) XSync(d,False); /* warm up */
12+
int N=100; double t0=now_ms();
13+
for(int i=0;i<N;i++) XSync(d,False);
14+
double dt=now_ms()-t0;
15+
fprintf(stderr,"XSYNC-BENCH: %d round-trips = %.1fms total, %.3fms each\n", N, dt, dt/N);
16+
return 0;
17+
}

experiments/wasm-gui/map-bench-bin

1.23 MB
Binary file not shown.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env bash
2+
# XU3 integrated session: xfwm4 (WM, Greybird) + xfce4-panel (clock/tasklist/systray/separator) + a
3+
# decorated GTK app window, all wasm. Proves the panel runs UNDER the WM and the tasklist shows a real
4+
# window button for the managed app. (applicationsmenu is excluded here -- its populated menu deadlocks,
5+
# tracked separately.) Stack: dbus + xfconfd (serves the xfwm4 + xfce4-panel channels) + xfwm4 +
6+
# xfce4-panel + gtk-hello.
7+
set -uo pipefail
8+
cd "$(dirname "$0")/.."
9+
EXP="$(pwd)"; REPO="$(cd ../.. && pwd)"
10+
HOST="$REPO/target/debug/wasm-gui-host"; SIDECAR="$REPO/target/debug/secure-exec-sidecar"
11+
for f in "$HOST" "$SIDECAR" "$EXP/Xvfb.wasm" "$EXP/dbus-daemon.wasm" "$EXP/xfconfd.wasm" "$EXP/xfwm4.wasm" "$EXP/xfce4-panel.wasm" "$EXP/popup-repro.wasm"; do
12+
[ -f "$f" ] || { echo "MISSING: $f"; exit 1; }
13+
done
14+
15+
FONTS="${VMFONTS:-/tmp/vmfonts}"; LOCALE="${VMLOCALE:-/tmp/vmlocale}"
16+
XFT="${VMXFT:-/tmp/vmxft}"; THEMES="${VMTHEMES:-/tmp/vmthemes}"; WMDATA="${VMXFWM4:-/tmp/vmxfwm4}"
17+
[ -d "$FONTS" ] || bash "$EXP/scripts/prepare-fonts.sh" >/dev/null 2>&1 || true
18+
[ -d "$LOCALE" ] || bash "$EXP/scripts/prepare-locale.sh" "$LOCALE" >/dev/null 2>&1 || true
19+
[ -d "$XFT" ] || bash "$EXP/scripts/prepare-xftfonts.sh" "$XFT" >/dev/null 2>&1 || true
20+
bash "$EXP/scripts/prepare-themes.sh" "$THEMES" >/dev/null 2>&1 || true
21+
bash "$EXP/scripts/prepare-xfwm4.sh" "$WMDATA" >/dev/null 2>&1 || true
22+
23+
# Use SEPARATE vm-trees (each prepare script rm -rf's its own tree); the host MERGES all --vm-tree into
24+
# the VM root, so the panel channel (panel tree) and the xfwm4 channel (dbus tree) land in the same
25+
# xfconf dir without clobbering each other.
26+
# Panel tree (config + stub .so + .desktop); default plugins = clock tasklist systray separator.
27+
SESS=/tmp/vmxu3sess
28+
PLUGINS="${PLUGINS:-clock tasklist systray separator}" bash "$EXP/scripts/prepare-xfce4-panel.sh" "$SESS" >/dev/null 2>&1
29+
# dbus tree (separate) + machine-id + the xfwm4 channel.
30+
FIX=/tmp/vmxu3sess-dbus
31+
bash "$EXP/scripts/prepare-dbus-fixtures.sh" "$FIX" >/dev/null 2>&1
32+
mkdir -p "$FIX/etc" "$FIX/var/lib/dbus"
33+
printf '0123456789abcdef0123456789abcdef\n' > "$FIX/etc/machine-id"
34+
cp -f "$FIX/etc/machine-id" "$FIX/var/lib/dbus/machine-id"
35+
CHDIR="$FIX/root/.config/xfce4/xfconf/xfce-perchannel-xml"; mkdir -p "$CHDIR" "$FIX/root/.cache"
36+
cat > "$CHDIR/xfwm4.xml" <<'XML'
37+
<?xml version="1.0" encoding="UTF-8"?>
38+
<channel name="xfwm4" version="1.0"><property name="general" type="empty">
39+
<property name="theme" type="string" value="Greybird"/>
40+
<property name="title_font" type="string" value="Sans Bold 9"/>
41+
<property name="use_compositing" type="bool" value="false"/></property></channel>
42+
XML
43+
44+
W=800; H=600
45+
FB="$(mktemp /tmp/xu3-sess-fb.XXXXXX.bin)"
46+
OUT="${OUT:-/tmp/xu3-session.log}"
47+
PNG="${PNG:-$HOME/tmp/gui-progress/$(date -u +%Y-%m-%dT%H)/xu3-session.png}"
48+
mkdir -p "$(dirname "$PNG")"
49+
50+
echo "running XU3 session -> fb=$FB png=$PNG log=$OUT"
51+
WM_SETTLE_QUIET_MS=3000 WM_SETTLE_CAP_S=50 APP_SETTLE_MS=4000 \
52+
timeout 200 env -u DISPLAY NO_AT_BRIDGE=1 "$HOST" --xdemo --timeout "${TIMEOUT:-120}" \
53+
--server "$EXP/Xvfb.wasm" \
54+
--dbus "$EXP/dbus-daemon.wasm" \
55+
--dbus-service "$EXP/xfconfd.wasm" \
56+
--client "$EXP/xfwm4.wasm" \
57+
--client "$EXP/popup-repro.wasm" \
58+
--fonts-dir "$FONTS" --locale-dir "$LOCALE" \
59+
--vm-tree "$FIX" --vm-tree "$SESS" --vm-tree "$THEMES" --vm-tree "$WMDATA" --vm-tree "$XFT" \
60+
--fb-out "$FB" --sidecar "$SIDECAR" \
61+
-- :0 -screen 0 ${W}x${H}x24 -nolisten tcp -nolock -listen local -noreset -fbdir /data \
62+
> "$OUT" 2>&1 || true
63+
64+
echo "=== session evidence ==="
65+
grep -aiE "Gtk-ERROR|unreachable|No window manager|exited with code|deadlock|PARKED" "$OUT" | grep -aviE "NETWRITE|NETREAD" | head -6
66+
[ -s "$FB" ] && python3 "$EXP/scripts/fb2png.py" "$FB" "$PNG" "$W" "$H" 2>&1 | tail -1
67+
python3 - "$PNG" <<'PY'
68+
from PIL import Image
69+
import sys, os
70+
p=sys.argv[1]
71+
if not os.path.exists(p): print("XU3 SESSION: no png"); sys.exit(1)
72+
im=Image.open(p).convert("RGB"); W,H=im.size; px=im.load()
73+
# panel band: a near-full-width row of non-bg pixels near the top (y<32).
74+
def nb(x,y): return max(px[x,y])>25
75+
toprow=sum(1 for x in range(0,W,2) if any(nb(x,y) for y in range(0,30)))
76+
# app window: a large white-ish region in the body.
77+
white=[(x,y) for y in range(40,H,3) for x in range(0,W,3) if px[x,y][0]>235 and px[x,y][1]>235 and px[x,y][2]>235]
78+
print(f"panel band top-coverage: {toprow}/{W//2} columns")
79+
if white:
80+
xs=[q[0] for q in white]; ys=[q[1] for q in white]
81+
print(f"app window bbox: x[{min(xs)}..{max(xs)}] y[{min(ys)}..{max(ys)}]")
82+
else:
83+
print("app window: none detected")
84+
ok = toprow > (W//2)*0.7 and bool(white)
85+
print("XU3 SESSION:", "PASS (panel + decorated app under xfwm4)" if ok else "PARTIAL")
86+
PY
1.23 MB
Binary file not shown.

0 commit comments

Comments
 (0)