为什么没有@try ... @ catch使用 - [NSFileHandle writeData]?

时间:2014-09-08 16:47:49

标签: objective-c cocoa

我有一个类似于tee实用程序的方法。它接收通知已在管道上读取数据,然后将该数据写入一个或多个管道(连接到从属应用程序)。如果从属应用程序崩溃,那么该管道坏了,我自然会得到一个异常,然后在@try ... @ catch块中处理。

大部分时间都可以使用。我感到困惑的是偶尔,异常会使应用程序完全崩溃并发生未捕获的异常,并指向writeData行。我还没能弄清楚它崩溃时的模式是什么,但为什么它不会被抓住? (注意,这不是在调试器内执行。)

以下是代码:

//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 {
            }
    }
 }

我应该提一下,我在AppDelegate中设置了一个信号处理程序> appDidFinishLaunching:

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

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

无论应用程序崩溃还是信号被捕获,都会执行。 这是一个示例崩溃回溯:

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

2 个答案:

答案 0 :(得分:4)

所以,Crashlytics的好朋友能够在这里帮助我。引用它们:

  

以下是故事:

     
      
  • 管道因为子进程崩溃而死亡。下一次读/写将导致故障。
  •   
  • 发生写入,导致SIGPIPE(不是运行时异常)。
  •   
  • 如果屏蔽/忽略该SIGPIPE,NSFileHandle将检查errno并创建它抛出的运行时异常。
  •   
  • 比你的tee更深层次的函数:方法已将此写入包装在@ try / @ catch中(通过在__cxa_begin_catch上设置断点来证明)      
        
    • 该函数原来是“_dispatch_client_callout”,它调用了objc_terminate,它有效地杀死了   过程
    •   
  •   
     

为什么_dispatch_client_callout会这样做?我不确定,但你可以   看到这里的代码:   http://www.opensource.apple.com/source/libdispatch/libdispatch-228.23/src/object.m

     

不幸的是,AppKit的良好记录非常糟糕   公民面对运行时异常。

     

所以,你是对的,NSFileHandle提出了一个运行时异常   管道死亡,但不是在信号升高之前杀死了   处理。其他人遇到了这个确切的问题(在iOS上,它有   关于运行时异常的更好的语义。)

     

How can I catch EPIPE in my NSFIleHandle handling?

     

简而言之,我不相信你有可能抓住这个   例外。但是,通过使用较低级别的API忽略SIGPIPE   读/写这个文件句柄,我相信你可以解决这个问题。如   一般来说,我建议不要忽略信号,但在此   这种情况似乎很合理。

因此修订后的代码现在是:

-(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];
             }

        }
    }
}

答案 1 :(得分:1)

我知道这并没有多大回答为什么异常捕获似乎已经破解,但我希望这是解决问题的有用答案。

我遇到类似的问题,尝试读取/写入包含在NSFileHandle中的套接字。我通过使用fileDescriptor直接测试管道可用性来解决这个问题

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

然后我在尝试调用writeData:之前使用该方法进行了测试。