Rozwiązanie przedstawione w jednym z poprzednich artykułów pozwala na realizację daemona w PHP dzięki wykorzystaniu mechanizmu forkowania procesów. Niestety podatne jest na powstawanie tzw. procesów zombie - proces dziecko staje się procesem zombie dopóki proces główny nie obsłuży jego statusu wyjścia (ang. exit code) bądź sam (proces główny) nie zakończy działania. W efekcie, przez procesy zombie, zajmowane są sloty w tablicy procesów (ang. process table) co w kontekście długiego działania daemona będzie stanowiło problem.
SIGCHLD, SIG_IGN
Omawiana powyżej implementacja daemona pozwala na powstawanie procesów zombie, ale również obsługuje takie procesy - w momencie nadejścia nowego połączenia z daemonem wszystkie procesy zombie oczekujące na obsługę tj. połączenia które zrealizowały swoje zadanie bądź zakończyły swoje działanie z powodu timeoutu, zostaną zakończone. Niestety, w okresach kiedy nie są obsługiwane żadne połączenia istnieje możliwość występowania dużej liczby procesów zombie. Rozwiązaniem tego problemu może być ignorowanie sygnałów SIGCHLD przez proces główny. Wówczas procesy dzieci będą umierały natychmiast, bez oczekiwania na obsługę statusu wyjścia przez proces rodzica.
SIGUSR2
Jednym z wymagań stawianych procesowi głównemu jest kontrola liczby połączeń - chcemy zapobiegać nadmiernemu tworzeniu procesów w systemie. W poprzednim rozwiązaniu kontrolowane było to poprzez listę aktywnych procesów dzieci (oraz ich identyfikatorów PID). Możliwe było to dzięki obsłudze SIGCHLD, gdzie dokładnie wiedzieliśmy który proces kończy swoje działanie. Z kolei w omawianym tutaj rozwiązaniu, zapobiegającym powstawaniu procesów zombie, nie znamy identyfikatora procesu kończącego działanie, ponieważ nie obsługujemy SIGCHLD. Jednak funkcjonalność tą zrealizujemy w inny sposób - proces główny będzie kontrolował liczbę aktywnych procesów zwiększając ten licznik w momencie utworzenia nowego procesu do obsługi połączenia oraz zmniejszał go obsługując SIGUSR2 (procesy dzieci umierając będą wysyłały ten sygnał do procesu głównego). Obsługa sygnałów SIGUSR2 również będzie następowała w momencie nadejścia nowego połączenia, ale nie będzie to stanowiło problemu gdyż zanim zostanie utworzony nowy proces, licznik aktywnych procesów zostanie zaktualizowany, a tym samym będziemy wiedzieli czy możemy obsłużyć nowe połączenie.
SIGINT, SIGTERM
Do rozwiązania pozostaje jeszcze jedna kwestia - proces główny umierając, powinien także ubijać aktywne procesy dzieci. Ponieważ nie przechowujemy listy identyfikatorów aktywnych procesów przez proces główny daemona, nie możemy ubijać poszczególnych procesów wysyłając SIGINT / SIGTERM do poszczególnych procesów dzieci. Zamiast tego, proces parenta umierając będzie wysyłał SIGINT / SIGTERM do wszystkich procesów o tym samym identyfikatorze grupy procesów co proces główny. Poszczególne procesy dzieci nie zostaną do końca odłączone od procesu rodzica (nie będą stawały się liderami sesji), a więc tym samym będą posiadały ten sam identyfikator grupy procesów.
Przedstawione powyżej rozwiązanie stanowi tylko jedno z możliwych rozwiązań problemów zombie. Zakres opisanych tutaj zmian jest niewielki względem oryginalnego rozwiązania, a więc będzie prosty do wprowadzenia. Docelowe rozwiązanie i tak będzie zależało od wymagań stawianemu Waszemu daemonowi, niemniej moje rozwiązanie może stanowić dobrą bazę do dalszych prac.
Kompletny kod znajdziecie tutaj.
Przydatne linki:
- http://en.wikipedia.org/wiki/Zombie_process
- http://pleac.sourceforge.net/pleac_php/processmanagementetc.html
- http://php.net/manual/en/function.pcntl-fork.php
- http://fixunix.com/unix/533215-how-avoid-zombie-processes.html
- http://stackoverflow.com/questions/9976441/terminating-zombie-child-processes-forked-from-socket-server
- http://stackoverflow.com/questions/16238510/pcntl-fork-results-in-defunct-parent-process
- http://unix.stackexchange.com/questions/11172/how-can-i-kill-a-defunct-process-whose-parent-is-init
- http://lubutu.com/code/spawning-in-unix
tagi: php , daemons , fork , multitasking , pcntl , signals , sockets , zombie process