Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MacOS 14: Crash on dispatch_activate after fork #817

Open
bukka opened this issue Feb 27, 2024 · 0 comments
Open

MacOS 14: Crash on dispatch_activate after fork #817

bukka opened this issue Feb 27, 2024 · 0 comments

Comments

@bukka
Copy link

bukka commented Feb 27, 2024

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)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant