NSURLConnection timeout

I'm having trouble interrupting NSURLConnection requests in our iPhone app. It seems like this has been happening lately. As soon as he enters this state, he remains in this state. The only resolution seems to kill the application and restart it.

remarks:

  • The main code that NSURLConnection executes has not changed (with the exception of the recently added user agent code).
  • You still can’t find a reproducible case, but timeouts seem to happen after the application sits in the background for a while, especially if it works on 3G (without WiFi).
  • Apache on the server does not register any requests from the client while it is experiencing these timeouts.
  • Some indications that other applications, such as Mail and Safari, are affected (i.e., time out), although not sequentially.
  • 3G coverage is solid where I am, and not rule out a transient problem causing the problem (it is assumed that this is not the case).
  • All requests are sent to our own API server and are calm POST requests.
  • We use our own timeout based on NSTimer due to problems with timeoutInterval and POST requests. I tried to play with increasing timeout value - the problem is still arising.

Other things:

  • The app has recently been converted to ARC.
  • Launching the application in iOS 5.1.1.
  • The app uses the latest versions of UrbanAirship, TestFlight and Flurry SDK.
  • The ARC branch for TouchXML is also used to parse responses.

As you can see below, the code runs in the main thread. I assumed that something was blocking this thread, but the stack traces I see when the application is paused indicates that the main thread is perfect. I believe that NSURLConnection uses its own thread and should be blocked.

#define relnil(v) (v = nil) - (id) initWebRequestController { self = [super init]; if (self) { //setup a queue to execute all web requests on synchronously dispatch_queue_t aQueue = dispatch_queue_create("com.myapp.webqueue", NULL); [self setWebQueue:aQueue]; } return self; } - (void) getStuffFromServer { dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(aQueue, ^{ dispatch_sync([self webQueue], ^{ error_block_t errorBlock = ^(MyAppAPIStatusCode code, NSError * error){ dispatch_async(dispatch_get_main_queue(), ^{ [[self delegate] webRequestController:self didEncounterErrorGettingPointsWithCode:code andOptionalError:error]; }); }; parsing_block_t parsingBlock = ^(CXMLDocument * doc, error_block_t errorHandler){ NSError * error = nil; CXMLNode * node = [doc nodeForXPath:@"apiResult/data/stuff" error:&error]; if (error || !node) { errorHandler(MyAppAPIStatusCodeFailedToParse, error); } else { stuffString = [node stringValue]; } if (stuffString) { dispatch_async(dispatch_get_main_queue(), ^{ [[self delegate] webRequestController:self didFinishGettingStuff:stuffString]; }); } else { errorHandler(MyAppAPIStatusCodeFailedToParse, error); } }; NSURL * url = [[NSURL alloc] initWithString:[NSString stringWithFormat:MyAppURLFormat_MyAppAPI, @"stuff/getStuff"]]; NSMutableURLRequest * urlRequest = [[NSMutableURLRequest alloc] initWithURL:url]; NSMutableDictionary * postDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys: [[NSUserDefaults standardUserDefaults] objectForKey:MyAppKey_Token], @"token", origin, @"from", destination, @"to", transitTypeString, @"mode", time, @"time", nil]; NSString * postString = [WebRequestController httpBodyFromDictionary:postDictionary]; [urlRequest setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]]; [urlRequest setHTTPMethod:@"POST"]; if (urlRequest) { [self performAPIRequest:urlRequest withRequestParameters:postDictionary parsing:parsingBlock errorHandling:errorBlock timeout:kTimeout_Standard]; } else { errorBlock(MyAppAPIStatusCodeInvalidRequest, nil); } relnil(url); relnil(urlRequest); }); }); } - (void) performAPIRequest: (NSMutableURLRequest *) request withRequestParameters: (NSMutableDictionary *) requestParameters parsing: (parsing_block_t) parsingBlock errorHandling: (error_block_t) errorBlock timeout: (NSTimeInterval) timeout { NSAssert([self apiConnection] == nil, @"Requesting before previous request has completed"); NSString * postString = [WebRequestController httpBodyFromDictionary:requestParameters]; [request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]]; NSString * erVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; NSString * erBuildVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; if ([erBuildVersion isEqualToString:erVersion] || [erBuildVersion isEqualToString:@""]) { erBuildVersion = @""; } else { erBuildVersion = [NSString stringWithFormat:@"(%@)", erBuildVersion]; } NSString * iosVersion = [[UIDevice currentDevice] systemVersion]; NSString * userAgent = [NSString stringWithFormat:@"MyApp/%@%@ iOS/%@", erVersion, erBuildVersion, iosVersion]; [request setValue:userAgent forHTTPHeaderField:@"User-Agent"]; [request setTimeoutInterval:(timeout-3.0f)]; dispatch_sync(dispatch_get_main_queue(), ^{ NSURLConnection * urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; if (urlConnection) { [self setApiConnection:urlConnection]; requestParseBlock = [parsingBlock copy]; requestErrorBlock = [errorBlock copy]; NSMutableData * aMutableData = [[NSMutableData alloc] init]; [self setReceivedData:aMutableData]; relnil(aMutableData); [urlConnection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; [urlConnection start]; relnil(urlConnection); NSTimer * aTimer = [NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:@selector(timeoutTimerFired:) userInfo:nil repeats:NO]; [self setTimeoutTimer:aTimer]; } else { errorBlock(MyAppAPIStatusCodeInvalidRequest, nil); } }); //we want the web requests to appear synchronous from outside of this interface while ([self apiConnection] != nil) { [NSThread sleepForTimeInterval:.25]; } } - (void) timeoutTimerFired: (NSTimer *) timer { [[self apiConnection] cancel]; relnil(apiConnection); relnil(receivedData); [self requestErrorBlock](MyAppAPIStatusCodeTimeout, nil); requestErrorBlock = nil; requestParseBlock = nil; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { [self requestErrorBlock](MyAppAPIStatusCodeFailedToConnect, error); relnil(apiConnection); relnil(receivedData); [[self timeoutTimer] invalidate]; relnil(timeoutTimer); requestErrorBlock = nil; requestParseBlock = nil; } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { [receivedData setLength:0]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [receivedData appendData:data]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { MyAppAPIStatusCode status = MyAppAPIStatusCodeFailedToParse; CXMLDocument *doc = [[self receivedData] length] ? [[CXMLDocument alloc] initWithData:[self receivedData] options:0 error:nil] : nil; DLog(@"response:\n%@", doc); if (doc) { NSError * error = nil; CXMLNode * node = [doc nodeForXPath:@"apiResult/result" error:&error]; if (!error && node) { status = [[node stringValue] intValue]; if (status == MyAppAPIStatusCodeOK) { [self requestParseBlock](doc, [self requestErrorBlock]); } else if (status == MyAppAPIStatusCodeTokenMissingInvalidOrExpired) { [Definitions setToken:nil]; [self requestMyAppTokenIfNotPresent]; [Definitions logout]; dispatch_async(dispatch_get_main_queue(), ^{ [[self delegate] webRequestControllerDidRecivedExpiredTokenError:self]; }); } else { [self requestErrorBlock](status, nil); } } else { [self requestErrorBlock](status, nil); } } else { status = MyAppAPIStatusCodeUnexpectedResponse; [self requestErrorBlock](status, nil); } relnil(doc); relnil(apiConnection); relnil(receivedData); [[self timeoutTimer] invalidate]; relnil(timeoutTimer); requestErrorBlock = nil; requestParseBlock = nil; } 

Below are a few screenshots of the queues / threads when the application was in a problem state. Note that I believe that thread 10 is associated with the cancellation made in the previous timeout, although waiting for the mutex is curious. In addition, the bit in thread 22 about Flurry does not appear continuously when a problem occurs in other cases.

Screenshots of the stack trace:

http://img27.imageshack.us/img27/5614/screenshot20120529at236.png http://img198.imageshack.us/img198/5614/screenshot20120529at236.png

Perhaps I am missing something clearly wrong in these tracks, as I am relatively new to iOS / Apple development.

All of this would be much easier to solve if I had a source for NSURLConnection and the code associated with it, but, for example, I accept hits in the dark at this moment.

+5
source share
1 answer

Removing the TestFlight 1.0 SDK seems to fix the problem. TestFlight also confirmed that they are working on a fix. Given that more than a month has passed since the error was originally confirmed by others, I wonder how close we should get the fix.

+4
source

Source: https://habr.com/ru/post/944007/


All Articles