Skip to content

macOS: menu bar icon re-render triggers synchronous RunningBoard XPC call; redundant setImage on every SwiftUI re-render #6302

Description

@rachyandco

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:

  1. Run NymVPN on an Intel Mac with error reporting enabled and the menu bar item visible.
  2. Keep the app running across sleep/wake cycles or on a flaky wifi network so the tunnel status changes repeatedly.
  3. 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:

  1. 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.
  2. Optionally enable Sentry app-hang tracking V2 (enableAppHangTrackingV2) so non-fully-blocking hangs like this are classified separately from real freezes.
  3. No user-visible bug to chase beyond that; the stall itself is in runningboardd.

Activity

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