TCP server socket for localhost gets broken after an app has been running in background for some time

Is there any Apple documentation or any developer experience to help me understand why a listening TCP server socket on iOS device might become broken after long app inactivity and how to detect the case and restore the socket?

The long story:

My app has two components that mostly communicate with each other over TCP with address 127.0.0.1, that is, localhost.

The connection is not open all the time, it gets open only on demand in specific situations, when the client component connects to the other side on localhost or some other service on the Internet.

Localhost server is running all the time without restarts to be always ready to accept connections when needed.

In general, everything works fine and stable for hours of testing.

But when the app has been left idle for some time (10 minutes) and I wake it up to test, my blocking accept()on the server socket starts always failing with ECONNABORTED (errno code 53, message Software caused connection abort), and connect() call (which is non-blocking on the client side and is waiting with select() for state changes) almost immediately returns an error ECONNREFUSED (code 61, message Connection refused) when I retrieve current state with getsockopt



It just makes no sense because

- the server socket on the same device should be listening on 127.0.0.1 all the time. My code did never call close() on the socket nor unbind or break it in any other way. There are no other errors that might suggest the server socket is not bound or not listening anymore. Still, the connecting side always fails with ECONNREFUSED.


- my accept() code is blocking - it waits for connections, while I run the connect() code in a loop with 10 second waiting in between. Clearly, at least one of those connection attempts should succeed, but they do not. The accept() call seems to be blocking as it should - it waits until the connect() code is executed on the client side and only then almost immediately fails with ECONNABORTED (while connect() fails with ECONNREFUSED at the same moment).


Why a valid listening and bound socket should suddenly start refusing connections? Is it something iOS specific? I suspect that iOS might somehow kill listening sockets if an app has been inactive for some time, but shouldn't then the socket handle become invalid and shouldn't accept() return some error immediately instead of waiting for client connection and erroring only after that?


For the server socket I am using FastSocket iOS library, which is just a thin synchronous blocking wrapper around BSD sockets, and everything seems to be pretty standard network programming inside there.


My only hope is some workaround to detect that the server socket was somehow broken and I should close it and recreate with socket(), bind() and listen(). But I have no idea how do I reliably detect this situation in my code because the socket handle seems to be valid and accept is still blocking and waiting for connections, as it should.

At least for now I have implemented a workaround:

if I detect ECONNABORTED 3 times in a row, I restart the server socket fully, and I have to call bind() multimple times because close() seems to not really unbind the socket completely, therefore ios complains that the port is still in use for up to 20 seconds or so.


After socket restart, it works for 10-20 seconds in sleep mode, but then again loses connection and I have to restart it.


Not sure, if that's the right way to do it but I just need that socket to be alive even during sleep.

Is there any Apple documentation or any developer experience to help me understand why a listening TCP server socket on iOS device might become broken after long app inactivity …?

Yes there is. It’s very likely that your listening socket’s resources have been reclaimed by the OS. Technote 2277 Networking and Multitasking has the details here.

My recommendation is… well, my first recommendation is that you not use BSD Sockets for intra-process communication. There are much more efficient ways to do that, and those have the added benefit of being much more reliable. However, I’m going to presume that that ship has already sailed.

If you’re stuck with your current architecture then your best option is to close your listening socket before the app becomes eligible for suspension and then re-open it when that’s no longer the case. It’s hard to offer detailed advice on that front because every app has different requirements, but in the most simple case you can close the listening socket in

-applicationDidEnterBackground:
and re-open it in
-applicationWillEnterForeground:
.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
TCP server socket for localhost gets broken after an app has been running in background for some time
 
 
Q