2012-05-31 2 views
5

Ho avuto problemi intermittenti con richieste NSURLConnection scadute nella nostra app per iPhone. Sembra che si stia verificando più tardi. Una volta che entra in questo stato, rimane in quello stato. L'unica soluzione sembra essere uccidere l'app e riavviarla.NSURCollegamento temporizzazione

Osservazioni:

  • Il codice di base che esegue il NSURLConnection non è cambiato (ad eccezione di qualche codice personalizzato user-agent recentemente aggiunto).
  • Devono ancora trovare un caso riproducibile, ma i timeout sembrano verificarsi dopo che l'app è rimasta seduta in background per un po ', in particolare se in esecuzione su 3G (senza WiFi).
  • Apache sul server non registra alcuna richiesta dal client mentre si verificano questi timeout.
  • Alcune indicazioni che altre app, come Mail e Safari, sono interessate (ad esempio, si verificano i timeout), anche se non coerentemente.
  • La copertura 3G è solida dove mi trovo, per non escludere un problema transitorio che causa il problema (presumibilmente non probabile).
  • Tutte le richieste vanno al nostro server API e sono richieste POST riposanti.
  • Utilizziamo il nostro timeout basato su NSTimer, a causa dei problemi con le richieste timeoutInterval e POST. Ho provato a giocare con l'aumento del valore di timeout - il problema si verifica ancora.

Altre cose varie:

  • App è stato recentemente convertito in ARC.
  • Applicazione in esecuzione in iOS 5.1.1.
  • L'app utilizza le ultime versioni di UrbanAirship, TestFlight e Flurry SDK.
  • Utilizzare anche il ramo ARC di TouchXML per analizzare le risposte.

Come si può vedere di seguito, il codice viene eseguito sul thread principale. Presuppone che qualcosa stia bloccando su quel thread, ma le tracce dello stack che vedo quando si sospende l'app suggeriscono che il thread principale va bene. Suppongo che NSURLConnection stia utilizzando una propria discussione e che debba essere bloccata.

#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; 
} 

Gli URL di seguito sono alcuni screenshot delle code/thread quando l'applicazione era in stato problematico. Nota, credo che il thread 10 sia correlato all'annullamento eseguito sul timeout precedente, sebbene l'attesa del mutex sia curiosa. Inoltre, il bit nella discussione 22 su Flurry non appare costantemente quando si verifica il problema in altre occasioni.

screenshots dello stack:

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

Forse sto affaccia qualcosa ovviamente sbagliato in quelle tracce, come io sono relativamente nuovo per lo sviluppo iOS/Apple.

Tutto questo sarebbe molto più semplice da risolvere se avessi il sorgente per NSURLConnection e il codice correlato, ma come è, sto prendendo pugnalate al buio a questo punto.

+0

Stai postando troppe informazioni, ma forse non abbastanza. Qual è il messaggio di errore? – Mundi

+0

Non c'è nessun messaggio di errore - questa è la cosa. NSURLConnection inizia, ma non finisce mai, e il nostro NSTimer inizia a cancellarlo. – mackinra

+0

Dopo aver pensato dappertutto per un problema simile, mi sono imbattuto in questo: http://stackoverflow.com/questions/10149811/why-does-nsurlconnection-fail-to-reach-the-backend Penso che potrebbe essere solo la causa del mio dolore. Proveremo a strappare TestFlight e vedere cosa succede. – mackinra

risposta

4

La rimozione dell'SDK TestFlight 1.0 sembrava risolvere il problema.TestFlight ha anche confermato che stanno lavorando a una correzione. Dato che è passato più di un mese da quando il bug è stato inizialmente confermato da altri, mi chiedo quanto siamo vicini a ottenere una correzione.

+0

Suppongo di aver colpito qualcosa con il mutex nelle tracce dello stack ... che TFRunLoopOperation nella thread 10 è il codice TestFlight. Ulteriori discussioni su questo bug di TestFlight sono disponibili qui: https://github.com/AFNetworking/AFNetworking/issues/307 – mackinra