Multiprocess Demons in PHP

    Why might you need to write daemons in PHP?
    • Performing time-consuming background tasks;
    • execution of tasks that last longer than the timeout for an HTTP request (30 seconds);
    • performing tasks at a higher level of access than the server process (read - under root).


    The basics


    • PID - process identifier. Unique for the current moment positive number.
    • pcntl is a PHP extension for working with child processes. We smoke a manual.
    • posix is a PHP extension for working with the functions of the POSIX standard. We smoke a manual.


    If you have a question about some unfamiliar function - don't be upset! They are all documented in PHP Manual. It is unlikely that I will be able to talk about them in more detail and more interesting.

    Forking (we produce processes)


    How to make two from one process? Windows programmers (including me) are more familiar with the system when we write a function that will be main()for a child thread. In * nix, this is not so, because I will talk a little about this multiprocessing system. * nixoids can safely skip this part if they already know everything.

    So. There is such a function pcntl_fork. Oddly enough, she takes no arguments. What to do?

    After pcntl_forkthe script, schizophrenia begins: the code seems to be the same, but it is executed by two parallel processes. However, if you simply paste it into a script pcntl_fork, you will not see anything visual, except that there are conflicts of access to resources.

    The trick is thatpcntl_forkreturns 0 to the child process and the PID of the child process to the parent. Here is a common usage pattern pcntl_fork:

    $ pid = pcntl_fork ();
    if ($ pid == -1) {
        //error
    } elseif ($ pid) {
        // parent process gets here
    } else {
        // and here is the child process
    }            
    // and both processes will get here
    


    By the way, it pcntl_forkworks only in CGI and CLI modes. From under the Apache - it is impossible. Is logical.

    Demonization



    To demonize a script, you need to untie it from the console and put it into an endless loop. Let's see how this is done.

    // create a child process
    $ child_pid = pcntl_fork ();
    if ($ child_pid) {
        // exit the parent console-bound process
        exit  
    }
    // make the main process a child. 
    // After that, he can also bear children. 
    // The harsh life of these processes ... 
    posix_setsid ();
    


    After such actions, we are left with a demon - a program without a console. So that she does not complete her execution immediately, we put her into an endless loop (well, almost):

    while (! $ stop_server) {
        // TODO: doing something
    }
    


    Child processes



    At the moment, our demon is single-process. For a number of obvious reasons, this may not be enough. Consider creating child processes.

    $ child_processes = array ();
    while (! $ stop_server) {
        if (! $ stop_server and (count ($ child_processes) <MAX_CHILD_PROCESSES)) {
            // TODO: get the task
            // produce the child process
            $ pid = pcntl_fork ();
            if ($ pid == -1) {
                // TODO: error - could not create the process
            } elseif ($ pid) {
                // process created
                $ child_processes [$ pid] = true;
            } else {
                $ pid = getmypid ();
                // TODO: child process - here is the workload
                exit
            }
        } else {
            // so as not to idle the cycle
            sleep (SOME_DELAY); 
        }
        // check if one of the children died
        while ($ signaled_pid = pcntl_waitpid (-1, $ status, WNOHANG)) {
            if ($ signaled_pid == -1) {
                // no children left
                $ child_processes = array ();
                break;
            } else {
                unset ($ child_processes [$ signaled_pid]);
            }
        }
    } 
    

    Signal processing



    The next most important task is to provide signal processing. Now our demon knows nothing about the outside world, and you can kill him only by completing the process through kill -SIGKILL. This is bad. This is very bad - it SIGKILLwill interrupt the processes in the middle. In addition, information cannot be transmitted to him.

    There are a bunch of interesting signals that can be processed, but we will focus on the SIGTERM- signal of a correct shutdown.

    // Without this directive, PHP will not intercept signals
    declare (ticks = 1);
    // Handler
    function sigHandler ($ signo) {
        global $ stop_server;
        switch ($ signo) {
            case SIGTERM: {
                $ stop_server = true;
                break;
            }
            default: {
                // all other signals
            }
        }
    }
    // register the handler
    pcntl_signal (SIGTERM, "sig_handler");
    


    That's all. We intercept the signal - set the flag in the script - use this flag so as not to start new threads and complete the main loop.

    Maintaining the uniqueness of the demon


    And the final touch. It is necessary that the daemon does not start twice. Usually for these purposes, so-called .pid files - the file in which the pid of this particular daemon is written, if it is running.

    function isDaemonActive ($ pid_file) {
        if (is_file ($ pid_file)) {
            $ pid = file_get_contents ($ pid_file);
            // check for the presence of a process
            if (posix_kill ($ pid, 0)) {
                // daemon is already running
                return true;
            } else {
                // there is a pid file, but there is no process 
                if (! unlink ($ pid_file)) {
                    // I can’t destroy the pid file. error
                    exit (-1);
                }
            }
        }
        return false;
    }
    if (isDaemonActive ('/ tmp / my_pid_file.pid')) {
        echo 'Daemon already active';
        exit
    }
    


    And after demonization, you need to write the current PIDdaemon to the pid file .

    file_put_contents ('/ tmp / my_pid_file.pid', getmypid ());
    


    That's all there is to know for writing daemons in PHP. I did not talk about shared access to resources, because this problem is wider than writing demons.

    Good luck

    Syntax highlighted article on my blog .

    Also popular now: