Clarification on NWListener / NWConnection lifecycle across app backgrounding and suspension

I’m using Apple’s Network framework to implement a UDP client using NWConnection, and I have a question regarding the lifecycle guarantees provided by the Network framework for NWListener and NWConnection when an iOS application transitions to the background and is subsequently suspended.

I was going through this Technical Note TN2277 (specifically the Listening socket section), which describes that if the app has gone into the background and eventually gets suspended, then even though the underlying socket is still active/functional, new connections might be immediately rejected by the kernel.

In the scenario where the system suspends the app and later reclaims the resources from underneath the listening socket, the app will no longer be able to listen for incoming connections. On resumption, it might be possible that the app is not even notified that the underlying resource has been reclaimed.

Relevant Quotes from the Tech note:

Once your app goes into the background, it may be suspended. Once it is suspended, it's unable to properly process incoming connections on the listening socket. However, the socket is still active as far as the kernel is concerned. If a client connects to the socket, the kernel will accept the connection but your app won't communicate over it. Eventually the client will give up, but that might take a while. Thus, it's better to close the listening socket when going into the background, which will cause incoming connections to be immediately rejected by the kernel.

If the system suspends your app and then, later on, reclaims the resources from underneath your listening socket, your app will no longer be listening for connections, even after it has been resumed. The app may or may not be notified of this, depending on how it manages the listening socket. It's generally easier to avoid this problem entirely by closing the listening socket when the app is in the background.

Does the above hold true for Network Framework UDP sockets, or is the stateUpdateHandler of the corresponding NWListener executed on resumption, indicating that the socket has been reclaimed and the state is either cancelled/failed (non-recoverable) or waiting (recoverable)?

If yes, should the app close the NWListener when going into background since it might not be able to determine whether the underlying socket resource has been reclaimed or not on resumption?

Additionally, for NWConnection client/accepted-client sockets, does the same semantics apply?

Answered by DTS Engineer in 900470022

Yeah, I really need to update TN2277, because while its core message is still valid a lot of the details have changed:

  • Network framework may or may not use BSD Sockets internally, depending on a bunch of factors. In many common cases it uses the user space networking stack, so there are no sockets to be found.
  • Given that, the terminology used by TN2277 isn’t valid any more. Rather than say that a socket has its resources reclaimed, I now say that the connection or listener has been defuncted it (which reflects the jargon used by the kernel).
  • Modern systems are a lot more agressive about defuncting than old systems. The technote suggests that you can force a defunct by locking the screen. That’s no longer necessary. The system will defunct stuff immediately on suspending your app.

In terms of Network framework versus BSD Sockets, Network framework has an asynchrony model so it can tell you about stuff going defunct. It does that by transitioning the connection to the .failed(…) state, with a corresponding call to your state update handler.

The one oddity that I’m aware of is that for listeners you’ll see different behaviour based on whether you have the listener registered with Bonjour. If you do, the listener will fail on suspend because the IPC connection to mDNSResponder gets defuncted. But if you don’t register with Bonjour, it seems that the listener simply stops listening.

Which brings me back to the original advice in TN2277:

  • For connections, you get to decide whether it makes sense to proactively close the connection when you become eligible for suspension.
  • If you don’t, you may end up getting defuncted anyway, so be prepared for that.
  • For listeners, it’s best to stop your listener when you become eligible for suspension. That gives you consistent behaviour in all cases:
    • If you’re registered with Bonjour, the service gets unregistered.
    • Incoming connections get rejected by the kernel; without that, they can end up just hanging.

And remember that this is all about becoming eligible for suspension, rather than being in the background. If you’re in the background but not suspended — for example, an audio streaming app that’s currently playing audio — then none of this is a concern.

Share and Enjoy

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

Yeah, I really need to update TN2277, because while its core message is still valid a lot of the details have changed:

  • Network framework may or may not use BSD Sockets internally, depending on a bunch of factors. In many common cases it uses the user space networking stack, so there are no sockets to be found.
  • Given that, the terminology used by TN2277 isn’t valid any more. Rather than say that a socket has its resources reclaimed, I now say that the connection or listener has been defuncted it (which reflects the jargon used by the kernel).
  • Modern systems are a lot more agressive about defuncting than old systems. The technote suggests that you can force a defunct by locking the screen. That’s no longer necessary. The system will defunct stuff immediately on suspending your app.

In terms of Network framework versus BSD Sockets, Network framework has an asynchrony model so it can tell you about stuff going defunct. It does that by transitioning the connection to the .failed(…) state, with a corresponding call to your state update handler.

The one oddity that I’m aware of is that for listeners you’ll see different behaviour based on whether you have the listener registered with Bonjour. If you do, the listener will fail on suspend because the IPC connection to mDNSResponder gets defuncted. But if you don’t register with Bonjour, it seems that the listener simply stops listening.

Which brings me back to the original advice in TN2277:

  • For connections, you get to decide whether it makes sense to proactively close the connection when you become eligible for suspension.
  • If you don’t, you may end up getting defuncted anyway, so be prepared for that.
  • For listeners, it’s best to stop your listener when you become eligible for suspension. That gives you consistent behaviour in all cases:
    • If you’re registered with Bonjour, the service gets unregistered.
    • Incoming connections get rejected by the kernel; without that, they can end up just hanging.

And remember that this is all about becoming eligible for suspension, rather than being in the background. If you’re in the background but not suspended — for example, an audio streaming app that’s currently playing audio — then none of this is a concern.

Share and Enjoy

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

Thanks @DTS Engineer .

So, for NWConnection (UDP/TCP), when the app goes into the suspended state, it would potentially be defuncted, and upon resumption the stateUpdateHandler would deliver a .failed state. In contrast, for an NWListener (UDP/TCP) that is not registered with Bonjour, it might simply stop listening and become stale, and the application would not receive any notification in this case.

Since it can stop listening in this scenario, the recommendation is to proactively close the listener before the app becomes eligible for suspension.

Is this a fair understanding?

Additionally, does the same semantic apply to the dispatch queue? For example, let's say I have created a serial dispatch queue (which in turn targets a global queue). When my application goes into the suspended state, can the dispatch queue also be defuncted? If yes, is there any notification mechanism that lets the application know that the dispatch queue has been defuncted?

Clarification on NWListener / NWConnection lifecycle across app backgrounding and suspension
 
 
Q