PHP STDERR átirányítás
Sziasztok!
Éppen azon próbálkozom, hogy PHP-ban daemont írjak. Egészen sok problémát már megoldottam, de egyet nem sikerült. Ha a PHP-ban becsukom az
Normális működés esetén a PHP-nak nem kellene használnia az
Mit javasoltok erre?
(Egyébként a cucc open source lesz, rövidesen szeretném majd prezentálni itt a Weblaboron is.)
Teszt kód, ittStrace kimenet:
■ Éppen azon próbálkozom, hogy PHP-ban daemont írjak. Egészen sok problémát már megoldottam, de egyet nem sikerült. Ha a PHP-ban becsukom az
STDERR
-t, akkor utána hiába nyitom újra, nem ugyanaz lesz a file descriptor number, ezért EBADF (Bad file descriptor)
hibával elszáll. (Ezt egyékbént nem egészen értem, hogy miért.)Normális működés esetén a PHP-nak nem kellene használnia az
STDERR
-t, de szeretnék lekezelni minden hibalehetőséget.Mit javasoltok erre?
(Egyébként a cucc open source lesz, rövidesen szeretném majd prezentálni itt a Weblaboron is.)
Teszt kód, itt
STDOUT
-al megvalósítva:
<?php
ini_set('display_errors', true);
error_reporting(E_ALL);
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
fopen('/tmp/stdin', 'rw');
fopen('/tmp/stdout', 'rw');
fopen('/tmp/stderr', 'rw');
echo('test');
munmap(0x7f57246bb000, 207) = 0
close(3) = 0
munmap(0x7f57247f0000, 4096) = 0
close(0) = 0
munmap(0x7f57246bd000, 4096) = 0
close(1) = 0
munmap(0x7f57246bc000, 4096) = 0
close(2) = 0
gettimeofday({1300125804, 921571}, NULL) = 0
lstat("/tmp/stdin", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lstat("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=4096, ...}) = 0
open("/tmp/stdin", O_RDONLY) = 0
fstat(0, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(0, 0, SEEK_CUR) = 0
close(0) = 0
gettimeofday({1300125804, 922135}, NULL) = 0
lstat("/tmp/stdout", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
open("/tmp/stdout", O_RDONLY) = 0
fstat(0, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(0, 0, SEEK_CUR) = 0
close(0) = 0
gettimeofday({1300125804, 922565}, NULL) = 0
lstat("/tmp/stderr", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
open("/tmp/stderr", O_RDONLY) = 0
fstat(0, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(0, 0, SEEK_CUR) = 0
close(0) = 0
write(1, "test", 4) = -1 EBADF (Bad file descriptor)
munmap(0x7f57246be000, 528384) = 0
close(3) = 0
munmap(0x7f57247f0000, 4096) = 0
close(0) = 0
munmap(0x7f57246bd000, 4096) = 0
close(1) = 0
munmap(0x7f57246bc000, 4096) = 0
close(2) = 0
gettimeofday({1300125804, 921571}, NULL) = 0
lstat("/tmp/stdin", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lstat("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=4096, ...}) = 0
open("/tmp/stdin", O_RDONLY) = 0
fstat(0, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(0, 0, SEEK_CUR) = 0
close(0) = 0
gettimeofday({1300125804, 922135}, NULL) = 0
lstat("/tmp/stdout", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
open("/tmp/stdout", O_RDONLY) = 0
fstat(0, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(0, 0, SEEK_CUR) = 0
close(0) = 0
gettimeofday({1300125804, 922565}, NULL) = 0
lstat("/tmp/stderr", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
open("/tmp/stderr", O_RDONLY) = 0
fstat(0, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lseek(0, 0, SEEK_CUR) = 0
close(0) = 0
write(1, "test", 4) = -1 EBADF (Bad file descriptor)
munmap(0x7f57246be000, 528384) = 0
Amit én csinálok: Nem
Nem nyitok újra semmilyen descriptort (kicsit hacky-nak érzem ezt az "átirányítás" dolgot), hanem csak nyomok egy ignore_user_abort(true);-t, ha esetleg bármilyen véletlen kimenet menne a scriptből, akkor mégse álljon le az egész szó nélkül.
Ha ugyanis egy CLI-ből futott scriptben bezárod az STDOUT-ot és a scripted történetesen mégis tol valamit a kimenetre, a PHP azt hiszi, hogy weboldalt hajt végre és a kliens bezárta a kapcsolatot (nem tud írni az STDOUT-ra) így mindenféle üzenet nélkül leáll.
Mindneképpen szükséged van nyitott STDERR-re?
Ezen kívül a blogodon írott daemon implementációhoz (remek cikk egyébként!) még egy minimális adalék: miután a processz session és pg leader lesz, még egyszer forkolnod kell (kilépni a szülő processzből), hogy "elereszd" a tty-t.
Nem feltétlen
Ami a blogomon írt daemon implementációt illeti, legjobb tudomásom szerint a második fork csak ahhoz kell, hogy ne kelljen zombi processzeket kezelni, mivel ha a process group leader kilép, akkor onnantól az init adoptálja a gyerekprocesszeket. Ergó a programozói (egészséges) lustaság egyik fajtája. Én inkább kezelek zombikat.
Szerk: megnéztem ignore_user_abort-tal is, becsukott FD-vel tök mindegy, be van-e kapcsolva, így is, meg úgy is elhasal. Ergó nem használ semmit.
Szerk 2: Utána néztem a double forknak, itt van egy egész jó leírás róla: http://stackoverflow.com/questions/881388/what-is-the-reason-for-performing-a-double-fork-when-creating-a-daemon
Második fork témában:
Terminál
Valószínűleg soha, de ha már
Mondjuk egy socket server esetén például nem feltétlen kell "megdögleni" a szülő processzben, hanem egy pcntl_wait-tel figyelni a meghaló forkokat és szépen újakat indítani a helyükre (asszonyról lemászás elkerülése végett ;))
Guardian
Szerk: megnéztem
Nekem csukott STDOUT-ra való írásnál abszolút tökéletesen működik, STDERR-rel pedig a záró echo lefut, igaz, warninggal:
Warning: fopen(php://stderr): failed to open stream: operation failed in /var/phpprojects/php_tracker/test.php on line 5
Call Stack:
0.0035 320116 1. {main}() /var/phpprojects/php_tracker/test.php:0
0.0036 319896 2. fopen(string(12), string(1)) /var/phpprojects/php_tracker/test.php:5
All OK
[root@wcphp test]#
Ja...
Mégsem hasal el
test.php
[root@wcphp test]# php test.php
[root@wcphp test]# ls
test.php test_was_ok_20110314211156
[root@wcphp test]#
ignore_user_abort nélkül viszont igen:
test.php
[root@wcphp test]# php test.php
[root@wcphp test]# ls
test.php
[root@wcphp test]#
PHP 5.3.5 (cli) (built: Jan 7 2011 18:29:01)
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
with eAccelerator v0.9.6.1, Copyright (c) 2004-2010 eAccelerator, by eAccelerator
with Xdebug v2.1.0, Copyright (c) 2002-2010, by Derick Rethans
[root@wcphp test]#
Jogos
Ergó tied a pont, probléma megoldva. Nem úgy, ahogy szerettem volna, de jó lesz.
Király, izgatottan várjuk a
Van
Parent-child process
Nem kell
Vagy arra gondolsz, hogy a gyerekprocesszt indítsam popen-nel és kapjam el az STD ki/bemeneteit?
Valóban... error_handler()
De.
Milyen verziójú PHP?
http://bugs.php.net/bug.php?id=38199
Emellett csak kis stilisztikai dolog:
STDIN csak olvasható, STDOUT és STDERR pedig csak írható, bár nem hiszem, hogy ez problémát okozna.
5.3
monit
A memory leak-ekre lehet
Ilyenkor csak arra kell figyelni, hogy a szülő tuti ne okozzon galibát, szóval lehetőleg a legminimálisabb kód kerüljön a fork elé és alaposan át kell nézni a pcntl_work környékén a loopot.
Monit
Ami a PHP-t illeti, nekem van olyan daemonom és nem tűnt úgy, mint ha eszeveszett módon memleakelne, pedig dolgozik rendesen. Persze az is hozzá tartozik, hogy mindenhol explicit módon unsetelve vannak a dolgok, stb. Talán azért, mert az 5.3-ban sok mindent javítottak az enginen. Ha memleakel, akkor majd max nekiesek debuggerrel és kikeresem, hogy hol van, úgyis ideje lenne, hogy valaki végre rendbe rakja a PHP-t.
memleak
Hobbyprojekt
Végre volt egy kis időm hogy
Az absztrakt "forkoló" osztály itt található:
PHPTracker_Threading_Forker
És itt egy implementációja:
PHPTracker_Seeder_Peer
Építő jellegű kritikát szívesen fogadok.
Ha már itt tartunk, ti hogyan egységtesztelnétek egy ilyen osztályt?
most talaltam, nagyon
http://andytson.com/blog/2010/05/daemonising-a-php-cli-script-on-a-posix-system/
ugy tunik, hogy, ha lezarod a STDIN, STDOUT, STDERR fd-ket, majd _megfelelo sorrendben_ nyitsz uj file descriptor-okat, akkor azok at fogjak venni a STDIN, STDOUT, STDERR helyet...
mukodo pelda, /home/tyrael-t ertelemszeruen at kell irni:
Tyrael
Nem
halisten en nem hasznalok mas
ha Mac-en vagy FreeBSD-n nem megy, az hataresetet, AIX-on meg hasonlon nem tervezek a kozeljovoben sem php-t futtatni. :)
Tyrael
Ez valóban így működik, C-ben
Mivel a PHP nem használja az STDERR-t, és normális esetben a daemonod nem ír semmit a kimenetre (display erros off, csak mint a weben), ha ezt nem teszed meg, nem jelent komoly kockázatot.
Viszont az ignore_user_abort fontos, ha esetleg mégis menne a kimenetre valami a scripted ne haljon le.
További magyarázat:
biztos hogy nekem akartal
tisztaban vagyok vele, hogy hogy mukodik normal esetben az input/output redirection, az egesz szal arrol szolt, hogy nem lehetne-e valahogy azt megoldani, hogy ne a scripted inditasanal legyen ez az atiranyitas elkovetve, hanem ugyanugy mint a damonizalt alkalmazasoknal megszokott, a parent process forkolja ki a workert es detacholja magat az eredeti STDIN/STDOUT/STDERR-rol.
ahogy tobben le is irtak/irtuk, bezarni viszonylag egyszeru ezeket, sima fclose mukodik szepen, viszont ilyenkor ha barmi kodod irna/olvasna ezekrol a file descriptorokrol, akkor elszallna a kodod hibaval.
ezert lenne jobb, ha nem csak bezarni tudnad oket, hanem lecserelni.
egesz eddig nem tudtam rola, hogy lenne lehetoseg a futo php scriptbol meglepni ezt (kulso exec meg ilyesmi nelkul), viszont a fentebb linkelt blogpostban leirja a szerzo, hogy ha lezarod ezeket az FD-ket, majd __megfelelo sorrendben__ nyitsz harom uj FD-t, akkor a PHP ezeket fogja STDIN/STDOUT/STDERR-kent hasznalni.
es ez igy lehetove teszi, amit szerettunk volna.
viszont janoszen(proclub) bedobta, hogy ez nem minden Unix like rendszeren mukodne, mert nem mindegyik osztja az FD-ket szekvencialisan novekvo sorrendben, ezert az ujranyitasos trukk ott nem mukodne valoszinuleg.
ezert irta hogy dup2-t kellene hasznalni, erre irtam, hogy az php-bol nem elerheto kozvetlenul, illetve kerdeztem, hogy mely Unix like rendszerek erintettek ebben, mert ha csak nagyon obscure rendszereken problema ez, akkor engem nem zavar.
ha megis felreertettelek volna, akkor javits ki nyugodtan.
ignore_user_abort pedig csak akkor szukseges, hogyha lezarod de nem lecsereled az FD-ket, mi mar nem errol beszeltunk itt.
Tyrael
Tisztázás
Ergó amíg nincs dup2 támogatás a PHP-ban, Tyrael megoldása lesz a jó. Amint nő bele, érdemes azt használni. (Nem kizárt, hogy nekiállok belepatchelni, megfelelő tanuló feladatnak tűnik nekem.)
Ha nem nyitod újra a 0,1,2-es
igen, ezt nagyon fontos kiemelni.
ha ugyanott nyitja a kodod az FD-ket ahol 2 sorral feljebb bezarod oket, akkor viszonylag kicsi az eselye, hogy nyilik valami FD, de persze elofordulhat:
- valami hiba miatt lefut egy error_handler
- erkezik egy signal es lefut a ra felhuzott signal handler
- fatal error miatt megall a kod a bezaras utan, de a megnyitas elott, es a register_shutdown_function-nel felhuzott callback, esetleg ugyanez ob_start-tal
szoval ha zarod oket, akkor szerintem is mindenkepp nyiss helyette masikat, de nem linux környezetben inkabb ne a scripten belulrol intezd az atiranyitast.
Tyrael
Érdemes (lenne) dup2-vel
ugy tunik, hogy Unix speci szerint a legalacsonyabb FD-t kell kiosztania az OS-nek.
http://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
"The open() function shall return a file descriptor for the named file that is the lowest file descriptor not currently open for that process."
Tyrael
Nem garantálja
igen, nem is arra a reszre
btw. inditottam neki egy threadet az internalson:
http://marc.info/?l=php-internals&m=130416511430852&w=2
Tyrael