diff --git a/Cargo.toml b/Cargo.toml index a3162d5..9f8f8a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ objc2-system-configuration = { version = "0.3", features = ["SCNetworkConfigurat plist = "1.8" [target.'cfg(target_os = "macos")'.dependencies] +objc2 = { version = "0.6" } objc2-core-wlan = { version = "0.3" } objc2-foundation = { version = "0.3" } diff --git a/src/os/macos/wifi.rs b/src/os/macos/wifi.rs index 510af92..d38efa2 100644 --- a/src/os/macos/wifi.rs +++ b/src/os/macos/wifi.rs @@ -1,14 +1,26 @@ +use objc2::rc::autoreleasepool; use objc2_core_wlan::CWWiFiClient; use objc2_foundation::NSString; /// Returns the macOS Wi-Fi transmit rate in bps for the given interface name. +/// +/// The CoreWLAN calls below produce autoreleased Obj-C/XPC objects +/// (`CWFRequestParameters`, `CWFInterface`, the `CWFXPCRequestProtocolCoreWLAN` +/// proxy). This is called from long-lived threads with no draining autorelease +/// pool (e.g. `netwatch`'s interface monitor re-enumerates on every network +/// event), so without an explicit pool those objects are added to a pool that +/// never drains and accumulate without bound — a steady multi-MB/hour macOS +/// leak. A scoped pool per call frees them immediately. pub(crate) fn get_wifi_transmit_rate(iface_name: &str) -> Option { - let client = unsafe { CWWiFiClient::sharedWiFiClient() }; - let name = NSString::from_str(iface_name); + autoreleasepool(|_pool| { + let client = unsafe { CWWiFiClient::sharedWiFiClient() }; + let name = NSString::from_str(iface_name); - let wifi_iface = unsafe { client.interfaceWithName(Some(&name)) }; - wifi_iface.map(|i| { - let transmit_rate = unsafe { i.transmitRate() }; - return (transmit_rate * 1e6) as u64; + let wifi_iface = unsafe { client.interfaceWithName(Some(&name)) }; + wifi_iface.map(|i| { + let transmit_rate = unsafe { i.transmitRate() }; + (transmit_rate * 1e6) as u64 + }) }) } +