Very helpful information, thank you.
I also found that when I enable App Sandbox there's another thread running. Without app sandbox there's just one thread. Could that affect fork?
Crashing example that involves just fork with nothing else:
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow?
}
func app() {
let appDelegate = AppDelegate()
let application = NSApplication.shared
application.delegate = appDelegate
print("launch app")
let _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
print("never here")
}
// without App Sandbox there's one thread at this point
// with App Sandbox there are two threads at this point
let pid = forkme()
if pid == -1 {
fatalError("error")
} else if pid == 0 {
print("after fork in child")
app()
print("exiting child")
} else {
print("after fork in parent")
usleep(10_000_000)
print("exiting parent")
}
C helper:
pid_t forkme(void) {
return fork();
}
Partial crash log:
System Integrity Protection: enabled
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x000000019984afac
Termination Reason: Namespace SIGNAL, Code 5 Trace/BPT trap: 5
Terminating Process: exc handler [47238]
Application Specific Information:
crashed on child side of fork pre-exec
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libobjc.A.dylib 0x19984afac objc_initializeAfterForkError + 0
1 libobjc.A.dylib 0x199832958 initializeNonMetaClass + 1064
2 libobjc.A.dylib 0x1998325bc initializeNonMetaClass + 140
3 libobjc.A.dylib 0x19984cf14 initializeAndMaybeRelock(objc_class*, objc_object*, locker_mixin<lockdebug::lock_mixin<objc_lock_base_t>>&, bool) + 156
4 libobjc.A.dylib 0x1998321c8 lookUpImpOrForward + 884
5 libobjc.A.dylib 0x199831b64 _objc_msgSend_uncached + 68
6 For 0x104a68b68 app() + 76 (main.swift:43)
7 For 0x104a68488 main + 480 (main.swift:57)
8 dyld 0x199873f28 start + 2236
There's also this debug message in the console:
objc[47238]: +[NSResponder initialize] may have been in progress in another thread when fork() was called.
exiting parent
If I swap what's done in child and what's done in parent:
} else if pid == 0 {
print("after fork in child")
usleep(10_000_000)
print("exiting child")
} else {
print("after fork in parent")
app()
print("exiting parent")
}
then it works. So there's something I can and can't do in the child process? What are the rules?
In one of the links above you mentioned that fork is fundamentally incompatible with "higher level frameworks"? Does that include, say, XPC? or URLSession? or Network framework?
Basically I want to make the old style "treadless" app (understandably the threads created by OS or standard library are probably inevitable): create N sub processes, off-load work to them from the main process and communicate to those processes by some means (I'm considering between pipes, XPC, Network framework and URLSession but I am open to consider a better alternative!).
Topic:
Code Signing
SubTopic:
General
Tags: