2010-12-27 16 views
9

Estoy trabajando en una aplicación de iPhone que implica cargar fotos completas desde la cámara (generalmente entre 1.5 a 2.0 MB cada una) y sus miniaturas (mucho más pequeño) a Amazon S3.POSIX error 12 ("No se puede asignar memoria") al cargar archivos desde un iPhone

Las miniaturas siempre cargar con éxito, pero a veces las imágenes completas no, y cuando fallan, fallan con código de error POSIX 12, también conocido como ENOMEM. Sin embargo, agregué código de depuración para imprimir la cantidad de memoria libre cuando ocurre el error, y siempre hay bastante libre, generalmente más de 100 MB.

Además, el error aparece con mayor frecuencia cuando la carga ocurre a través de 3G y menos cuando está a través de wifi, lo que parece extraño, ya que la solicitud no está descargando mucho y el archivo cargado ya está en la memoria (I también he intentado transmitirlo desde el disco sin ninguna mejora).

He intentado cargar el archivo usando NSURLConnection, las funciones Foundation CFHTTP * y la biblioteca ASIHTTPRequest, pero independientemente, el error ocurre con la misma frecuencia. Aún más extraño, todo lo que Google ha revelado es que los usuarios finales a veces obtienen el código de error 12 de Safari; no he visto a ningún desarrollador de iOS mencionarlo. Estoy trabajando con una base de código heredada, por lo que es posible que haya algún problema con ella, pero ni siquiera estoy seguro de qué buscar. ¡Cualquier idea sería muy apreciada!

+0

¿cómo está volviendo ENOMEM la llamada? –

+0

Cuando estaba usando NSURLConnection, obtenía el error como parte del método de delegado didFailWithError - un NSError con el dominio de error POSIX, código de error 12 y la descripción localizada "No se puede asignar memoria". Cuando utilicé CFHTTPMessageRef, fallaba cuando llamaba a CFReadStreamRead() (función devuelta -1), y luego errno era igual a 12. –

+0

Veo el mismo problema al intentar cargar archivos con Google Docs API en 3G. Una publicación en la API de Dropbox menciona un problema similar: http://forums.dropbox.com/topic.php?id=25351. Parece suceder en conexiones lentas (3G) con archivos grandes. – Kamchatka

Respuesta

2

La única manera pude evitar este problema, es usar sockets directamente y formar el encabezado HTTP manualmente. Así que mi código cargando en la actualidad se ve así:

- (void)socketClose 
{ 
    [_inputStream setDelegate:nil]; 
    [_inputStream close]; 
    [_inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
    SCR_RELEASE_SAFELY(_inputStream); 

    [_outputStream setDelegate:nil]; 
    [_outputStream close]; 
    [_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
    SCR_RELEASE_SAFELY(_outputStream); 

    SCR_RELEASE_SAFELY(_headerBuffer); 
} 

- (void)sendRequest 
{ 
    [self socketClose]; 
    SCR_RELEASE_SAFELY(_headerBuffer); 

    if (!_shouldCancel) 
    { 
     NSString *httpMessage = [NSString stringWithFormat:@"POST upload.php HTTP/1.1\r\n" 
           "Host:" 
#ifndef TESTBED 
           " %@" 
#endif 
           "\r\n" 
           "User-Agent: MyApp/3.0.0 CFNetwork/534 Darwin/10.7.0\r\n" 
           "Content-Length: %d\r\n" 
           "Accept: */*\r\n" 
           "Accept-Language: en-us\r\n" 
           "Accept-Encoding: gzip, deflate\r\n" 
           "Content-Type: application/x-www-form-urlencoded\r\n" 
           "Connection: keep-alive\r\n\r\n" 
           "data=" 
#ifndef TESTBED 
           , [self.serverUrl host] 
#endif 
           , _bytesToUpload]; 

     NSString *key = @"data="; 
     NSData *keyData = [key dataUsingEncoding:NSASCIIStringEncoding]; 
     _bytesToUpload -= [keyData length]; 
     _bytesToUpload = MAX(0, _bytesToUpload); 

     _headerBuffer = [[NSMutableData alloc] initWithData:[httpMessage dataUsingEncoding:NSUTF8StringEncoding]]; 

     _writtenDataBytes = 0; 

     CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault 
              , (CFStringRef)[self.serverUrl host] 
#ifdef TESTBED 
              , 8888 
#else 
              , 80 
#endif 
              , (CFReadStreamRef *)(&_inputStream) 
              , (CFWriteStreamRef *)(&_outputStream)); 

     [_inputStream setDelegate:self]; 
     [_outputStream setDelegate:self]; 

     [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 
     [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 

     [_inputStream open]; 
     [_outputStream open]; 
    } 
} 

- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent 
{ 
    if (_outputStream == theStream) 
    { 
     switch (streamEvent) 
     { 
      case NSStreamEventOpenCompleted: 
      { 
       [self regenerateTimeoutTimer]; 
       break; 
      } 
      case NSStreamEventHasSpaceAvailable: 
      { 
       SCR_RELEASE_TIMER(_timeoutTimer); 
       NSInteger length = _headerBuffer.length; 

       if (length > 0) 
       { 
        NSInteger written = [_outputStream write:(const uint8_t *)[_headerBuffer bytes] maxLength:length]; 
        NSInteger rest = length - written; 

        if (rest > 0) 
        { 
         memmove([_headerBuffer mutableBytes], (const uint8_t *)[_headerBuffer mutableBytes] + written, rest); 
        } 

        [_headerBuffer setLength:rest]; 
       } 
       else 
       { 
        const uint8_t *dataBytes = [_data bytes]; 

        while ([_outputStream hasSpaceAvailable] && (_writtenDataBytes < _bytesToUpload)) 
        { 
         NSInteger written = [_outputStream write:dataBytes 
                 maxLength:MIN(_dataLength, _bytesToUpload - _writtenDataBytes)]; 

         if (written > 0) 
         { 
          _writtenDataBytes += written; 
         } 
        } 
       } 

       [self regenerateTimeoutTimer]; 

       break; 
      } 
      case NSStreamEventErrorOccurred: 
      { 
       SCR_RELEASE_TIMER(_timeoutTimer); 
       [self reportError:[theStream streamError]];     
       break; 
      } 
      case NSStreamEventEndEncountered: 
      { 
       SCR_RELEASE_TIMER(_timeoutTimer); 
       [self socketClose]; 
       break; 
      } 
     } 
    } 
    else if (_inputStream == theStream) 
    { 
     switch (streamEvent) 
     { 
      case NSStreamEventHasBytesAvailable: 
      { 
       SCR_RELEASE_TIMER(_timeoutTimer); 

       /* Read server response here if you wish */ 

       [self socketClose]; 

       break; 
      } 
      case NSStreamEventErrorOccurred: 
      { 
       SCR_RELEASE_TIMER(_timeoutTimer); 
       [self reportError:[theStream streamError]]; 
       break; 
      } 
      case NSStreamEventEndEncountered: 
      { 
       SCR_RELEASE_TIMER(_timeoutTimer); 
       [self socketClose]; 
       break; 
      } 
     } 
    } 
} 

Aunque ASIHTTPRequest podría trabajar aquí, hemos decidido alejarse de dichas dependencias, tanto con el fin de obtener un rendimiento y mantener todo bajo nuestro propio control de precisión. Puede usar la herramienta Wireshark para depurar este tipo de cosas.

+0

¿Qué hace tu regenerateTimeoutTimer? Llamada [self stream: theStream handleEvent: streamEvent] y haz antes de eso ...? – JOM

+0

regenerateTimeoutTimer simplemente restablece el temporizador que uso para determinar cuándo se agotó el tiempo de espera de la solicitud. El sentido aquí es si tenemos incluso algunos bytes, estamos empezando a esperar de nuevo. Pero si no recibimos más bytes en el período de tiempo especificado (el tiempo que desee, configurable), nuestra clase genera una excepción de tiempo de espera personalizado y, por lo tanto, no esperamos por siempre (a veces ocurre en errores de red). así que tome como solución). –

+0

Después de más tiempo trabajando en esto y viendo el error "No se puede asignar memoria", llegué al hecho de que, en algunas circunstancias, puede aparecer incluso en el código de ejemplo anterior. La clave es abrir el socket correctamente. Se muestra aquí: https://developer.apple.com/library/ios/#samplecode/SimpleURLConnections/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009245 –

1

La clave para evitar este problema es cargar el archivo mediante una secuencia. Al utilizar NSMutableURLRequest, esto se puede lograr usando algo similar a lo siguiente:

NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url]; 
[request setHTTPBodyStream:[NSInputStream inputStreamWithFileAtPath:filePath]]; 

Al utilizar ASIHTTPRequest, la transmisión de un archivo se haya cumplido con esto:

ASIHTTPRequest* request = [ASIHTTPRequest requestWithURL:url]; 
[request setPostBodyFilePath:filePath]; 
rRequest.shouldStreamPostDataFromDisk = YES; 
+0

No olvides establecer el método de solicitud en POST o PUT. –

+0

De hecho, probé esto. Parece que es el camino a seguir con cargas de archivos grandes, en cualquier caso, y puede haber disminuido la frecuencia de errores ENOMEM, pero todavía ocurren. Aún así, gracias por señalar eso, es una buena técnica. –

1

Han resuelto este error al usar la operación para solicitud (NSMutableUrlConnection) con @autorelease{} para la función principal. NSPOXIS aparece solo algunas veces.

- (void)main 
NSURLConnection* connection; 
    @autoreleasepool //urgently needed for 3G upload 
    { 

     self.currentRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"test.php"]]; 
     [self.currentRequest setHTTPMethod:@"PUT"]; 

     [self.currentRequest setHTTPBody:self.data];//inpustStream doesn't work 

     connection = [NSURLConnection connectionWithRequest:self.currentRequest delegate:self]; 
     [connection start]; 

    }//end autorelease pool 

     do 
     { 

      [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]; 
      if ([self isCancelled]) 
      { 
       connection   = nil; 
       isFailed = YES; 
       break; 
      } 
      self.status(statusUpdateMessage); 
     } 
     while (!isFailed && !isCompleted); 
     [timer invalidate];//test 
     timer = nil; 

//corresponding of status via blocks 
     self.completed(!isFailed); 
     self.status(isFailed ? errorMessage : @"Completed"); 
     if (isFailed) 
     { 
      self.failed(errorMessage != nil ? errorMessage : @"Undefined error"); 
     } 

     self.data = nil; 
     self.currentRequest = nil; 

     connection = nil; 

} 
Cuestiones relacionadas