MacOS 14: Crash on dispatch_activate after fork
bukka opened this issue · comments
We are just working on improvement / fix for PHP timer (max execution time) with use of libdispatch: php/php-src#13468 . This is working on CLI but if run on PHP-FPM, which uses a typical process worker (master process creates multiple children), it crashes. After debugging it, we found out that it is due to the fact that Curl, which is loaded in master process, uses SCDynamicStoreCopyProxies which does libdispatch calls. When FPM forks, the code using libdispatch is unable to activate the timer.
I have create a minimal reproducer (just using plain C) which presents the problem and show the crash:
// timer.c can be compiled as
// cc -framework Foundation -framework SystemConfiguration -o timer timer.c
#include <dispatch/dispatch.h>
#include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
void timer_handler(void *ctx)
{
printf("handle\n");
}
void timer_cancel(void *ctx)
{
printf("cancel\n");
}
void sigchld_handler(int signum) {
int status;
pid_t pid;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
if (WIFEXITED(status)) {
printf("Child %d exited with status %d\n", pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child %d killed by signal %d\n", pid, WTERMSIG(status));
}
}
}
int main()
{
// if SCDynamicStoreCopyProxies is in parent, it crashes
CFDictionaryRef dict = SCDynamicStoreCopyProxies(NULL);
if (dict)
CFRelease(dict);
signal(SIGCHLD, sigchld_handler);
int pid = fork();
if (pid == 0) {
// if SCDynamicStoreCopyProxies is in the child, it works
// CFDictionaryRef dict = SCDynamicStoreCopyProxies(NULL);
// if(dict)
// CFRelease(dict);
// using global queue crashes which could be expected
// dispatch_queue_global_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
// but custom queue which should not have global state also crashes
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_UTILITY, 0);
dispatch_queue_t queue = dispatch_queue_create("net.php.zend_max_execution_timer", attr);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer == NULL) {
printf("timer is null\n");
return 1;
}
dispatch_source_set_event_handler_f(timer, timer_handler);
dispatch_source_set_cancel_handler_f(timer, timer_cancel);
dispatch_source_set_timer(
timer,
dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
2 * NSEC_PER_SEC,
0
);
dispatch_activate(timer);
sleep(10);
} else if (pid > 0) {
printf("created child %d\n", pid);
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("Child exited with status %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child killed by signal %d\n", WTERMSIG(status));
}
printf("finishing\n");
} else {
printf("fork error\n");
}
return 0;
}
When this is run, it will show that child crashes (segfault) due to accessing invalid memory which happens during dispatch_activate
.
Is this issue known and is libdispatch not usable after the fork? Potentially is there anything that can be done to get around this issue (except not doing fork)?