[英]Why doesn't @try…@catch work with -[NSFileHandle writeData]?
我有一個類似於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
所以,Crashlytics的好朋友能夠在這里幫助我。 引用它們:
這是故事:
- 管道因為子進程崩潰而死亡。 下一次讀/寫將導致故障。
- 發生寫入,導致SIGPIPE(不是運行時異常)。
- 如果屏蔽/忽略該SIGPIPE,NSFileHandle將檢查errno並創建它拋出的運行時異常。
- 比你的tee 更深的函數:方法將這個寫包裝在@ try / @ catch中(通過在
__cxa_begin_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上,它有更好的關於運行時異常的語義)。
簡而言之,我不相信你有可能抓住這個例外。 但是,通過忽略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];
}
}
}
}
我知道這並沒有解釋為什么異常捕獲似乎被打破,但我希望這是一個有用的解決方法來解決這個問題。
我在嘗試讀取/寫入包含在NSFileHandle
的套接字時遇到了類似的問題。 我通過使用fileDescriptor
直接測試管道可用性來解決這個fileDescriptor
- (BOOL)socketIsValid
{
return (write([fh fileDescriptor], NULL, 0) == 0);
}
然后我在嘗試調用writeData:
之前使用該方法進行了測試writeData:
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.