Discussion on PHP process management

  • 2021-11-29 23:23:53
  • OfStack

This article is a supplement and improvement to the previous article. Create a main (master) process, install a timer in the main process, check the queue length every 5 minutes, calculate the required worker process according to the queue length,

Then create or kill child processes. The advantage of this is to prevent queues from piling up and tasks from being processed in time. To update the business code, only reload operation is needed.

The whole process has the following knowledge points:

To create a daemon:

Set default file permissions fork1 processes, parent process exits Call setsid to create 1 new session Change the current working directory to the root directory Close file descriptors that are no longer needed

Using signals to realize timers
The timer in the previous article depends on the timing task of the system. This time, it is realized by alarm clock signal. The versions of php below 5.3. 0 depend on ticks, and the versions of 5.3. 0 and above can use pcntl_signal_dispatch

Signal: Provides a method of asynchronous event processing. When a signal appears, the process has the following three ways to process the signal

Ignore this signal Capture signal Perform the default action of the system, and the default action of most signals is to terminate the process

Common signal
SIGKILL and SIGSTOP are two kinds of signals that can not be ignored and captured by users

SIGINT (2): Program termination signal, usually Ctrl-C), to inform the foreground process group to terminate the process

SIGQUIT (3): Similar to SIGINT, but controlled by the QUIT character (usually Ctrl +/). The core file is generated when the process receives this message and exits

SIGKILL (9): Terminate the process immediately and cannot be ignored for capture or blocking

SIGUSR1 (10): User-defined signal

SIGUSR2 (12): For user use

SIGALRM (14): Alarm clock signal

SIGTERM (15): Terminates the process and can be captured by the program so that the process can finish the cleanup operation.

SIGSTOP (19): Stops 1 process, which is not finished, but only pauses execution

Preventing zombie processes
All processes will become zombie processes when they exit. At this time, if the parent process is still running and wait or waitpid is not called, the resources occupied by the zombie process will not be cleaned. If the parent process has terminated, the zombie process will be cleaned by the init process.

Transfer business codes, the main codes are as follows

One point to note is that when creating a daemon to close the input and output and error the output stream, if there are output characters such as echo behind the code, a fatal error will occur, and it is necessary to redirect the output stream to/dev/null in the php code. Or redirect when the terminal starts the process


<?php
define('PROC_MAX', 10);
define('PROC_MIN', 5);
 
$cmd = $argv[1];
$aPid = [];
$pidFile = __DIR__ . '/pid.pid';
$pid = file_get_contents($pidFile);
 
switch($cmd){
 case 'start' :
  if(posix_kill($pid, 0)){
   echo "gamelog process is already exsits!\n";
   return false;
  }
  // Set default file permissions 
  umask(022);
  //fork
  $pid = pcntl_fork();
  if($pid < 0){
   exit('fork error!');
  }else if($pid > 0){
   exit;
  }
  // Disengage from the current terminal 
  posix_setsid();
  // Change the current working directory to the root directory 
  chdir('/');
  // Close the file descriptor 
  fclose(STDIN);
  fclose(STDOUT);
  fclose(STDERR);
  // Redirect input and output 
  global $STDOUT, $STDERR;
  $STDOUT = fopen('/dev/null', 'a');
  $STDERR = fopen('/dev/null', 'a');
   
  cli_set_process_title('gamelog:master');
  $pid = posix_getpid();
  file_put_contents($pidFile, $pid);
  // Alarm clock signal 
  pcntl_signal(SIGALRM, function() use (&$aPid) {
   pcntl_alarm(300);
   $workerNum = mt_rand(1, 20);// Check the number of processes you need here 
   $daemonNum = count($aPid);
    
   ($workerNum > PROC_MAX) && ($workerNum = PROC_MAX);
   if($daemonNum < $workerNum){
    $procNum = $workerNum - $daemonNum;
    $procNum = max(PROC_MIN, $procNum);
    for($p = 1; $p <= $procNum; $p++){
     $pid = pcntl_fork();
     if ($pid < 0) {
      exit('fork error!');
     } else if ($pid == 0) {
      cli_set_process_title('gamelog:worker');
      while (true) {
       //do your work
       usleep(100);
      }
      exit();
     } else {
      $aPid[] = $pid;
     }
    }
   }else if($daemonNum > $workerNum){
    $wokerNum = max($wokerNum, PROC_MIN);
    $killNum = $daemonNum - $workerNum;
    foreach($aPid as $key=>$pid){
     if(posix_kill($pid, SIGKILL)){
      unset($aPid[$key]);
      if(--$killNum <= 0){
       break;
      }
     }
    }
   }
  }, false);
   
  pcntl_signal(SIGUSR1, function() use (&$aPid, $pid){
   foreach($aPid as $key=>$chpid){
    if(!posix_kill($chpid, SIGKILL)){
     echo "kill child $chpid faild\n";
    }
   }
   posix_kill($pid, SIGKILL);
  }, false);
   
  pcntl_signal(SIGUSR2, function() use (&$aPid, $pid){
   foreach($aPid as $key=>$chpid){
    if(!posix_kill($chpid, SIGKILL)){
     echo "kill child $chpid faild\n";
    }
   }
   if(!posix_kill($pid, SIGALRM)){
    echo "restart gamelog faild\n";
   }
  }, false);
   
  posix_kill($pid, SIGALRM);
  while (true) {
   pcntl_signal_dispatch();
   $pid = pcntl_wait($status, WUNTRACED);// Non-blocking 
  }
  break;
  
 case 'stop' :
  if(!posix_kill($pid, SIGUSR1)){
   exit('stop gamelog process error!');
  }
  break;
 case 'reload' :
  if(!posix_kill($pid, SIGUSR2)){
   exit('restop gamelog process error!');
  }
  break;
 default :
  echo "Useage php signal.php start|stop|reload\n";
}

Related articles: