NSFileHandleDataAvailableNotification files repeatedly with no new data (resulting in very high CPU usage)
Asked Answered
S

1

8

I'm attempting to read data from the Standard Error of a NSTask in Cocoa using waitForDataInBackgroundAndNotify. The following code does read the stream, so it's already working partially.

The problem I have is that sometimes the NSFileHandleDataAvailableNotification starts firing repeatedly (thousands of times per second) with no new data at all ([data length] returns 0). My process then starts using a lot of CPU, slowing the machine to a halt. Has any of you guys hit something like this before in the past? Thanks in advance.

/**
 * Start reading from STDERR
 *
 * @private
 */

- (void)startReadingStandardError {
    NSFileHandle *fileHandle = [_task.standardError fileHandleForReading];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(errorData:)
                                                 name:NSFileHandleDataAvailableNotification
                                               object:fileHandle];
    [fileHandle waitForDataInBackgroundAndNotify];
}

/**
 * Fired whenever new data becomes available on STDERR
 *
 * @private
 */

-(void) errorData: (NSNotification *) notification
{
    NSFileHandle *fileHandle = (NSFileHandle*) [notification object];
    NSData *data = [fileHandle availableData];

    if ([data length]) {
       // consume data
    }

   [fileHandle waitForDataInBackgroundAndNotify];
}
Schock answered 8/4, 2013 at 19:17 Comment(0)
S
10

So, ended up figuring it out by myself. According to the NSFileHandle Class Reference, if the NSData object returned by availableData has a length of 0, it means that the end-of-file has been reached. I was not handling this case correctly. This fixed it for me:

/**
 * Fired whenever new data becomes available on STDERR
 *
 * @private
 */

-(void) errorData: (NSNotification *) notification
{
    NSFileHandle *fileHandle = (NSFileHandle*) [notification object];
    NSData *data = [fileHandle availableData];

    if ([data length]) {
        // consume data
        // ...

        [fileHandle waitForDataInBackgroundAndNotify];
    } else {
        // EOF was hit, remove observer
        [[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleDataAvailableNotification object:fileHandle];
    }
}
Schock answered 8/4, 2013 at 20:5 Comment(2)
You should also close the file.Villous
I love how nowhere in the docs it is mentioned that waitForDataInBackgroundAndNotify must be called again after the notification arrived. Thanks!Gregg

© 2022 - 2024 — McMap. All rights reserved.