📜 ⬆️ ⬇️

PHP multiprocess daemons

Why might you need to write demons in PHP?


The basics




If you have a question about some unfamiliar function - do not worry! They are all documented in the PHP Manual. It is unlikely that I will be able to tell about them in more detail and more interesting.

Forking (multiplying processes)


How to make two of one process? Windows programmers (including me) are more familiar with the system when we write a function that will be main() for the child thread. In * nix everything is not so, because I will talk a little about this system multiprocess. * 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_fork , the script starts schizophrenia: the code seems to be the same, but is executed by two parallel processes. However, if you simply insert the pcntl_fork into the script, you will not see anything visual, except that access to resources conflicts.

The trick is that pcntl_fork returns 0 to the child process and the PID of the child process to the parent. Here is the usual pcntl_fork usage pcntl_fork :

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


By the way, pcntl_fork works 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 infinite 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, too, can produce children. 
 // The harsh life of these processes ... 
 posix_setsid ();


After such actions, we stay with the demon - a program without a console. So that it does not complete its execution immediately, let it go into an infinite loop (well, almost):

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


Child processes



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

 $ child_processes = array ();

 while (! $ stop_server) {
     if (! $ stop_server and (count ($ child_processes) <MAX_CHILD_PROCESSES)) {
         // TODO: get the task
         // fetch child process
         $ pid = pcntl_fork ();
         if ($ pid == -1) {
             // TODO: error - could not create 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 drive the cycle at idle
         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 it can only be killed by terminating the process via kill -SIGKILL . This is bad. This is very bad - SIGKILL will interrupt the processes in the middle. In addition, he can not pass information.

There are a lot of interesting signals that can be processed, but we will focus on SIGTERM - the signal of proper completion of work.

 // 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 handler
 pcntl_signal (SIGTERM, "sig_handler");


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

Maintaining the uniqueness of the demon


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

 function isDaemonActive ($ pid_file) {
     if (is_file ($ pid_file)) {
         $ pid = file_get_contents ($ pid_file);
         // check for process
         if (posix_kill ($ pid, 0)) {
             // daemon already running
             return true;
         } else {
             // there is a pid file, but there is no process 
             if (! unlink ($ pid_file)) {
                 // I can not destroy the pid-file.  mistake
                 exit (-1);
             }
         }
     }
     return false;
 }

 if (isDaemonActive ('/ tmp / my_pid_file.pid')) {
     echo 'Daemon already active';
     exit;
 }


After demonization, the current PID daemon should be written to the pid-file.

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


That's all you need to know to write demons in PHP. I did not talk about the general access to resources, because this problem is wider than the writing of demons.

Good luck!

An article with syntax highlighting is on my blog .

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


All Articles