Objective c 为什么';t@try…@catch与-[NSFileHandle writeData]一起工作?

Objective c 为什么';t@try…@catch与-[NSFileHandle writeData]一起工作?,objective-c,cocoa,Objective C,Cocoa,我有一个类似于tee实用程序的方法。它接收到一个通知,表示已在管道上读取数据,然后将该数据写入一个或多个管道(连接到从属应用程序)。如果一个下级应用程序崩溃,那么这个管道就会断开,我自然会得到一个异常,然后在@try…@catch块中处理 这在大多数情况下都有效。让我感到困惑的是,偶尔,异常会导致应用程序完全崩溃,同时出现一个未捕获的异常,并指向writeData行。我一直无法弄清楚它崩溃时的模式是什么,但为什么它永远不会被捕获?(注意,这不是在调试器内部执行。) 代码如下: //in setu

我有一个类似于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

我知道这并不能回答为什么异常捕获看起来被破坏了,但我希望这是一个解决这个问题的有用答案

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

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

然后,在尝试调用
writeData:

之前,我使用该方法进行了测试,因此,Crashlytics的好心人能够在这里帮助我。引用他们的话:

故事是这样的:

  • 由于子进程崩溃,管道死亡。下一次读/写操作将导致故障
  • 发生该写操作时,会导致SIGPIPE(不是运行时异常)
  • 如果该SIGPIPE被屏蔽/忽略,NSFileHandle将检查errno并创建一个运行时异常,并引发该异常
  • 一个比tee:method更深的函数已将此写入封装在@try/@catch中(通过在
    \uucxa\u begin\u catch
    上设置断点来证明)
    • 该函数是“\u dispatch\u client\u callout”,它调用objc\u terminate,从而有效地终止 过程
为什么调度客户调出这样做?我不确定,但你可以 请参见此处的代码: 

不幸的是,AppKit在成为一名优秀员工方面的记录非常糟糕 面临运行时异常的公民

因此,NSFileHandle引发有关的运行时异常是正确的 管道正在消亡,但在发出杀死管道的信号之前 过程其他人也遇到了这个问题(在iOS上,它已经 更好的关于运行时异常的语义)



简言之,我认为你不可能抓住这个机会 例外。但是,通过忽略SIGPIPE并使用较低级别的api 读取/写入此文件句柄,我相信您可以解决此问题。作为 一般来说,我建议不要忽略信号,但在这种情况下 在这种情况下,这似乎是合理的

因此,经修订的守则现为:

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

        }
    }
}
-(void)tee:(NSNotification*)通知{
NSData*readData=notification.userInfo[NSFileHandleNotificationDataItem];
totalDataRead+=readData.length;
//NSLog(@“总数据读取%ld”,总数据读取);
NSArray*pipes=[teebracks-objectForKey:notification.object];
if(readData.length){
用于(NSPipe*管道中的管道){
NSInteger numTries=3;
size\u t byteslight=readData.length;
while(bytesleet>0&&numTries>0){
ssize_t amountSent=write([[pipe fileHandleForWriting]fileDescriptor],[readData bytes]+readData.length-byteslight,byteslight);
如果(发送的数量<0){
NSLog(@“写入失败;尝试了%lu个字节;错误:%zd”,字节左,已发送的数量);
打破
}否则{
bytesLeft=bytesLeft-发送的数量;
如果(字节左>0){
NSLog(@“管道已满,正在重试;尝试了%lu字节;写入了%zd”,(无符号长)[readData length],amountSent);
睡眠(1);//可能太长了,但这种情况很少见
numTries--;
}
}
}
如果(字节左>0){
如果(numTries==0){
NSLog(@“写入失败4:尝试三次后无法写入管道;放弃”);
}
[自我重新安排];
}
}
}
}

这不是一个异常,它是一个C信号。那么为什么它会说“由于未捕获的异常'NSFileHandleOperationException'”而终止应用程序?我很好奇:@catch块中的断点在这次崩溃中没有被命中?我怀疑“rescheduleOnMain”是在主线程崩溃后不能做的事情。这是正确的。如果在调试器下运行(在catch的第一行上有一个断点),并且发生管道故障,则调试器将在写入时捕获一个SIGPIPE;当我继续时,我得到了“get signal 13”,然后是堆栈转储,在writeData行和“libc++abi.dylib:以NSException类型的未捕获异常终止”,然后我再次点击continue,它将我带到一个系统问题报告。在最后一部分,抓住一个信号管并继续下去是很标准的。你们有没有找到解决这个问题的办法?你能捕获这个异常吗?我试过了,上面的写测试结果很好。我认为问题是,书写本身会导致管道失效,而管道已经失效。@麦克沃思好吧,那么问题可能是管道在写入操作过程中下降了吗?还有一个解决方法是将级别降低到低于
NSFileHandle
;如果你想知道其他的事情,我希望你发帖子,因为我想知道:)我遇到了同样的问题,但我发现EPIPE实际上是被处理的,并被做成了一个objc异常。