Software (please complete the following information):
- Operating system: macOS 15.7.9 (24G830), MacBookAir9,1 (Intel)
- NymVPN version: 2026.11.0 (808); menu bar code unchanged on develop
Describe the bug
Sentry reports App Hanging: App hanging for at least 2000 ms on macOS. The main thread is blocked in a synchronous XPC call to runningboardd that AppKit makes as a side effect of our menu bar icon being updated. The stall is in the OS, but the app triggers the call more often than necessary.
Symbolicated main thread (bottom to top):
-[NSStatusBarButton setImage:] ← SwiftUI MenuBarExtra label re-render
-[NSStatusItem _adjustLength]
-[NSView setNeedsDisplay:]
-[NSNotificationCenter postNotificationName:object:userInfo:]
-[NSObject(NSDelayedPerforming) performSelector:withObject:afterDelay:inModes:]
_NSSwiftProcessInfo.beginActivity
-[_NSActivityAssertion _initWithActivityOptions:reason:expirationHandler:]
rbs_acquire_appnap_assertion
-[RBSConnection acquireAssertion:error:]
xpc_connection_send_message_with_reply_sync ← blocked here > 2 s
Every frame above the app binary is Apple framework code. RunningBoard was slow to answer (typical on Intel Macs under pressure or right after wake).
Context: the process had been running for 5 days (app_start_time 2026-09-01). Breadcrumbs show wifi connectivity flapping none/wifi ten times in a row. Each flap changes tunnel status, each status change updates the menu bar icon, and each icon update pays the RunningBoard round trip. This event is the flap where RunningBoard stalled.
App-side amplifier: in NymVPNDaemon/NymVPNDaemonApp.swift the MenuBarExtra label is Image(nsImage: menuBarNSImage) where menuBarNSImage is a computed property that builds a new NSImage on every body evaluation. SwiftUI therefore calls setImage: on the status bar on every re-render, not only on actual state transitions.
Sentry: nym-vpn-ios issue 7714097128.
To Reproduce
Steps to reproduce the behavior:
- Run NymVPN on an Intel Mac with error reporting enabled and the menu bar item visible.
- Keep the app running across sleep/wake cycles or on a flaky wifi network so the tunnel status changes repeatedly.
- When
runningboardd is slow (post-wake, memory pressure), an App Hanging event is sent. Hard to reproduce deterministically; happens opportunistically in the field.
Expected behavior
Status-bar image updates should happen only when the icon actually changes, so the RunningBoard round trip is paid once per real state transition. Hangs caused by system XPC stalls should be distinguishable from real freezes in Sentry.
Screenshots
N/A. Sentry stack trace above.
Additional context
Recommended changes:
- Cache the five menu bar
NSImage instances once (or store the NSImage in @State and update it inside updateImageName(with:)), so the MenuBarExtra label only produces a new image when menuBarImageName changes. Removes redundant setImage: calls on unrelated re-renders.
- Optionally enable Sentry app-hang tracking V2 (
enableAppHangTrackingV2) so non-fully-blocking hangs like this are classified separately from real freezes.
- No user-visible bug to chase beyond that; the stall itself is in
runningboardd.
Software (please complete the following information):
Describe the bug
Sentry reports
App Hanging: App hanging for at least 2000 mson macOS. The main thread is blocked in a synchronous XPC call torunningboarddthat AppKit makes as a side effect of our menu bar icon being updated. The stall is in the OS, but the app triggers the call more often than necessary.Symbolicated main thread (bottom to top):
Every frame above the app binary is Apple framework code. RunningBoard was slow to answer (typical on Intel Macs under pressure or right after wake).
Context: the process had been running for 5 days (app_start_time 2026-09-01). Breadcrumbs show wifi connectivity flapping
none/wifiten times in a row. Each flap changes tunnel status, each status change updates the menu bar icon, and each icon update pays the RunningBoard round trip. This event is the flap where RunningBoard stalled.App-side amplifier: in
NymVPNDaemon/NymVPNDaemonApp.swifttheMenuBarExtralabel isImage(nsImage: menuBarNSImage)wheremenuBarNSImageis a computed property that builds a newNSImageon every body evaluation. SwiftUI therefore callssetImage:on the status bar on every re-render, not only on actual state transitions.Sentry: nym-vpn-ios issue 7714097128.
To Reproduce
Steps to reproduce the behavior:
runningboarddis slow (post-wake, memory pressure), anApp Hangingevent is sent. Hard to reproduce deterministically; happens opportunistically in the field.Expected behavior
Status-bar image updates should happen only when the icon actually changes, so the RunningBoard round trip is paid once per real state transition. Hangs caused by system XPC stalls should be distinguishable from real freezes in Sentry.
Screenshots
N/A. Sentry stack trace above.
Additional context
Recommended changes:
NSImageinstances once (or store theNSImagein@Stateand update it insideupdateImageName(with:)), so theMenuBarExtralabel only produces a new image whenmenuBarImageNamechanges. Removes redundantsetImage:calls on unrelated re-renders.enableAppHangTrackingV2) so non-fully-blocking hangs like this are classified separately from real freezes.runningboardd.