Duplicate file handles created by FSEventStreamCreate

Originator:therzok
Number:rdar://7359453 Date Originated:Oct 8, 2019 at 7:51 PM
Status:Open Resolved:
Product:macOS Product Version:10.14.4
Classification: Reproducible:Yes
 
Basic Information
Please provide a descriptive title for your feedback:
Duplicate file handles created by FSEventStreamCreate
Which area are you seeing an issue with?
Core Foundation API
What type of issue are you reporting?
Incorrect/Unexpected Behavior
Description
Please describe the issue and what steps we can take to reproduce it:
I couldn't find Core Services as a category, so I guessed the best place to put it was under Core Foundation.

There is a hard limit (by default) for macOS apps for 256 file handles per process. But this limit can easily be reached via FSEventStreamCreate, as it duplicates handles for every directory segment under the path we're watching.

Given an application that has to deal with user environments, having event streams created for a few deeply nested directories can easily exhaust the number of handles an app can possibly create, leading to undefined behaviour on the application from then on.

The initial assumption was that migrating from multiple FSEventStreams with one root path - to one FSEventStream with multiple root paths would deduplicate the file handles being used. It seems to not be the case.

A sample source file attached below can reproduce this issue, where watching something in the lines of:
```
~/Documents
~/Documents
~/Applications
```

Creates this set of file handles:
```
 fw  8417 therzok    4r     DIR    1,4     4544     625235 /Users/therzok/Documents
 fw  8417 therzok    5r     DIR    1,4     4896     625234 /Users/therzok
 fw  8417 therzok    6r     DIR    1,4      192     170037 /Users
 fw  8417 therzok    7r     DIR    1,4     4544     625235 /Users/therzok/Documents
 fw  8417 therzok    8r     DIR    1,4     4896     625234 /Users/therzok
 fw  8417 therzok    9r     DIR    1,4      192     170037 /Users
 fw  8417 therzok   10r     DIR    1,4      384    1138601 /Users/therzok/Applications
 fw  8417 therzok   11r     DIR    1,4     4896     625234 /Users/therzok
 fw  8417 therzok   12r     DIR    1,4      192     170037 /Users
```

The expected set of handles should be something like:
```
 fw  8417 therzok    4r     DIR    1,4     4544     625235 /Users/therzok/Documents
 fw  8417 therzok    5r     DIR    1,4     4896     625234 /Users/therzok
 fw  8417 therzok    6r     DIR    1,4      192     170037 /Users
 fw  8417 therzok    7r     DIR    1,4     4544     625235 /Users/therzok/Applications
```

A workaround I've also tried was using a larger cone of watching, such as, "~", but that causes a huge spike in the amount of notifications received for the app, causing high energy consumptions and overall having a big toll on the user machine.

Code attachment:
#import <Foundation/Foundation.h>

static void cb(ConstFSEventStreamRef streamRef,
                       void *pClientCallBackInfo,
                       size_t numEvents,
                       void *pEventPaths,
                       const FSEventStreamEventFlags eventFlags[],
                       const FSEventStreamEventId eventIds[]) {
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Get the current user documents directory
        NSArray<NSString *> *pathArray =
            NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsPath = [pathArray firstObject];

        pathArray = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES);
        NSString *applicationDirectory = [pathArray firstObject];

        // Append the documents directory twice and the directory's parent once
        NSArray<NSString *> *pathsToWatch = [NSArray arrayWithObjects:
                                             documentsPath,
                                             documentsPath,
                                             applicationDirectory,
                                             nil];

        FSEventStreamRef stream =
            FSEventStreamCreate(NULL,
                                &cb,
                                NULL,
                                (__bridge CFArrayRef)pathsToWatch,
                                kFSEventStreamEventIdSinceNow,
                                0,
                                kFSEventStreamCreateFlagWatchRoot | kFSEventStreamCreateFlagFileEvents);

        dispatch_queue_t queue = dispatch_queue_create("fw", DISPATCH_QUEUE_SERIAL);
        FSEventStreamSetDispatchQueue(stream, queue);

        FSEventStreamStart(stream);

        // now run lsof -p on the process to see open handles
        // At this point, we have a lot of duplicate handles:
        /*
         fw  8417 therzok    4r     DIR    1,4     4544     625235 /Users/therzok/Documents
         fw  8417 therzok    5r     DIR    1,4     4896     625234 /Users/therzok
         fw  8417 therzok    6r     DIR    1,4      192     170037 /Users
         fw  8417 therzok    7r     DIR    1,4     4544     625235 /Users/therzok/Documents
         fw  8417 therzok    8r     DIR    1,4     4896     625234 /Users/therzok
         fw  8417 therzok    9r     DIR    1,4      192     170037 /Users
         fw  8417 therzok   10r     DIR    1,4      384    1138601 /Users/therzok/Applications
         fw  8417 therzok   11r     DIR    1,4     4896     625234 /Users/therzok
         fw  8417 therzok   12r     DIR    1,4      192     170037 /Users
         */
        sleep(100000);
    }
    return 0;
}

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!