Uwaga: to pytanie zostało pierwotnie zadane tutaj, ale czas nagród minął, mimo że w rzeczywistości nie znaleziono akceptowalnej odpowiedzi. Ponownie zadaję to pytanie, uwzględniając wszystkie szczegóły podane w pierwotnym pytaniu.
Skrypt w Pythonie uruchamia zestaw funkcji klasowych co 60 sekund za pomocą modułu schedule :
# sc is a sched.scheduler instance
sc.enter(60, 1, self.doChecks, (sc, False))
Skrypt działa jako proces zdemonizowany przy użyciu kodu znajdującego się tutaj .
Szereg metod klas, które są wywoływane jako część doChecks, używa modułu podprocesu do wywoływania funkcji systemowych w celu uzyskania statystyk systemowych:
ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE).communicate()[0]
Działa to dobrze przez pewien czas, zanim cały skrypt ulegnie awarii z następującym błędem:
File "/home/admin/sd-agent/checks.py", line 436, in getProcesses
File "/usr/lib/python2.4/subprocess.py", line 533, in __init__
File "/usr/lib/python2.4/subprocess.py", line 835, in _get_handles
OSError: [Errno 12] Cannot allocate memory
Wynik działania free -m na serwerze po awarii skryptu to:
$ free -m
total used free shared buffers cached
Mem: 894 345 549 0 0 0
-/+ buffers/cache: 345 549
Swap: 0 0 0
Na serwerze działa CentOS 5.3. Nie mogę odtworzyć na moich własnych skrzynkach CentOS ani z żadnym innym użytkownikiem zgłaszającym ten sam problem.
Próbowałem wielu rzeczy, aby debugować to, jak sugerowano w pierwotnym pytaniu:
Rejestrowanie wyników działania free -m przed i po wywołaniu Popen. Nie ma znaczącej zmiany w zużyciu pamięci, tj. Pamięć nie jest stopniowo wykorzystywana w trakcie wykonywania skryptu.
Dodałem close_fds = True do wywołania Popen, ale to nie miało znaczenia - skrypt nadal zawieszał się z tym samym błędem. Sugerowane tutaj i tutaj .
Sprawdziłem rlimity, które pokazały (-1, -1) zarówno na RLIMIT_DATA, jak i RLIMIT_AS, jak sugerowano tutaj .
Artykuł sugerował mającą swap może być przyczyną, ale wymiany jest rzeczywiście dostępne na żądanie (w zależności od hostingowego) i to również było sugerowane jako fałszywe powodować tutaj .
Procesy są zamykane, ponieważ jest to zachowanie przy użyciu .communicate (), zgodnie z kopią zapasową kodu źródłowego Pythona i komentarzami tutaj .
Wszystkie testy można znaleźć na GitHub tutaj, z funkcją getProcesses zdefiniowaną w linii 442. Jest ona wywoływana przez doChecks () począwszy od linii 520.
Skrypt został uruchomiony ze strace z następującymi danymi wyjściowymi przed awarią:
recv(4, "Total Accesses: 516662\nTotal kBy"..., 234, 0) = 234
gettimeofday({1250893252, 887805}, NULL) = 0
write(3, "2009-08-21 17:20:52,887 - checks"..., 91) = 91
gettimeofday({1250893252, 888362}, NULL) = 0
write(3, "2009-08-21 17:20:52,888 - checks"..., 74) = 74
gettimeofday({1250893252, 888897}, NULL) = 0
write(3, "2009-08-21 17:20:52,888 - checks"..., 67) = 67
gettimeofday({1250893252, 889184}, NULL) = 0
write(3, "2009-08-21 17:20:52,889 - checks"..., 81) = 81
close(4) = 0
gettimeofday({1250893252, 889591}, NULL) = 0
write(3, "2009-08-21 17:20:52,889 - checks"..., 63) = 63
pipe([4, 5]) = 0
pipe([6, 7]) = 0
fcntl64(7, F_GETFD) = 0
fcntl64(7, F_SETFD, FD_CLOEXEC) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7f12708) = -1 ENOMEM (Cannot allocate memory)
write(2, "Traceback (most recent call last"..., 35) = 35
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/usr/bin/sd-agent/agent."..., 52) = 52
open("/home/admin/sd-agent/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/home/admin/sd-agent/dae"..., 60) = 60
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/usr/bin/sd-agent/agent."..., 54) = 54
open("/usr/lib/python2.4/sched.py", O_RDONLY|O_LARGEFILE) = 8
write(2, " File \"/usr/lib/python2.4/sched"..., 55) = 55
fstat64(8, {st_mode=S_IFREG|0644, st_size=4054, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000
read(8, "\"\"\"A generally useful event sche"..., 4096) = 4054
write(2, " ", 4) = 4
write(2, "void = action(*argument)\n", 25) = 25
close(8) = 0
munmap(0xb7d28000, 4096) = 0
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/usr/bin/sd-agent/checks"..., 60) = 60
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/usr/bin/sd-agent/checks"..., 64) = 64
open("/usr/lib/python2.4/subprocess.py", O_RDONLY|O_LARGEFILE) = 8
write(2, " File \"/usr/lib/python2.4/subpr"..., 65) = 65
fstat64(8, {st_mode=S_IFREG|0644, st_size=39931, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000
read(8, "# subprocess - Subprocesses with"..., 4096) = 4096
read(8, "lso, the newlines attribute of t"..., 4096) = 4096
read(8, "code < 0:\n print >>sys.st"..., 4096) = 4096
read(8, "alse does not exist on 2.2.0\ntry"..., 4096) = 4096
read(8, " p2cread\n # c2pread <-"..., 4096) = 4096
write(2, " ", 4) = 4
write(2, "errread, errwrite)\n", 19) = 19
close(8) = 0
munmap(0xb7d28000, 4096) = 0
open("/usr/lib/python2.4/subprocess.py", O_RDONLY|O_LARGEFILE) = 8
write(2, " File \"/usr/lib/python2.4/subpr"..., 71) = 71
fstat64(8, {st_mode=S_IFREG|0644, st_size=39931, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000
read(8, "# subprocess - Subprocesses with"..., 4096) = 4096
read(8, "lso, the newlines attribute of t"..., 4096) = 4096
read(8, "code < 0:\n print >>sys.st"..., 4096) = 4096
read(8, "alse does not exist on 2.2.0\ntry"..., 4096) = 4096
read(8, " p2cread\n # c2pread <-"..., 4096) = 4096
read(8, "table(self, handle):\n "..., 4096) = 4096
read(8, "rrno using _sys_errlist (or siml"..., 4096) = 4096
read(8, " p2cwrite = None, None\n "..., 4096) = 4096
write(2, " ", 4) = 4
write(2, "self.pid = os.fork()\n", 21) = 21
close(8) = 0
munmap(0xb7d28000, 4096) = 0
write(2, "OSError", 7) = 7
write(2, ": ", 2) = 2
write(2, "[Errno 12] Cannot allocate memor"..., 33) = 33
write(2, "\n", 1) = 1
unlink("/var/run/sd-agent.pid") = 0
close(3) = 0
munmap(0xb7e0d000, 4096) = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x589978}, {0xb89a60, [], SA_RESTORER, 0x589978}, 8) = 0
brk(0xa022000) = 0xa022000
exit_group(1) = ?
/var/log/messages
lubdmesg
polecenie.Odpowiedzi:
Zgodnie z ogólną zasadą (tj wanilia jądrach),
fork
/clone
porażki zENOMEM
nastąpić w szczególności z powodu bądź uczciwy do Boga out-of-pamięci stanu (dup_mm
,dup_task_struct
,alloc_pid
,mpol_dup
,mm_init
itd rechot), albo dlategosecurity_vm_enough_memory_mm
zawiodły cię podczas egzekwowania na politykę overcommitu .Zacznij od sprawdzenia rozmiaru maszyny wirtualnej procesu, któremu nie udało się rozwidlić w momencie próby forkowania, a następnie porównaj z ilością wolnej pamięci (fizycznej i wymiany) w odniesieniu do polityki overcommit (podłącz liczby).
W twoim konkretnym przypadku należy pamiętać, że Virtuozzo ma dodatkowe kontrole w egzekwowaniu nadmiernego zobowiązania . Ponadto, nie jestem pewien, ile kontroli naprawdę mają od wewnątrz kontenera, na zamiany i konfiguracji overcommitu (w celu wywarcia wpływu na wynik egzekucji).
Teraz, aby faktycznie iść do przodu, powiedziałbym, że masz dwie opcje :
PAMIĘTAJ, że wysiłek związany z kodowaniem może pójść na marne, jeśli okaże się, że to nie ty, ale jakiś inny facet w innej instancji na tym samym serwerze, na którym działa amock.
Jeśli chodzi o pamięć, wiemy już, że
subprocess.Popen
używafork
/clone
pod maską , co oznacza, że za każdym razem, gdy to nazywasz, żądasz jeszcze raz tyle pamięci, ile Python już pochłania , tj. W setkach dodatkowych MB, wszystko po to, aby to zrobićexec
słaby plik wykonywalny o rozmiarze 10 kB, taki jakfree
lubps
. W przypadku niekorzystnej polityki nadmiernego zaangażowania wkrótce zobaczyszENOMEM
.Alternatywami dla
fork
tych, które nie mają tabel stron nadrzędnych itp., Są problemy z kopiowaniemvfork
iposix_spawn
. Ale jeśli nie masz ochoty na przepisywanie fragmentówsubprocess.Popen
w zakresievfork
/posix_spawn
, rozważ użyciesuprocess.Popen
tylko raz, na początku swojego skryptu (gdy ślad pamięci Pythona jest minimalny), aby odrodzić skrypt powłoki, który następnie uruchamiafree
/ps
/sleep
i cokolwiek innego w pętla równoległa do twojego skryptu; sonduj dane wyjściowe skryptu lub czytaj je synchronicznie, być może z oddzielnego wątku, jeśli masz inne rzeczy, którymi musisz zająć się asynchronicznie - wykonaj przetwarzanie danych w Pythonie, ale pozostaw rozwidlenie procesowi podrzędnemu.JEDNAK , w twoim konkretnym przypadku możesz pominąć wywoływanie
ps
ifree
całkowicie; te informacje są łatwo dostępne w Pythonie bezpośrednio zprocfs
, niezależnie od tego, czy zdecydujesz się uzyskać do nich dostęp samodzielnie, czy za pośrednictwem istniejących bibliotek i / lub pakietów . Jeślips
ifree
byłyby jedynymi narzędziami, z których korzystałeś, możesz całkowicie wyeliminowaćsubprocess.Popen
.Wreszcie, cokolwiek zrobisz, jeśli o
subprocess.Popen
to chodzi, jeśli twój skrypt wycieknie z pamięci, w końcu i tak uderzysz w ścianę. Miej go na oku i sprawdź , czy nie ma wycieków pamięci .źródło
gc.collect()
tuż przedtemsubprocess.Popen
pomaga w przypadkach, gdy odśmiecacz nie działał przez jakiś czas./proc/fd/maps
Śledzenie, aby ustalić, czy w rzeczywistości problemem jest nadmierna pamięćPatrząc na wynik
free -m
, wydaje mi się, że w rzeczywistości nie masz dostępnej pamięci wymiany. Nie jestem pewien, czy w Linuksie zamiana zawsze będzie dostępna automatycznie na żądanie, ale miałem ten sam problem i żadna z odpowiedzi tutaj naprawdę nie pomogła. Dodanie pamięci wymiany rozwiązało jednak problem w moim przypadku, więc ponieważ może to pomóc innym osobom borykającym się z tym samym problemem, publikuję odpowiedź na temat dodawania 1 GB wymiany (w systemie Ubuntu 12.04, ale powinno działać podobnie w innych dystrybucjach).Możesz najpierw sprawdzić, czy jest włączona jakakolwiek pamięć wymiany.
jeśli jest pusty, oznacza to, że nie masz włączonej wymiany. Aby dodać wymianę 1 GB:
Dodaj następujący wiersz
fstab
do, aby zamiana była stała.Źródło i więcej informacji można znaleźć tutaj .
źródło
swap może nie być sugerowanym wcześniej czerwonym śledziem. Jak duży jest dany proces w Pythonie tuż przed
ENOMEM
?W jądrze 2.6
/proc/sys/vm/swappiness
kontroluje, jak agresywnie jądro będzie obracać się w celu wymiany iovercommit*
zapisuje, ile i jak precyzyjnie jądro może przydzielić pamięć mrugnięciem i skinieniem głowy. Podobnie jak status twojego związku na Facebooku, jest to skomplikowane .ale nie zgodnie z danymi wyjściowymi
free(1)
polecenia, które nie pokazują żadnej przestrzeni wymiany rozpoznawanej przez instancję serwera. Twój usługodawca hostingowy może z pewnością wiedzieć o wiele więcej na ten temat niż ja, ale wirtualne systemy RHEL / CentOS, których użyłem, zgłosiły wymianę dostępną dla systemu gościa.Dostosowanie artykułu KB Red Hat 15252 :
Porównaj swoje
/proc/sys/vm
ustawienia ze zwykłą instalacją CentOS 5.3. Dodaj plik wymiany. Opuśćswappiness
i zobacz, czy dłużej pożyjesz.źródło
ps -o user,pid,vsz="Mem(Kb)" -o cmd $PYTHON_PID
lub top (1), powinno to zrobić.Możesz to łatwo naprawić
jeśli jesteś pewien, że Twój system ma wystarczającą ilość pamięci. Zobacz Linux ponad heurystykę .
źródło
Nadal podejrzewam, że twój klient / użytkownik ma załadowany jakiś moduł jądra lub sterownik, który koliduje z
clone()
wywołaniem systemowym (może jakieś niejasne rozszerzenie bezpieczeństwa, coś w rodzaju LIDS, ale bardziej niejasne?) Lub w jakiś sposób zapełnia niektóre struktury danych jądra, które są niezbędne dofork()
/clone()
do działania (tablica procesów, tablice stron, tablice deskryptorów plików itp.).Oto odpowiednia część strony podręcznika
fork(2)
:Sugeruję, aby użytkownik spróbował tego po uruchomieniu standardowego, ogólnego jądra i po załadowaniu tylko minimalnego zestawu modułów i sterowników (minimum niezbędne do uruchomienia aplikacji / skryptu). Stamtąd, zakładając, że działa w tej konfiguracji, mogą przeprowadzić wyszukiwanie binarne między tym a konfiguracją, w której występuje problem. Jest to standardowa procedura rozwiązywania problemów 101 z administratorem systemu.
Odpowiedni wiersz w Twoim
strace
to:... Wiem, że inni mówili o wymianie i dostępności pamięci (i zalecałbym skonfigurowanie przynajmniej małej partycji wymiany, o ironio, nawet jeśli jest na dysku RAM ... ścieżki kodu przez jądro Linuksa, gdy ma nawet niewielka część dostępnej wymiany była wykonywana znacznie intensywniej niż te (ścieżki obsługi wyjątków), w których jest dostępna zerowa wymiana.
Podejrzewam jednak, że to nadal czerwony śledź.
Fakt, że
free
zgłasza 0 (ZERO) pamięci używanej przez pamięć podręczną i bufory, jest bardzo niepokojący. Podejrzewam, żefree
wynik ... i prawdopodobnie problem z aplikacją tutaj, są spowodowane przez jakiś zastrzeżony moduł jądra, który w jakiś sposób koliduje z alokacją pamięci.Zgodnie ze stronami podręcznika dla fork () / clone () wywołanie systemowe fork () powinno zwrócić EAGAIN, jeśli wywołanie spowodowałoby naruszenie limitu zasobów (RLIMIT_NPROC) ... jednak nie mówi, czy ma zostać zwrócony EAGAIN przez inne naruszenia RLIMIT *. W każdym razie, jeśli twój cel / host ma jakieś dziwne Vormetric lub inne ustawienia bezpieczeństwa (lub nawet jeśli twój proces działa pod jakąś dziwną polityką SELinux), może to powodować tę awarię -ENOMEM.
Jest mało prawdopodobne, aby był to normalny, typowy problem z Linuksem / UNIXem. Dzieje się tam coś niestandardowego.
źródło
Czy próbowałeś użyć:
Myślałem, że to rozwiązało dokładnie ten sam problem dla mnie. Ale potem mój proces skończył się śmiercią zamiast niepowodzeniem odrodzenia, co jest jeszcze gorsze.
Po kilku testach stwierdziłem, że dzieje się tak tylko w starszych wersjach Pythona: dzieje się tak z 2.6.5, ale nie z 2.7.2
Moje wyszukiwanie przywiodło mnie tutaj python-close_fds-issue , ale wyłączenie funkcji closed_fds nie rozwiązało problemu. Wciąż warto go przeczytać.
Odkryłem, że Python przecieka deskryptory plików, po prostu obserwując go:
Podobnie jak ty, chcę przechwytywać dane wyjściowe polecenia i chcę uniknąć błędów OOM ... ale wygląda na to, że jedynym sposobem jest użycie mniej błędnej wersji Pythona. Nieidealny...
źródło
Widziałem niechlujny kod, który wygląda tak:
Powinieneś sprawdzić, czy tak się dzieje w kodzie Pythona. Errno jest poprawne tylko w przypadku niepowodzenia wywołania systemowego.
Edytowano, aby dodać:
Nie mówisz, jak długo ten proces trwa. Potencjalni konsumenci pamięci
źródło
errno
jest resetowana wiele razy po drodze.Może możesz po prostu
To działa w moim przypadku.
Źródła: https://github.com/openai/gym/issues/110#issuecomment-220672405
źródło