NSURLConnection timing out

He estado teniendo problemas intermitentes con las requestes de NSURLConnection en la aplicación de iPhone. Parece que está ocurriendo más tarde. Una vez que entra en este estado, permanece en ese estado. La única resolución parece estar matando la aplicación y reiniciarla.

Observaciones:

  • El código del núcleo que ejecuta la NSURLConnection no ha cambiado (a exception de un código de agente de usuario personalizado recientemente agregado).
  • Aún no ha encontrado un caso reproducible, pero los times de espera parecen ocurrir después de que la aplicación haya estado en segundo plano, especialmente si se ejecuta en 3G (sin WiFi).
  • Apache en el server no registra requestes del cliente mientras está experimentando estos times de espera.
  • Algunas indicaciones de que otras aplicaciones, como Mail y Safari se ven afectadas (es decir, experimentan times de espera), aunque no siempre.
  • La cobertura 3G es sólida en donde estoy, para no descartar un problema transitorio que desencadene el problema (se supone que no es probable).
  • Todas las requestes se envían a nuestro propio server de API, y son requestes POST tranquilas.
  • Usamos nuestro propio time de espera basado en NSTimer, debido a los problemas con el time de esperaInterval y las requestes POST. Intenté jugar con boost el valor de time de espera: el problema sigue ocurriendo.

Otras cosas variadas:

  • La aplicación se convirtió recientemente a ARC.
  • Ejecución de la aplicación en iOS 5.1.1.
  • La aplicación utiliza las últimas versiones de UrbanAirship, TestFlight y Flurry SDK.
  • También usa la twig ARC de TouchXML para analizar las respuestas.

Como puede ver a continuación, el código se ejecuta en el hilo principal. Supuse que algo está bloqueando en ese hilo, pero los rastros de stack que veo al suspender la aplicación sugieren que el hilo principal está bien. Supongo que NSURLConnection está utilizando su propio hilo y que debe estar bloqueado.

#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(timeoutTimerFinetworking:) 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) timeoutTimerFinetworking: (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 == MyAppAPIStatusCodeTokenMissingInvalidOrExpinetworking) { [Definitions setToken:nil]; [self requestMyAppTokenIfNotPresent]; [Definitions logout]; dispatch_async(dispatch_get_main_queue(), ^{ [[self delegate] webRequestControllerDidRecivedExpinetworkingTokenError: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; } 

Las URL a continuación son algunas capturas de pantalla de las queues / subprocesss cuando la aplicación se encontraba en un estado problemático. Tenga en count que creo que el subprocess 10 está relacionado con la cancelación realizada en el time de espera anterior, aunque la espera mutex es curiosa. Además, el bit en el hilo 22 sobre Flurry no aparece constantemente cuando se experimenta el problema en otras ocasiones.

Captura de pantalla de seguimiento de trazas:

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

Tal vez estoy pasando por alto algo obviamente incorrecto en esos rastros, ya que soy relativamente nuevo en el desarrollo de iOS / Apple.

Todo esto sería mucho más fácil de resolver si tuviera la fuente para NSURLConnection y el código relacionado, pero tal como es, estoy tomando apuñalamientos en la oscuridad en este punto.

La eliminación del TestFlight 1.0 SDK pareció solucionar el problema. TestFlight también confirmó que están trabajando en una solución. Dado que ha pasado más de un mes desde que el error fue originalmente confirmado por otros, me pregunto qué tan cerca estamos de get una solución.