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?
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"