NWConnection cancel: Do we need to wait for pending receive callbacks to be cancelled?

Hi,

I’m using Network Framework to implement a UDP client via NWConnection, and I’m looking for clarification about the correct and fully safe shutdown procedure, especially regarding resource release.

I have initiated some pending receive calls on the NWConnection (using receive). After calling connection.cancel(), do we need to wait for the cancellation of these pending receives?

As mentioned in this thread, NWConnection retains references to the receive closures and releases them once they are called. If a receive closure holds a reference to the NWConnection itself, do we need to wait for these closures to be called to avoid memory leaks? Or, if there are no such retained references, we don't need to wait for the cancellation of the pending I/O and cancelled state for NWConnection?

Answered by DTS Engineer in 868147022
do we need to wait for the cancellation of these pending receives?

This is very much like my answer on your other thread about listeners: The system doesn’t care, but there might be reasons for you to care.

The connection will hold a strong reference to your receive closure until it calls it. After that, it will release that reference. If the closure references the connection, which is very common, then that forms a retain loop. However, that’s not a problem because, in general, you shouldn’t rely on releasing the last reference to cancel the connection. Rather, you should call cancel() on it. That’s guaranteed to complete all the outstanding receives which breaks any retain loops they might’ve formed.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

do we need to wait for the cancellation of these pending receives?

This is very much like my answer on your other thread about listeners: The system doesn’t care, but there might be reasons for you to care.

The connection will hold a strong reference to your receive closure until it calls it. After that, it will release that reference. If the closure references the connection, which is very common, then that forms a retain loop. However, that’s not a problem because, in general, you shouldn’t rely on releasing the last reference to cancel the connection. Rather, you should call cancel() on it. That’s guaranteed to complete all the outstanding receives which breaks any retain loops they might’ve formed.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks @DTS Engineer,

However, that’s not a problem because, in general, you shouldn’t rely on releasing the last reference to cancel the connection. Rather, you should call cancel() on it. That’s guaranteed to complete all the outstanding receives which breaks any retain loops they might’ve formed.

Some Questions:

  1. When we call .cancel, the teardown begins and all pending receives are supposed to be cancelled. If I suspend the dispatch queue on which async events for the NWConnection are delivered, the cancellation callbacks will not be invoked. In that case, will the reference held by the receive closure remain unreleased, potentially causing a memory leak if the receive closure holds a strong reference to another object?

2 And if the receive closure does not hold a strong reference, then even if the callback is not invoked, it should not be a problem, right?

Let’s start with the easy one:

Does the same behavior apply to the send completion handler as well?

Yes.

If I suspend the dispatch queue on which async events for the NWConnection are delivered

Suspend for how long?

If you suspend the queue indefinitely then you’ll definitely have problems.

You can release a suspended queue. Dispatch will trap in that case.

Given that, you either end up with a leak (if you ‘forget’ the reference to the queue) or abandoned memory (if you maintain that reference). The queue, any blocks on it, and anything they reference will all hang around in memory indefinitely.

It’s fine to suspend and then promptly resume the queue; everything will clean up after the resume.

And if the receive closure does not hold a strong reference … it should not be a problem

No, that’s still a problem, because the queue still keeps a reference to the blocks that are waiting to be run. So, you leak/abandon less memory, but that doesn’t eliminate the problem entirely.

Oh, if you plan use weak references here, you should read my recent rant comments over on Swift Forums. Network framework’s send and receive closures are kinda halfway between the first and second cases that I describe, and thus firmly in the ‘using weak is questionable’ camp.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks @DTS Engineer .

I now understand that the dispatch queue holds a strong reference to the closures, and those closures are only released once they run. If the queue is suspended and the closures never execute, those references are never released and the memory remains retained indefinitely.

In my case, I suspended the dispatch queue only to simulate certain scenarios - I don’t intend to suspend the queue. My main question was whether, after calling .cancel() on an NWConnection, I need to wait for confirmation from the OS that the pending I/O has actually been cancelled. Based on our discussion on the thread, my understanding is that I do not need to wait. Is that correct?

To restate the scenario: If I call .cancel() on the NWConnection and then release my own reference to the dispatch queue (e.g., set it to nil), the NWConnection still holds a strong reference to that queue. So the queue won’t be released until the NWConnection itself completes its teardown.

My understanding is that .cancel() begins tearing down the NWConnection, and even if cancellation of the pending I/O is delayed, the NWConnection will eventually drop its reference to the queue as part of its cleanup, without waiting for any pending send/receive closures to execute. Since the closures themselves do not hold a strong reference back to the NWConnection, the teardown does not wait for those closures to run before releasing its reference to the queue. Right?

my understanding is that I do not need to wait. Is that correct?

I think it’s better to say that “nothing is the system requires you to wait”. You may want to wait for your own reasons, but Network framework doesn’t require it.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

NWConnection cancel: Do we need to wait for pending receive callbacks to be cancelled?
 
 
Q