PHP un Unix signāli

Kā nu gadījās, kā ne, bet radās vajadzība uzrakstīt programmiņu, kura sēž uz linux servera atmiņā un vairāk vai mazāk regulāri veic visvisādas darbības. Ideoloģiski nekas sarežģīts tas nav:

<?php
 
while (true) {
  // darām nepieciešamās lietas
  sleep(1);
}
 
?>

Palaižam skriptiņu ar /usr/local/bin/php -q skripts.php & (pieņemu, ka php komandrindas versija atrodas direktorijā /usr/local/bin) un viss notiek. Ja nu vajag jamo nobeigt, tad, uzzinot ar ps -u | grep skripts.php jamā PID, rakstām kill -9 [PID] un skripts ir beidzies:

# /usr/local/bin/php -q skripts.php &
[1] 15147
# ps -u | grep skripts.php
laacz    16838  0.1  1.3  3672 1656 pts/3    S    22:23   0:00 /usr/local/bin/php -q skripts.php
# kill -9 15147
#
[1]+  Done                    /usr/local/bin/php -q skripts.php
#

Bet, kā jau var nojaust, es neesmu apmierināts. Ir nepieciešama vismaz viena elementāra lieta &mdash; iespēja šo skriptu apstādināt civilizētā veidā. Civilizēti apstādināt nozīmē ļaut skriptam pabeigt darāmos darbus, pirms beigt savu eksistenci.

Pirmā metode, kuru realizēju, bija gana vienkārša:

<?php
 
while (!defined('DO_QUIT')) {
 
    // Darām nepieciešamās lietas
 
    // Ja mums ir uzradies failiņš 'exit.sig', tad mēs nu saprotam,
    //   ka laiks beigt darbu.
    if (file_exists('exit.sig')) {
      define('DO_QUIT', true);
      @unlink('exit.sig');
    }
 
    sleep(1);
}
 
// te mēs beidzam darāmos darbus (diskonektējam,
//   unlockojam un visu pārējo)
 
?>

Šis variants it kā apmierina. Viss strādā, viss notiek. Taču, gribās kaut ko daiļāku, kaut ko estētiski baudāmāku. Kaut ko tādu, kas ļaujās linuxa signāliem. Piem., pasakot mūsu skripta procesam kill -SIGTERM [mūsu procesa PID]

Te nu talkā nāk PHP extensija pcntl.

Pirmām kārtām, šī ekstensija ir domāta tikai linux videi (tiesa, eksistē arī neoficiāla cygwin versija win32 videi).

Otrām kārtām tā strādās tikai, ja kompilēsiet PHP CLI/CGI modē. Pat tad, ja kompilēsiet to kā moduli, atstājot noklusēto CLI/CGI versijas izveidi un norādot –enable-pcntl tas neizdosies.

Tādējādi man nācās nokompilēt papildus atsevišķu PHP CLI/CGI versiju:

./configure \
--with-zlib-dir=/usr \
--with-mysql=/usr/local/mysql \
--enable-sockets \
--enable-pcntl

Kad nu PHP ar šo ekstensiju ir nokompilēts un novietots zināmā vietā (pieņemsim, ka tekošajā direktorijā), varam sākt darboties ar procesiem.

Pats pirmais moments &mdash; lai skripts spētu apstrādāt signālus, tam ir jādeklarē t.s. ticks:

<?php
    // ... kods ...
    declare(ticks=1);
    // ... kods ...
?>

Lai iestāstītu PHP, ka mums konkrētais signāls jāapstrādā, mēs rakstām iepriekšminēto funkciju apmēram sekojoši:

<?php
 
// Iemācam skriptam apstrādāt signālus
 
declare(ticks=1);
 
// Funkcija, kura tiks izsaukta SIGTERM signāla
//  saņemšanas gadījienā
 
function sigterm_handler($signo) {
  define('DO_QUIT', true);
}
 
// Definējam, ka, saņemot SIGTERM, mēs izsaucam
//  funkciju sigterm_handler(), kurai automātiski
//  parametrā tiek nodots signāla numurs
 
pcntl_signal(SIGTERM, "sigterm_handler");
 
while (!defined('DO_QUIT')) {
 
  // Darām nepieciešamās lietas
 
  sleep(1);
}
 
 
// te mēs beidzam darāmos darbus (diskonektējam,
//   unlockojam un visu pārējo)
 
?>

Daile, kur tu rodies:

# ./php -q skripts.php &
[1] 16838
# ps -u | grep skripts.php
laacz    16838  0.1  1.3  3672 1656 pts/3    S    22:23   0:00 ./php -q skripts.php
# kill -SIGTERM 15147
#
[1]+  Done                    ./php -q skripts.php
#

Dikti jau nu smuki. Mēs esam iemācījušies darīt lietas. Bet, patiesībā, visam būtu jābūt vēl vienkāršāk. Apnīkoši ir visu laiku rakstīt ps -u | grep skripts.php, vai tad ne?

Ir vispārpieņemts, ka šādos gadījumos, kad mēs vēlamies uzzināt kādas patstāvīgi atmiņā esošas programmas PID, vajag meklēt failu programma.pid, kurš tad arī satur ši maģisko PID. Mēs visnotaļ aši pārņemsim šo ideoloģiju, ieviešot failu skrips.pid

Tātad, palaižot skriptu, mums šis fails ir jāizveido, bet beidzot darbu - jāizdzēš.

<?php
# Faila nosaukums, kurš saturēs PID
$pidfile = 'skripts.pid';
# Ar getmypid() iegūstam tekošā skripta PID
$pid = getmypid();
if ($fh = fopen($pidfile, 'w')) {
  fputs($fh, $pid);
  fclose($fh);
} else {
  die('Nav izdevies izveidot ' . $pidfile . '!');
}
 
# ... pārējā skripta daļa ...
 
if (file_exists($pidfile)) {
  unlink($pidfile);
}
?>

Tādējādi mēs, palaižot skriptu, iegūstam papildus failu, kurā atrodas tā PID. Kā to izmantot? Sekojošā komada paskaidros manā vietā:

# ./php -q skripts.php &
[1] 16838
# ps -u | grep skripts.php
laacz    16838  0.1  1.3  3672 1656 pts/3    S    22:23   0:00 ./php -q skripts.php
# kill -SIGTERM `cat skripts.pid`
#
[1]+  Done                    ./php -q skripts.php
#

Attiecīgi, nu jau esošo implementāciju var izmantot startup/shutdown skriptā. Piem., pieņemsim, ka mums ir fails skriptsctl (ar palaišanas [+x] tiesībām)

# ls -al skriptsctl
-rwx------    1 laacz    wheel        1418 Jan 15 10:04 skriptsctl

Un šis skripts izskatās šādi:

#!/bin/bash
 
# PIDs mums glabāsies failā skripts.pid
pidfile="skripts.pid"
 
# Ja mums eksistē skripts.pid, tad uzreiz savācam PID
#  un iebarojam to mainīgajam $pid
if [ -f "$pidfile" ]; then
pid=`cat $pidfile`
fi
 
case "$1" in
# Šajā izvēlē mēs startējam skriptu
'start')
  # Vai mums eksistē fails skripts.pid?
  if [ -f "$pidfile" ]; then
    # Palaižam komandu, kas atgriezīs 1, ja nav procesa
    #   ar tādu PID, un 0, ja ir
    ps -p $pid >/dev/null
    # Vai mums process nav?
    if [ $? -eq 1 ]; then
      # Sāknējam procesu
      ./php -q skripts.php &
    else
      echo "Process jau ir uzstartēts" >/dev/stderr
      exit 1
    fi
  else
    # Sāknējam procesu
    ./php -q skripts.php &
  fi
  ;;
# Šajā izvēlē mēs stopojam skriptu
'stop')
  # Vai mums eksistē fails skripts.pid?
  if [ -f "$pidfile" ]; then
    # Palaižam komandu, kas atgriezīs 1, ja nav procesa
    #   ar tādu PID, un 0, ja ir
    ps -p $pid >/dev/null
    # Vai mums process ir?
    if [ $? -eq 0 ]; then
      # Beidzējam procesu
      kill -SIGTERM $pid
    else
      echo "PID fails ir, bet paša procesa nemaz i nav" >/dev/stderr
      exit 1
    fi
  else
    echo "PID faila nemaz i nav" >/dev/stderr
    exit 1
  fi
  ;;
*)
  # Pārējos gadījumos drukājam helpu
  echo "Usage $0 start|stop"
  ;;
esac
exit 0

Ceru, ka šis materiāls kādam palīdzēs. Nepretendēju uz to, ka visi koda un komandrindas piemēri ir 100% precīzi un bez kļūmēm, taču, ja nu kas - griežieties pie manis :) Tieši tas pats par gramatiku. Atrodams esmu IRCNetā (irc.apollo.lv), #php.lv kanālā.

Vistogaišāko, laacz.

 
php_un_unix_signali.txt · Labota: 2009/02/19 12:32 , labojis andrisp
 
Ja nav norādīts citādi, viki saturs pieejams ar šādas licenzes noteikumiem:CC Attribution-Noncommercial-Share Alike 3.0 Unported
Recent changes RSS feed Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki