我有一个类似于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
答案 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:
之前使用该方法进行了测试。