Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
999 views
in Technique[技术] by (71.8m points)

macos - Why can't I use cocoa frameworks in different forked processes?

I was playing with the NSSound class to play a sound in a background process of my own so as to not block user input. I decided to call fork() but that is giving me problems. At the very moment the sound is allocated the forked process crashes. The funny thing is if I construct an example which only calls fork(), then the child process can call NSSound without problems, the crashes come only if I try to use other cocoa APIs before the fork()call. See this example with the crashme?() calls commented:

#import <AppKit/AppKit.h>
#import <math.h>

#define FILENAME 
    "/System/Library/Components/CoreAudio.component/" 
    "Contents/SharedSupport/SystemSounds/dock/drag to trash.aif"

void crashme1(void)
{
    NSString *path = [[NSString alloc] initWithUTF8String:"false file"];
    NSSound *sound = [[NSSound alloc]
        initWithContentsOfFile:path byReference:YES];
}

void crashme2(void)
{
    NSInteger tag = 0;
    [[NSWorkspace sharedWorkspace]
        performFileOperation:NSWorkspaceRecycleOperation
        source:@"." destination:nil
        files:[NSArray arrayWithObject:@"false file"] tag:&tag];
}

double playAif(void)
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSString *path = [[NSString alloc] initWithUTF8String:FILENAME];
    NSSound *sound = [[NSSound alloc]
        initWithContentsOfFile:path byReference:YES];
    [path release];

    if (!sound) {
        [pool release];
        return -1;
    }

    const double ret = [sound duration];
    [sound play];
    [sound autorelease];
    [pool release];
    return ret;
}

int main(int argc, char *argv[])
{
    //crashme1();
    //crashme2();
    int childpid = fork();
    if (0 == childpid)  {
        printf("Starting playback
");
        double wait = playAif();
        sleep(ceil(wait));
        wait = playAif();
        sleep(ceil(wait));
        printf("Finished playback
");
    }
    return 0;
}

When I run this from the commandline it works, but if I uncomment one of the calls to either crashme?() functions the playback in the forked process never starts. Is it because any of the Cocoa framework APIs are not async-signal safe? Is there any way to make the calls async signal safe by wrapping them somehow?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I'll quote the Leopard CoreFoundation Framework Release Notes. I don't know if they're still online, since Apple tends to replace rather than extend the Core Foundation release notes.

CoreFoundation and fork()

Due to the behavior of fork(), CoreFoundation cannot be used on the child-side of fork(). If you fork(), you must follow that with an exec*() call of some sort, and you should not use CoreFoundation APIs within the child, before the exec*(). The applies to all higher-level APIs which use CoreFoundation, and since you cannot know what those higher-level APIs are doing, and whether they are using CoreFoundation APIs, you should not use any higher-level APIs either. This includes use of the daemon() function.

Additionally, per POSIX, only async-cancel-safe functions are safe to use on the child side of fork(), so even use of lower-level libSystem/BSD/UNIX APIs should be kept to a minimum, and ideally to only async-cancel-safe functions.

This has always been true, and there have been notes made of this on various Cocoa developer mailling lists in the past. But CoreFoundation is taking some stronger measures now to "enforce" this limitation, so we thought it would be worthwhile to add a release note to call this out as well. A message is written to stderr when something uses API which is definitely known not to be safe in CoreFoundation after fork(). If file descriptor 2 has been closed, however, you will get no message or notice, which is too bad. We tried to make processes terminate in a very recognizable way, and did for a while and that was very handy, but backwards binary compatibility prevented us from doing so.

In other words, it has never been safe to do much of anything on the child side of a fork() other than exec a new program.

Besides the general POSIX prohibition, an oft-mentioned explanation is: a) pretty much all Cocoa programs are multithreaded these days, what with GCD and the like. B) when you fork(), only the calling thread survives into the child process; the other threads just vanish. Since those threads could have been manipulating shared resources, the child process can't rely on having sane state. For example, the malloc() implementation may have a lock to protect shared structures and that lock could have been held by one of the now-gone threads at the time of the fork(). So, if the remaining thread tries to allocate memory, it may hang indefinitely. Other shared data structures may simply be in a corrupted state. Etc.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...