2014-09-08 6 views
6

Ich habe eine Methode, die dem Tee-Dienstprogramm ähnelt. Es empfängt eine Benachrichtigung, dass Daten in einer Pipe gelesen wurden, und schreibt diese Daten dann in eine oder mehrere Pipes (verbunden mit untergeordneten Anwendungen). Wenn eine untergeordnete App abstürzt, dann ist diese Pipe defekt, und ich bekomme natürlich eine Ausnahme, die dann in einem @try ... @ catch-Block behandelt wird.Warum funktioniert @try ... @ fangen nicht mit - [NSFileHandle writeData]?

Dies funktioniert die meiste Zeit. Was mich irritiert, ist, dass die Ausnahme die App gelegentlich vollständig mit einer nicht abgefangenen Ausnahme abstürzt und auf die writeData-Zeile zeigt. Ich war nicht in der Lage herauszufinden, auf was das Muster steht, wenn es abstürzt, aber warum sollte es nie gefangen werden? (Hinweis: Dies ist nicht im Debugger ausgeführt wird.)

Hier ist der Code:

//in setup: 
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tee:) name:NSFileHandleReadCompletionNotification object:fileHandle]; 

-(void)tee:(NSNotification *)notification 
{ 
// NSLog(@"Got read for tee "); 

NSData *readData = notification.userInfo[NSFileHandleNotificationDataItem]; 
totalDataRead += readData.length; 
// NSLog(@"Total Data Read %ld",totalDataRead); 
NSArray *pipes = [teeBranches objectForKey:notification.object]; 

if (readData.length) { 
    for (NSPipe *pipe in pipes { 
      @try { 
       [[pipe fileHandleForWriting] writeData:readData]; 
      } 
      @catch (NSException *exception) { 
       NSLog(@"download write fileHandleForWriting fail: %@", exception.reason); 
       if (!_download.isCanceled) { 
        [_download rescheduleOnMain]; 
        NSLog(@"Rescheduling"); 
       } 
       return; 
      } 
      @finally { 
      } 
    } 
} 

Ich sollte erwähnen, dass ich einen Signal-Handler gesetzt haben in meinem AppDelegate> appDidFinishLaunching:

signal(SIGPIPE, &signalHandler); 
signal(SIGABRT, &signalHandler); 

void signalHandler(int signal) 
{ 
    NSLog(@"Got signal %d",signal); 
} 

Und Das führt aus, ob die App abstürzt oder das Signal abgefangen wird. Hier ist ein Beispiel Crash-Backtrace:

Crashed Thread:  0 Dispatch queue: com.apple.main-thread 

Exception Type:  EXC_CRASH (SIGABRT) 
Exception Codes:  0x0000000000000000, 0x0000000000000000 

Application Specific Information: 
*** Terminating app due to uncaught exception 'NSFileHandleOperationException', reason: '*** -[NSConcreteFileHandle writeData:]: Broken pipe' 
abort() called 
terminating with uncaught exception of type NSException 

Application Specific Backtrace 1: 
0 CoreFoundation      0x00007fff838cbbec __exceptionPreprocess + 172 
1 libobjc.A.dylib      0x00007fff90e046de objc_exception_throw + 43 
2 CoreFoundation      0x00007fff838cba9d +[NSException raise:format:] + 205 
3 Foundation       0x00007fff90a2be3c __34-[NSConcreteFileHandle writeData:]_block_invoke + 81 
4 Foundation       0x00007fff90c53c17 __49-[_NSDispatchData enumerateByteRangesUsingBlock:]_block_invoke + 32 
5 libdispatch.dylib     0x00007fff90fdfb76 _dispatch_client_callout3 + 9 
6 libdispatch.dylib     0x00007fff90fdfafa _dispatch_data_apply + 110 
7 libdispatch.dylib     0x00007fff90fe9e73 dispatch_data_apply + 31 
8 Foundation       0x00007fff90c53bf0 -[_NSDispatchData enumerateByteRangesUsingBlock:] + 83 
9 Foundation       0x00007fff90a2bde0 -[NSConcreteFileHandle writeData:] + 150 
10 myApp        0x000000010926473e -[MTTaskChain tee:] + 2030 
11 CoreFoundation      0x00007fff838880dc __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12 
12 CoreFoundation      0x00007fff83779634 _CFXNotificationPost + 3140 
13 Foundation       0x00007fff909bb9b1 -[NSNotificationCenter postNotificationName:object:userInfo:] + 66 
14 Foundation       0x00007fff90aaf8e6 _performFileHandleSource + 1622 
15 CoreFoundation      0x00007fff837e9ae1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17 
16 CoreFoundation      0x00007fff837dbd3c __CFRunLoopDoSources0 + 476 
17 CoreFoundation      0x00007fff837db29f __CFRunLoopRun + 927 
18 CoreFoundation      0x00007fff837dacb8 CFRunLoopRunSpecific + 296 
19 HIToolbox       0x00007fff90664dbf RunCurrentEventLoopInMode + 235 
20 HIToolbox       0x00007fff90664b3a ReceiveNextEventCommon + 431 
21 HIToolbox       0x00007fff9066497b _BlockUntilNextEventMatchingListInModeWithFilter + 71 
22 AppKit        0x00007fff8acf5cf5 _DPSNextEvent + 1000 
23 AppKit        0x00007fff8acf5480 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 194 
24 AppKit        0x00007fff8ace9433 -[NSApplication run] + 594 
25 AppKit        0x00007fff8acd4834 NSApplicationMain + 1832 
26 myApp        0x00000001091b16a2 main + 34 
27 myApp        0x00000001091ab864 start + 52 
+1

Es ist keine Ausnahme, es ist ein C-Signal. – Kevin

+1

Warum heißt es dann "Beenden der App aufgrund der nicht abgefangenen Ausnahme 'NSFileHandleOperationException'"? – mackworth

+0

Ich bin gespannt: Ein Breakpoint im @catch-Block wird bei diesem Absturz nicht getroffen? Ich vermute, dass "rescheduleOnMain" etwas ist, was man nach dem Absturz des Hauptthreads sowieso nicht tun kann. – stevesliva

Antwort

4

Also, die netten Leute bei Crashlytics konnten mir hier helfen. Zitieren sie:

Hier ist die Geschichte:

  • Das Rohr stirbt, weil das Kind Prozess abstürzt. Das nächste Lesen/Schreiben führt zu einem Fehler.
  • Dieser Schreibvorgang tritt auf, was zu einem SIGPIPE (keine Laufzeitausnahme) führt.
  • Wenn SIGPIPE maskiert/ignoriert wird, prüft NSFileHandle errno und erstellt eine Laufzeitausnahme, die es auslöst.
  • Eine Funktion tiefer als Ihr T-Stück: Methode hat dieses Schreiben in einem eingewickelt @ versuchen/@ catch (bewiesen durch einen Haltepunkt auf __cxa_begin_catch Einstellung)
    • Diese Funktion, die „_dispatch_client_callout“ erweist, Das ruft einen Aufruf von objc_terminate auf, der den Prozess effektiv beendet.

Warum tut dies _dispatch_client_callout zu tun?Ich bin nicht sicher, aber Sie können den Code sehen hier: http://www.opensource.apple.com/source/libdispatch/libdispatch-228.23/src/object.m

Leider hat AppKit eine wirklich schlechte Erfolgsbilanz eine gute Bürger angesichts der Laufzeit Ausnahmen zu sein.

So, Sie haben Recht, dass NSFileHandle eine Laufzeitausnahme über die Leitung sterben, aber nicht bevor ein Signal ausgelöst wird, das den Prozess tötet. Andere haben genau dieses Problem (auf iOS, das viel bessere Semantik über Laufzeitausnahmen hat) aufgetreten.

How can I catch EPIPE in my NSFIleHandle handling?

Kurz gesagt, ich glaube es nicht möglich ist, für Sie diese Ausnahme zu fangen. Aber, indem ich SIGPIPE und mit untergeordneten APIs zu lesen/schreibe in diese Datei behandeln, glaube ich, dass Sie dies umgehen können. Als eine allgemeine Regel, würde ich gegen das Ignorieren von Signalen empfehlen, aber in diesem Fall, scheint es vernünftig.

So ist die überarbeitete Code ist jetzt:

-(void)tee:(NSNotification *)notification { 
    NSData *readData = notification.userInfo[NSFileHandleNotificationDataItem]; 
    totalDataRead += readData.length; 
    // NSLog(@"Total Data Read %ld",totalDataRead); 
    NSArray *pipes = [teeBranches objectForKey:notification.object]; 

    if (readData.length) { 
     for (NSPipe *pipe in pipes) { 
      NSInteger numTries = 3; 
      size_t bytesLeft = readData.length; 
      while (bytesLeft > 0 && numTries > 0) { 
       ssize_t amountSent= write ([[pipe fileHandleForWriting] fileDescriptor], [readData bytes]+readData.length-bytesLeft, bytesLeft); 
       if (amountSent < 0) { 
        NSLog(@"write fail; tried %lu bytes; error: %zd", bytesLeft, amountSent); 
        break; 
       } else { 
        bytesLeft = bytesLeft- amountSent; 
        if (bytesLeft > 0) { 
         NSLog(@"pipe full, retrying; tried %lu bytes; wrote %zd", (unsigned long)[readData length], amountSent); 
         sleep(1); //probably too long, but this is quite rare 
         numTries--; 
        } 
       } 
      } 
      if (bytesLeft >0) { 
       if (numTries == 0) { 
        NSLog(@"Write Fail4: couldn't write to pipe after three tries; giving up"); 
       } 
       [self rescheduleOnMain]; 
      } 

     } 
    } 
} 
+0

Ich traf das gleiche Problem, aber ich fand, dass die EPIPE tatsächlich behandelt und in eine objc Ausnahme gemacht wird. –

1

Ich weiß, das nicht viel zu beantworten macht, warum die Fang Ausnahme gebrochen scheint, aber ich hoffe, dass dies eine hilfreiche Antwort ist das Problem zu umgehen.

Ich konfrontiert ein ähnliches Problem beim Lesen/Schreiben zu einem Socket in einem NSFileHandle verpackt. Ich arbeitete um ihn herum durch Testen der Verfügbarkeit Rohr direkt mit dem fileDescriptor wie so

- (BOOL)socketIsValid 
{ 
    return (write([fh fileDescriptor], NULL, 0) == 0); 
} 

dann getestet I mit dieser Methode vor writeData: zu nennen versucht.

+0

Ich habe es versucht, und der Write-Test oben ist in Ordnung. Ich denke, das Problem ist, dass das Schreiben selbst einen Fehler in der Pipe verursacht, im Gegensatz zu der Pipe, die bereits inaktiv ist. – mackworth

+0

@mackworth ok, vielleicht ist das Problem, dass die Pipe in der Mitte des Schreibvorgangs nach unten geht? ein Workaround kann immer noch eine Stufe unter "NSFileHandle" sein; Wenn Sie etwas anderes herausfinden, hoffe ich Sie post cuz würde ich gerne wissen :) –

Verwandte Themen