Environment
- markon: 0.14.4 (Tauri v2, macOS)
- macOS: 26.5.1 (25F80), Apple Silicon (arm64)
- Uptime: 7 days (launched 2026-07-01, observed 2026-07-08)
- Usage: tray-resident, settings window hidden; documents opened via HTTP server in system browser
Symptom
markon-gui process physical footprint reaches 1.5G after 7 days of uptime, even though the settings window is hidden and the HTTP server (document viewer) runs in the system browser — not in the Tauri webview.
After restart: 83MB (fresh process).
# After 7 days:
Physical footprint: 1.5G
Physical footprint (peak): 1.5G
# After restart:
Physical footprint: 83.1M
Physical footprint (peak): 116.8M
vmmap Output (after 7 days)
Process: markon-gui [11022]
Path: /Applications/Markon.app/Contents/MacOS/markon-gui
Launch Time: 2026-07-01 10:35:07.440 +0800
OS Version: macOS 26.5.1 (25F80)
Physical footprint: 1.5G
Physical footprint (peak): 1.5G
Idle exit: untracked
VIRTUAL RESIDENT DIRTY SWAPPED VOLATILE NONVOL EMPTY REGION
REGION TYPE SIZE SIZE SIZE SIZE SIZE SIZE SIZE COUNT (non-coalesced)
JS VM Gigacage (reserved) 4.0G 0K 0K 0K 0K 0K 0K 1
MALLOC_SMALL 1.4G 4.6M 4.6M 1.4G 0K 0K 0K 441
MALLOC_LARGE 54.9M 0K 0K 47.2M 0K 0K 0K 12
MALLOC_SMALL (empty) 85.2M 16K 16K 7.4M 0K 0K 0K 34
MALLOC_TINY 4096K 208K 208K 112K 0K 0K 0K 1
IOSurface 94.8M 0K 0K 0K 0K 0K 67.6M 49
WebKit Malloc 64.0M 48K 48K 2.3M 0K 0K 0K 3
WebKit Malloc metadata 128.0M 144K 144K 112K 0K 0K 0K 2
Stack 154.3M 144K 144K 2.0M 0K 0K 0K 77
__TEXT 1.2G 176.2M 0K 0K 0K 0K 0K 1056
__DATA 34.3M 3.0M 360K 846K 0K 0K 0K 977
__DATA_CONST 37.4M 10.9M 80K 3.0M 0K 0K 0K 1033
__OBJC_RO 79.1M 23.6M 0K 0K 0K 0K 0K 1
TOTAL 8.6G 220.5M 8.8M 1.5G 0K 544K 67.7M 7023
TOTAL, minus reserved VM space 4.6G 220.5M 8.8M 1.5G 0K 544K 67.7M 7023
Key observations:
- 1.4G in MALLOC_SMALL, the JavaScriptCore heap region
- 1.4G swapped out — only ~5M resident, so the JS heap is cold and paged to disk
- 441 MALLOC_SMALL regions → extreme heap fragmentation
- JS VM Gigacage 4.0G reserved → JavaScriptCore expanded to its max heap
- 94.8M IOSurface → WebKit compositing layers still held while window hidden
Code Audit (v0.14.4 → v0.15.6 diff)
I compared the full source tree between the two versions and reviewed these potential leak sources in the Tauri webview (settings panel):
① NIC poll loop — crates/gui/src/main.rs:580 (unchanged between versions)
loop {
sleep(Duration::from_secs(3)).await;
let cur = markon_core::net::available_bind_hosts();
if cur == prev { continue; }
prev = cur.clone();
let _ = app_for_watcher.emit("bind-hosts-changed", payload);
}
3-second polling loop emits events to the webview. Not the primary leak (NICs rarely change), but event payloads pass through Tauri IPC into JS context on each emission.
② Settings SPA — crates/gui/ui/index.html (3292 lines)
- No
setInterval calls (only short-lived setTimeout: 100ms–6s)
- No WebSocket, EventSource, or fetch polling
- 3 Tauri event listeners:
bind-hosts-changed, server-status-changed, menu:new-workspace
- No Service Worker, IndexedDB, or localStorage accumulation
- No explicit JS leak found in the settings page code
③ No memory-related fix in v0.15.6
The 100 commits between v0.14.4 and v0.15.6 are focused on: git diff/compare views, workspace collaboration features, TypeScript frontend migration. None mention memory, leak, performance optimization, GC, cleanup, or webview lifecycle.
The NIC poll code, event listeners, and webview setup are identical between versions.
Root Cause (suspected)
This is a systemic issue with Tauri/WKWebView/macOS memory management:
- WKWebView keeps compositing layers even when the window is hidden (IOSurface 94.8M)
- JavaScriptCore GC is non-compacting — freed objects leave holes, fragmented heap grows over days
- JIT code pages are immortal — once compiled, never freed, accumulates with any JS execution
- macOS swap policy — swaps cold pages out instead of pressuring the process to compact its heap
This affects ALL Tauri/WKWebView apps that run as long-lived tray residents.
Suggested Fixes
- Periodic webview reload — call
window.location.reload() on the settings page every N hours (e.g. 12h) when the window is hidden. This tears down and recreates the JS heap.
- Webview process recycle — Tauri-side, destroy and recreate the WKWebView instance periodically.
- Tauri upstream — investigate whether Tauri can expose WKWebView memory pressure APIs or
WebPageProxy::tryClose() for idle webviews.
Environment
Symptom
markon-guiprocess physical footprint reaches 1.5G after 7 days of uptime, even though the settings window is hidden and the HTTP server (document viewer) runs in the system browser — not in the Tauri webview.After restart: 83MB (fresh process).
vmmap Output (after 7 days)
Key observations:
Code Audit (v0.14.4 → v0.15.6 diff)
I compared the full source tree between the two versions and reviewed these potential leak sources in the Tauri webview (settings panel):
① NIC poll loop —
crates/gui/src/main.rs:580(unchanged between versions)3-second polling loop emits events to the webview. Not the primary leak (NICs rarely change), but event payloads pass through Tauri IPC into JS context on each emission.
② Settings SPA —
crates/gui/ui/index.html(3292 lines)setIntervalcalls (only short-livedsetTimeout: 100ms–6s)bind-hosts-changed,server-status-changed,menu:new-workspace③ No memory-related fix in v0.15.6
The 100 commits between v0.14.4 and v0.15.6 are focused on: git diff/compare views, workspace collaboration features, TypeScript frontend migration. None mention memory, leak, performance optimization, GC, cleanup, or webview lifecycle.
The NIC poll code, event listeners, and webview setup are identical between versions.
Root Cause (suspected)
This is a systemic issue with Tauri/WKWebView/macOS memory management:
This affects ALL Tauri/WKWebView apps that run as long-lived tray residents.
Suggested Fixes
window.location.reload()on the settings page every N hours (e.g. 12h) when the window is hidden. This tears down and recreates the JS heap.WebPageProxy::tryClose()for idle webviews.