How to solve timeout problems caused by bad HTTP persistent connection?

Recently, I have been struggling with the HTTP timeout problem. After more than one month of investigation, I am pretty sure that this is caused by bad HTTP persistent connections. Details:

  • This app is for iOS.
  • Most users are running iOS 8.
  • I am using NSURLConnection .
  • iOS 8 has a known keep alive bug , but my problem is different. More specifically, this error causes NSURLErrorNetworkConnectionLost , but my error is NSURLErrorTimedOut . However, I'm not sure if my problem was caused by another iOS 8 error.
  • The behavior of my problem is: after some time of use - after some HTTP requests are successfully sent and corresponding responses are received - one request will call NSURLErrorTimedOut , and all subsequent (not too far from the last reusing a persistent connection) will call NSURLErrorTimedOut .
  • Some working workaround:
    • Kill and restart the application.
    • Turn your WiFi connection on iPhone to use 3G / 4G.
    • Turn on air mode and then turn it off.
  • My analysis: because of the behavior, the problem seems to be caused by a failed persistent connection. All subsequent requests continue to use this persistent connection, so all failure occurs with NSURLErrorTimedOut . From the workaround, we see that they all work because they cause an unsuccessful persistent connection and create a new persistent connection.

My questions:

  • Has anyone else encountered this problem?
  • Is this a known bug in iOS 8?
  • Is this caused by some unconventional server configuration? I do not control the servers, but I know that they are using nginx 1.6.1, and their engineers are working with me to investigate this problem. What information should I ask them?
  • Is there a way to make NSURLConnection not reuse the current persistent connection, but create a new one so that I can work around this problem after I find it in my code?

Update:

I successfully mitigated this issue on iOS 8 using CFNetwork and directly managing the Connection header. However, it seems the problem is getting worse on iOS 9.

Since my hope that Apple will fix it on iOS 9 has broken. I finally released the radar: http://www.openradar.me/22770738 .

If you run into this problem, try duplicating my radar or, better yet, launch your own radar if you have a more reliable reproducible sample.

+7
ios nginx nsurlconnection keep-alive persistent-connection
source share
3 answers

After 2 weeks of research, I can give answers to questions 3 and 4:

  1. nginx Permanent connection timeout is set for 5 s on the server, which should not be the reason. Server engineers have found that these time-out requests are usually accepted and answered. Thus, it is rather a problem with the client side. Since I have minimal reproducible code to exclude my code as a reason, the reason should be in iOS.
  2. The only thing I found is to use CFNetwork . A higher level API, such as the NSURLConnection or NSURLSession Connection header, will be overwritten by the system.
+2
source

Here's the problem, iOS just tries to reuse the connection after the server drops it.

Why CFNetwork SHOULD NOT

About two years ago, I switched to CFNetwork to solve this problem, but I recently discovered that it is not possible to implement SSL binding with the CFNetwork API. So now I am considering the possibility of returning to NSURLSession.

Workaround

After some searching, I found that the system would NOT reuse connections through NSURLSession s, so creating new sessions over a period of time should solve the problem.

But I also found (at least on macOS): every connection made by NSURLSession can last for 180 seconds, and this connection does not close by version or reset the session, so you may need to implement some caching mechanism to avoid creating a large the number of compounds.

Here is a simple mechanism that I am currently using:

 @interface HTTPSession : NSObject @property (nonatomic, strong) NSURLSession * urlSession; @property (nonatomic, assign) CFTimeInterval flushTime; @end + (NSURLSession *)reuseOrCreateSession { static NSMutableArray<HTTPSession *> * sessions = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sessions = [NSMutableArray<HTTPSession *> array]; }); const CFTimeInterval serverTimeoutSeconds = 10; const CFTimeInterval systemTimeoutSeconds = 40; CFTimeInterval now = CFAbsoluteTimeGetCurrent(); HTTPSession * resultSession = nil; for (HTTPSession * session in sessions) { CFTimeInterval lifeTime = now - session.flushTime; if (lifeTime < serverTimeoutSeconds) { resultSession = session; break; } if (lifeTime > systemTimeoutSeconds) { resultSession = session; resultSession.flushTime = now; break; } } if (!resultSession) { resultSession = [HTTPSession new]; NSURLSession * session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; // setup session resultSession.urlSession = session; resultSession.flushTime = now; [sessions addObject:resultSession]; } return resultSession.urlSession; } 
0
source

What if you add a timestamp for the entire URL of your request? I think this will make each request unique, and maybe iOS will establish a new connection every time you send a request (I'm not sure. Need to try)

-one
source

All Articles