簡體   English   中英

為什么不嘗試... @ catch使用 - [NSFileHandle writeData]?

[英]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上,它有更好的關於運行時異常的語義)。

我如何能趕上EPIPE我NSFIleHandle處理?

簡而言之,我不相信你有可能抓住這個例外。 但是,通過忽略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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM