How can I catch EPIPE in my NSFIleHandle handling?

I have an EPIPE problem in my iOS application and it does not fall into the @ try / @ catch / @ finally block. How can I catch this signal (probably SIGPIPE) ...

I created a "web proxy" in my application that will handle certain types of URLs. In this case, the error seems to be that the remote end (also in my application, but hiding in iOS libraries) closes its end of the socket. I am not receiving a notification (should I? Should I register for NSFileHandle, which can help here?).

I based this proxy on the HTTPServer that Matt Gallagher compiled (available here ), and the problem is in the subclass of the HTTPRequestHandler class that he compiled. Here's the code (this code is the equivalent of the startResponse method in the base class):

 -(void)proxyTS:(SSProxyTSResource *)proxyTS didReceiveResource:(NSData *)resource { NSLog(@"[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); CFHTTPMessageRef response = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1); CFHTTPMessageSetHeaderFieldValue(response, (CFStringRef)@"Content-Type", (__bridge CFStringRef)s_MIMEtype); CFHTTPMessageSetHeaderFieldValue(response, (CFStringRef)@"Connection", (CFStringRef)@"close"); CFHTTPMessageSetBody(response, (__bridge CFDataRef)resource); CFDataRef headerData = CFHTTPMessageCopySerializedMessage(response); @try { NSLog(@" -> writing %u bytes to filehandle...",[((__bridge NSData *)headerData) length]); [self.fileHandle writeData:(__bridge NSData *)headerData]; } @catch (NSException *exception) { // Ignore the exception, it normally just means the client // closed the connection from the other end. } @finally { NSLog(@" *ding*"); CFRelease(headerData); CFRelease(response); [self.server closeHandler:self]; } } 

And here is what appears in the console log on failure:

 Jan 15 14:55:10 AWT-NoTouch-iPhone-1 Streamer[1788] <Warning>: [SSProxyTSResponseHandler proxyTS:didReceiveResource:] Jan 15 14:55:10 iPhone-1 Streamer[1788] <Warning>: -> writing 261760 bytes to filehandle... Jan 15 14:55:11 iPhone-1 com.apple.launchd[1] (UIKitApplication:com.XXX.Streamer[0xf58][1788]) <Warning>: (UIKitApplication:com.XXX.Streamer[0xf58]) Exited abnormally: Broken pipe: 13 

It seems that since the other end has closed the phone, write() fails, so if someone can tell me how I can either detect that it is already closed and not try to write data to it OR that it no matter my program, which would be very useful.

+1
source share
1 answer

The immediate crash problem in SIGPIPE is resolved. I do not quite giggle about this solution, but at least the application does not crash. It is not clear that it works 100% correctly, but it seems to behave a little better.

I solved this problem by learning what happens next. While doing some research, I found that perhaps I should use the NSFileHandle writeabilityHandler property to set the block for writing. I do not completely sell this approach (it seemed confusing to me), but it can help.

Solution for processing Writability-handler:

During a web search on writeabilityHandler I stumbled upon a Bert Leung blog entry on some issues he had in a similar area. I took his code and changed it as follows, replacing the @try/@catch/@finally above with this code:

 self.pendingData = [NSMutableData dataWithData:(__bridge NSData *)(headerData)]; CFRelease(headerData); CFRelease(response); self.fileHandle.writeabilityHandler = ^(NSFileHandle* thisFileHandle) { int amountSent = send([thisFileHandle fileDescriptor], [self.pendingData bytes], [self.pendingData length], MSG_DONTWAIT); if (amountSent < 0) { // errno is provided by system NSLog(@"[%@ %@] Error while sending response: %d", NSStringFromClass([self class]), NSStringFromSelector(_cmd), errno); // Setting the length to 0 will cause this handler to complete processing. self.pendingData.length = 0; } else { [self.pendingData replaceBytesInRange:NSMakeRange(0, amountSent) withBytes:NULL length:0]; } if ([self.pendingData length] == 0) { thisFileHandle.writeabilityHandler = nil; // Hack to avoid ARC cycle with self. I don't like this, but... [[NSNotificationCenter defaultCenter] postNotification:self.myNotification]; } }; 

This worked fine, but it did not solve the problem. I was still getting SIGPIPE / EPIPE.

SIGPIPE will disappear!

This was not a surprise since it is almost the same as the previous writeData: but uses send() instead. The main difference is that using send() allows you to set errno . It was very useful, in fact - I got a couple of error codes (in errno), such as 54 (Connection reset by peer) and 32 (Broken pipe). 54 were beautiful, but 32 led to SIGPIPE / EPIPE. Then it dawned on me - maybe I should just ignore SIGPIPE.

Given this thought, I added a couple of hooks to my UIApplicationDelegate in application:didFinishLaunchingWithOptions:

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. [self installSignalHandlers]; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { ... 

and applicationWillTerminate: ::

 - (void)applicationWillTerminate:(UIApplication *)application { // Saves changes in the application managed object context before the application terminates. [self removeSignalHandlers]; [self saveContext]; } -(void)installSignalHandlers { signal(SIGPIPE,SIG_IGN); } -(void)removeSignalHandlers { signal(SIGPIPE, SIG_DFL); } 

Now, at least the application is not crashing. It is not clear that it works 100% correctly, but it seems that it behaves.

I also switched to the @try/@catch/@finally structure because it is more direct. In addition, after ignoring SIGPIPE, the @catch block starts. Right now I am registering an exception, but only so that I can see that it is working. In the released code, this log will be disabled.

+3
source

Source: https://habr.com/ru/post/974996/


All Articles