Once started, NWPathMonitor appears to be kept alive until cancelled, but is this documented?

NWPathMonitor appears to retain itself (or is retained by some internal infrastructure) once it has been started until cancelled. This seems like it can lead to memory leaks if the references to to the monitor are dropped. Is this behavior documented anywhere?

func nwpm_self_retain() {
    weak var weakRef: NWPathMonitor?
    autoreleasepool {
        let monitor: NWPathMonitor = NWPathMonitor()
        weakRef = monitor
        monitor.start(queue: .main)
        // monitor.cancel() // assertion fails unless this is called
    }

    assert(weakRef == nil)
}
nwpm_self_retain()
Answered by DTS Engineer in 870304022

NWPathMonitor appears to retain itself (or is retained by some internal infrastructure) once it has been started until cancelled.

Basically, yes. Start is registering a handler that's receiving events from the system, which, conceptually, ends up retaining the NWPathMonitor so that the handler has a valid target.

This seems like it can lead to memory leaks if the references to the monitor are dropped.

I haven't specifically tested it, but yes, that's certainly possible. You should cancel every NWPathMonitor that you start.

Having said that, it's also possible that your test isn't entirely valid. The "start()" process is an asynchronous action, so it's possible NWPathMonitor is temporarily holding a reference which it will drop once start later completes. If you wanted to test this properly, then you'd need to set a cancellation handler and see if that was called.

Is this behavior documented anywhere?

I'm not sure. I suspect it was documented when the API was initially released, but the network framework has gone through multiple revisions of its Swift binding, and it's possible this detail got dropped during that. However, it's not mentioned in the header doc, so it's possible it was never formally documented.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Accepted Answer

NWPathMonitor appears to retain itself (or is retained by some internal infrastructure) once it has been started until cancelled.

Basically, yes. Start is registering a handler that's receiving events from the system, which, conceptually, ends up retaining the NWPathMonitor so that the handler has a valid target.

This seems like it can lead to memory leaks if the references to the monitor are dropped.

I haven't specifically tested it, but yes, that's certainly possible. You should cancel every NWPathMonitor that you start.

Having said that, it's also possible that your test isn't entirely valid. The "start()" process is an asynchronous action, so it's possible NWPathMonitor is temporarily holding a reference which it will drop once start later completes. If you wanted to test this properly, then you'd need to set a cancellation handler and see if that was called.

Is this behavior documented anywhere?

I'm not sure. I suspect it was documented when the API was initially released, but the network framework has gone through multiple revisions of its Swift binding, and it's possible this detail got dropped during that. However, it's not mentioned in the header doc, so it's possible it was never formally documented.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thank you Kevin. It was pointed out to me that there is documentation implying that the monitor must be explicitly cancelled when it is finished being used in the generated Swift header for the type:

    /// Start the path monitor and set a queue on which path updates
    /// will be delivered.
    /// Start should only be called once on a monitor, and multiple calls to start will
    /// be ignored.
    /// Once started, the path monitor must be explicitly cancelled when it is no longer needed.
    final public func start(queue: DispatchQueue)

For whatever reason that information isn't in any of the typical documentation locations I checked (Xcode's Developer Documentation, online documentation, quick help, objc headers, etc).

Having said that, it's also possible that your test isn't entirely valid. The "start()" process is an asynchronous action, so it's possible NWPathMonitor is temporarily holding a reference which it will drop once start later completes. If you wanted to test this properly, then you'd need to set a cancellation handler and see if that was called.

Out of curiosity, is it possible to set a cancellation handler for the class-based API using an NWPathMonitor instance? i.e. is there a way to derive a nw_path_monitor_t from such an instance?

Out of curiosity, is it possible to set a cancellation handler for the class-based API using an NWPathMonitor instance? i.e. is there a way to derive a nw_path_monitor_t from such an instance?

No, I don't think so. The development path from the original C library to the "modern" Swift syntax was a bit messy, and I don't think we really made any effort to provide a path between the two. They were intended to be direct replacements for each other, so there wasn't really any reason to switch between the two.

A few other suggestions/notes that may be useful to be aware of:

  • Some of the C APIs’ design has more to do with API symmetry than useful functionality. Case in point, I have a very difficult time thinking of any reason why you'd actually "need" a cancellation handler for NWPathMonitor. It's a lightweight object (notably, it doesn't do any actual "networking”) and I can't really think of any reason why you'd care about when it ACTUALLY "finished" cancelling.

  • The primary reason the C API exists is for Apple's own use, not external developers. One of the unique issues we have is that some of our frameworks and daemon's cannot use higher-level languages (Swift/ObjC) or frameworks (Foundation) because those components are actually dependencies of those higher-level components. In concrete terms, the Network framework cannot use Foundation because the Network framework is what NSURLSession needs to use to implement "itself".

Note that the issue above explains the design of a number of "core" lower-level APIs like GCD and CoreFoundation. They weren't designed solely as "the best API possible" but were actually designed as "the best API possible that uses as little as possible from the rest of the system". That allows as much of the system as possible to use the API.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Once started, NWPathMonitor appears to be kept alive until cancelled, but is this documented?
 
 
Q