fix(macos): drain autoreleased CoreWLAN objects in get_wifi_transmit_rate#165
Merged
shellrow merged 1 commit intoJun 3, 2026
Merged
Conversation
get_wifi_transmit_rate queries CoreWLAN (CWWiFiClient/interfaceWithName/ transmitRate), which produces autoreleased Obj-C and XPC objects. netdev is called from long-lived threads with no run loop or draining autorelease pool — e.g. netwatch's interface monitor re-enumerates on every network event — so those objects are added to a top-level pool that never drains and accumulate without bound. On a live macOS process this leaked ~300K CoreWLAN objects in 7h (~18 MB/h RSS climb); Linux was unaffected since its path uses sysfs. Wrapping the query in objc2::rc::autoreleasepool drains the temporaries per call. Keeps the object count flat; adds objc2 to the macOS deps for the pool API.
Owner
|
Thanks for the great fix and detailed investigation. The change looks good. The only CI failure is a rustfmt issue, which I'll take care of on my side before the next release. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On macOS,
get_wifi_transmit_rate(src/os/macos/wifi.rs, called frominterface()to populatetransmit_speed) queries CoreWLAN —CWWiFiClient::sharedWiFiClient()→interfaceWithName→transmitRate—without an enclosing autorelease pool. Those calls produce autoreleased
Obj-C / XPC objects.
When
netdevis driven from a long-lived thread that has no run loop and nodraining autorelease pool, those objects are added to a top-level pool that
never drains, so they accumulate without bound. This happens in practice via
netwatch(and thusiroh), whoseinterface monitor calls
get_interfaces()on every network change.Evidence
heap <pid>on a ~7h process usingnetdevthroughnetwatch— CoreWLAN /CoreWiFi objects dominate the heap (~73 MB):
CWFRequestParameters(CoreWiFi)__NSXPCInterfaceProxy_CWFXPCRequestProtocolCoreWLANCWFInterface(CoreWiFi)RSS climbed ~18 MB/h, linear, no plateau. Linux hosts running the same software
were flat (their path is sysfs, no CoreWLAN), confirming the source.
Fix
Wrap the body in
objc2::rc::autoreleasepoolso each call drains itsautoreleased objects immediately (adds
objc2to the macOS deps for the poolAPI). Behavior-preserving — the returned rate is unchanged; only the
autoreleased temporaries are freed per call.
Validation
Same software, only the patch differs:
CWFRequestParameters→ 298,213 over 7h (~18 MB/h RSS climb).CWFRequestParameterspinned at 3, RSS flat (held 30+ min on amulti-host fleet, plus a separate long local run).
Reproducing
A unit test doesn't reproduce it — a synchronous in-process loop frees the
objects promptly (measured 0 growth over 100k calls); the leak is specific to
the async, threaded, over-time CoreWLAN XPC traffic on a pool-less long-lived
thread. To see it by hand on macOS: run a long-lived process that drives
netdev::get_interfaces()from a background monitor (e.g. vianetwatch/iroh) and watchheap <pid> | grep CWFRequestParametersclimb; with the patchit stays at a small constant.