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

I played with the NSSound class to play sound in the background so as not to block user input. I decided to call fork() , but that gives me problems. At the moment when the sound is highlighted, the branched process flies. The funny thing is, if I build an example that only calls fork() , then the child process can call NSSound without any problems, crashes only if I try to use other cocoa APIs before calling fork() . See this example with crashme?() Comments:

 #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\n"); double wait = playAif(); sleep(ceil(wait)); wait = playAif(); sleep(ceil(wait)); printf("Finished playback\n"); } return 0; } 

When I run this from the command line, it works, but if I uncomment one of the calls to crashme?() , Playback in a forked process never starts. Is it because any of the cocoa APIs is not async-signal safe? Is there a way to make an asynchronous call signal safe by wrapping them somehow?

+1
source share
1 answer

I will provide the release notes for the Leopard CoreFoundation Framework. I donโ€™t know if they are all online, because Apple is more likely to replace 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 are fork (), you must follow this with exec * (), and you should not use the CoreFoundation APIs inside the child element, before exec * (). This applies to all higher levels of the APIs that use CoreFoundation, and since you cannot know what these higher level APIs are and whether they use the CoreFoundation API, you should not use the higher level APIs either. This includes the use of the daemon () function.

In addition, for POSIX, only functions that are protected from asynchronization are safe to use on the child side of fork (), so even using a lower level libSystem / BSD / UNIX APIs should be minimized and ideally only asynchronous undo functions.

This has always been true, and notes have been made about this on Cocoa's various developer lists in the past. But CoreFoundation is now taking more drastic measures to โ€œforceโ€ the restriction, so we thought it was worth adding a note to the release to call it as well. A message is written to stderr when something uses an API that is not known to be safe in CoreFoundation after fork (). However, if the file descriptor 2 has been closed, you will not receive a message or notification, which is too bad. We tried to make the processes end in a very recognizable way, and for some time it was very convenient, but backward binary compatibility prevented us from doing this.

In other words, it was never safe to do anything on the child side of fork() , except exec the new program.

In addition to the general POSIX ban, the often-cited explanation is as follows: a) almost all Cocoa programs are multithreaded these days, with GCD, etc. B) when you fork() , only the calling thread survives in the child process; other threads just disappear. Because these threads could manipulate shared resources, the child process cannot rely on a health state. For example, an implementation of malloc() may have a lock to protect common structures and that the lock could be kept by one of the currently running threads during fork() . So, if the remaining thread tries to allocate memory, it can hang indefinitely. Other common data structures may simply be in a damaged state. Etc.

+3
source

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


All Articles