Kiedy parsowanie XML w Pythonie crashuje Apache...
Ostatnio podczas próby napisania webowej aplikacji w Pythonie, która miała między innymi parsować pliki XML natrafiłem na bardzo dziwny problem. Skrypt oparty był o framework Django. Podczas użycia metody xml.dom.minidom.parse() aplikacja się wykładała - nie zwracała żadnych błędów, a w przeglądarce widać było tylko białe okno. Jak zwykle przekopywanie polskiego internetu w poszukiwaniu odpowiedzi nic nie dało.
Okazało się, że problem tkwi w bibliotece Expat. Jest ona używana przez moduł xml.dom do parsowania kodu XML. W logach Apache można znaleźć taki oto wpis:
[notice] child pid 3238 exit signal Segmentation fault (11)
Okazuje się, że Apache może domyślnie używać innej wersji expat`a niż Python. Dzieją się wtedy różne dziwne rzeczy, w które nie będę się wgłębiał, bo właściwie nie ma po co. Ale jak sprawdzić, czy to jest rzeczywiście źródłem naszego problemu?
Najpierw należy sprawdzić jakiej wersji biblioteki używa Apache. W tym celu wydajemy polecenie (ścieżka do httpd może się nieco różnić od podanej):
ldd /usr/sbin/httpd | grep expat
Jako wynik powinniśmy otrzymać coś w rodzaju:
libexpat.so.0 => /usr/lib/libexpat.so.0 (0xb7e8c000)
Interesuje nas tylko ścieżka podana po strzałce. Wskazuje ona na plik używany przez nasz serwer. Sama nazwa nam nic nie powie, dlatego należy wyciągnąć numer wersji:
strings /usr/lib/libexpat.so.0 | grep expat_
Otrzymana odpowiedź powinna być podobna do tej:
expat_1.95.8
Musimy się jeszcze upewnić, że żadne zmienne systemowe nie wpływają na ścieżkę bibliotek ładowanych do Apache (np. LD_LIBRARY_PATH) i dowiedzieć jaki proces uruchomił serwer. W tym celu wykonujemy polecenie:
ps aux | grep http | head -3
Uwagę należy zwrócić na drugą kolumnę, w której to pokazany jest identyfikator procesu. Następnie wykonujemy komendę:
sudo lsof -p PID | grep expat
, gdzie PID należy zastapić identyfikatorem właściwego procesu. W ten sposób otrzymaliśmy listę plików, używanych przez dany proces. Przykładowy wynik polecenia:
httpd 3625 root mem REG 253,0 123552 6409040 /usr/lib/libexpat.so.0.5.0
Jak widać plik używany przez Apache okazał się nieco inny niż otrzymany wcześniej. Aby sprawdzić ostatecznie wersję Expat`a wykonujemy wcześniej już używane polecenie strings:
strings /usr/lib/libexpat.so.0.5.0 | grep expat_
Sprawdzenie wersji expat`a używanej przez Pythona jest nieco łatwiejsze:
python >>> import pyexpat >>> pyexpat.version_info
W odpowiedzi możemy dostać:
(1, 95, 7)
Jak widać Apache i Python używają różnych wersji Expat (odpowiednio 1.95.8 i 1.95.7). Sprawienie, aby oba programy ładowały odpowiednią wersji biblioteki wykonujemy polecenie:
LD_PRELOAD=/usr/lib/libexpat.so.0.5.0
Można teraz ponownie sprawdzić wersję ładowaną przez Pythona wykonując kroki opisane w poprzednim paragrafie.
Źródło: http://www.dscpl.com.au/wiki/ModPython/Articles/ExpatCausingApacheCrash
Dodaj komentarz