Remarque: Cette question a été posée à l'origine ici, mais le délai de prime a expiré même si une réponse acceptable n'a pas été trouvée. Je pose à nouveau cette question en incluant tous les détails fournis dans la question initiale.
Un script python exécute un ensemble de fonctions de classe toutes les 60 secondes à l'aide du module sched :
# sc is a sched.scheduler instance
sc.enter(60, 1, self.doChecks, (sc, False))
Le script s'exécute en tant que processus démonisé en utilisant le code ici .
Un certain nombre de méthodes de classe appelées dans le cadre de doChecks utilisent le module de sous - processus pour appeler des fonctions système afin d'obtenir des statistiques système:
ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE).communicate()[0]
Cela fonctionne bien pendant un certain temps avant que tout le script ne se bloque avec l'erreur suivante:
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
La sortie de free -m sur le serveur une fois que le script s'est écrasé est:
$ free -m
total used free shared buffers cached
Mem: 894 345 549 0 0 0
-/+ buffers/cache: 345 549
Swap: 0 0 0
Le serveur exécute CentOS 5.3. Je ne peux pas reproduire sur mes propres boîtiers CentOS ni avec aucun autre utilisateur signalant le même problème.
J'ai essayé un certain nombre de choses pour déboguer ceci comme suggéré dans la question originale:
Enregistrement de la sortie de free -m avant et après l'appel Popen. Il n'y a pas de changement significatif dans l'utilisation de la mémoire, c'est-à-dire que la mémoire n'est pas utilisée progressivement pendant l'exécution du script.
J'ai ajouté close_fds = True à l'appel Popen mais cela n'a fait aucune différence - le script s'est toujours écrasé avec la même erreur. Suggéré ici et ici .
J'ai vérifié les limites qui montraient (-1, -1) à la fois sur RLIMIT_DATA et RLIMIT_AS comme suggéré ici .
Un article a suggéré que le fait de ne pas avoir d'espace d'échange pourrait en être la cause, mais l'échange est en fait disponible sur demande (selon l'hébergeur Web) et cela a également été suggéré comme une cause bidon ici .
Les processus sont fermés parce que c'est le comportement de l'utilisation de .communicate () tel que sauvegardé par le code source Python et les commentaires ici .
Toutes les vérifications peuvent être trouvées sur GitHub ici avec la fonction getProcesses définie à partir de la ligne 442. Ceci est appelé par doChecks () à partir de la ligne 520.
Le script a été exécuté avec strace avec la sortie suivante avant le crash:
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
oudmesg
commandez.Réponses:
En règle générale ( par exemple dans les noyaux de vanille),
fork
/clone
échecs avecENOMEM
lieu spécifiquement en raison soit honnête à Dieu hors condition de mémoire insuffisante (dup_mm
,dup_task_struct
,alloc_pid
,mpol_dup
,mm_init
etc. croassement), ou parce quesecurity_vm_enough_memory_mm
vous avez échoué en application de la politique surdimensionnement .Commencez par vérifier la vmsize du processus qui n'a pas réussi à fork, au moment de la tentative de fork, puis comparez à la quantité de mémoire libre (physique et swap) en ce qui concerne la politique de surengagement (branchez les nombres.)
Dans votre cas particulier, notez que Virtuozzo a des vérifications supplémentaires dans l' application des surengagements . De plus, je ne suis pas sûr du niveau de contrôle que vous avez réellement, à partir de votre conteneur, sur la configuration de swap et de surengagement (afin d'influencer le résultat de l'application.)
Maintenant, pour aller de l'avant, je dirais qu'il vous reste deux options :
Notez que l'effort de codage peut être nul s'il s'avère que ce n'est pas vous, mais un autre gars colocalisé dans une instance différente sur le même serveur que vous exécutez amock.
En ce qui concerne la mémoire, nous savons déjà que
subprocess.Popen
utilisefork
/clone
sous le capot , ce qui signifie qu'à chaque fois que vous l'appelez, vous demandez à nouveau autant de mémoire que Python consomme déjà , c'est-à-dire dans les centaines de Mo supplémentaires, le tout pour ensuiteexec
un exécutable minuscule de 10 Ko tel quefree
oups
. Dans le cas d'une politique de surengagement défavorable, vous verrez bientôtENOMEM
.Les alternatives à
fork
cela n'ont pas ce problème de copie des tables de page parente, etc. sontvfork
etposix_spawn
. Mais si vous n'avez pas envie de réécrire des morceaux desubprocess.Popen
en termes devfork
/posix_spawn
, envisagez de n'utilisersuprocess.Popen
qu'une seule fois, au début de votre script (lorsque l'empreinte mémoire de Python est minimale), pour générer un script shell qui exécute ensuitefree
/ps
/sleep
et quoi que ce soit d'autre dans un boucle parallèle à votre script; interrogez la sortie du script ou lisez-le de manière synchrone, éventuellement à partir d'un thread séparé si vous avez d'autres choses à prendre en charge de manière asynchrone - effectuez votre analyse de données en Python mais laissez le forking au processus subordonné.CEPENDANT , dans votre cas particulier, vous pouvez ignorer l'appel
ps
etfree
tout à fait; ces informations sont facilement accessibles en Python directement depuisprocfs
, que vous choisissiez d'y accéder vous-même ou via des bibliothèques et / ou des packages existants . Sips
etfree
étaient les seuls utilitaires que vous utilisiez, vous pouvez vous en débarrassersubprocess.Popen
complètement .Enfin, quoi que vous fassiez en ce qui le
subprocess.Popen
concerne, si votre script perd de la mémoire, vous finirez par frapper le mur. Gardez un œil dessus et recherchez les fuites de mémoire .la source
gc.collect()
juste avantsubprocess.Popen
aide dans les cas où le ramasse-miettes n'avait pas fonctionné depuis un certain temps./proc/fd/maps
pour déterminer si la mémoire surchargée est en fait le problèmeEn regardant la sortie,
free -m
il me semble que vous n'avez pas de mémoire d'échange disponible. Je ne sais pas si sous Linux, le swap sera toujours disponible automatiquement à la demande, mais j'avais le même problème et aucune des réponses ici ne m'a vraiment aidé. L'ajout de mémoire d'échange a cependant résolu le problème dans mon cas, donc comme cela pourrait aider d'autres personnes confrontées au même problème, je poste ma réponse sur la façon d'ajouter un échange de 1 Go (sur Ubuntu 12.04 mais cela devrait fonctionner de la même manière pour les autres distributions.)Vous pouvez d'abord vérifier si une mémoire d'échange est activée.
s'il est vide, cela signifie qu'aucun échange n'est activé. Pour ajouter un swap de 1 Go:
Ajoutez la ligne suivante au
fstab
pour rendre le swap permanent.La source et plus d'informations peuvent être trouvées ici .
la source
swap peut ne pas être le hareng rouge suggéré précédemment. Quelle est la taille du processus python en question juste avant le
ENOMEM
?Sous le noyau 2.6,
/proc/sys/vm/swappiness
contrôle l'agressivité du noyau pour permuter, etovercommit*
classe la quantité et la précision avec laquelle le noyau peut répartir la mémoire avec un clin d'œil et un signe de tête. Comme votre statut de relation Facebook, c'est compliqué .mais pas en fonction de la sortie de votre
free(1)
commande, qui n'affiche aucun espace de swap reconnu par votre instance de serveur. Maintenant, votre hébergeur en sait certainement beaucoup plus que moi sur ce sujet, mais les systèmes RHEL / CentOS virtuels que j'ai utilisés ont signalé un échange disponible pour le système d'exploitation invité.Adaptation de l'article 15252 de la base de connaissances Red Hat :
Comparez vos
/proc/sys/vm
paramètres à une installation CentOS 5.3 simple. Ajoutez un fichier d'échange. Ratchetswappiness
et voyez si vous vivez plus longtemps.la source
ps -o user,pid,vsz="Mem(Kb)" -o cmd $PYTHON_PID
, ou top (1), devrait le faire.Pour une solution simple, vous pouvez
si vous êtes sûr que votre système dispose de suffisamment de mémoire. Voir Linux sur l'heuristique de validation .
la source
Je continue de soupçonner que votre client / utilisateur a chargé un module ou un pilote du noyau qui interfère avec l'
clone()
appel système (peut-être une amélioration de la sécurité obscure, quelque chose comme LIDS mais plus obscur?) Ou remplit en quelque sorte certaines des structures de données du noyau qui sont nécessaires pourfork()
/clone()
pour fonctionner (table de processus, tables de pages, tables de descripteurs de fichiers, etc.).Voici la partie pertinente de la
fork(2)
page de manuel:Je suggère à l'utilisateur d'essayer ceci après le démarrage dans un stock, un noyau générique et avec seulement un ensemble minimal de modules et de pilotes chargés (minimum nécessaire pour exécuter votre application / script). À partir de là, en supposant que cela fonctionne dans cette configuration, ils peuvent effectuer une recherche binaire entre cela et la configuration qui présente le problème. Il s'agit du dépannage standard de sysadmin 101.
La ligne pertinente dans votre
strace
est:... Je sais que d'autres ont parlé de la disponibilité de swap et de mémoire (et je vous recommanderais de configurer au moins une petite partition de swap, ironiquement même si elle est sur un disque RAM ... les chemins de code à travers le noyau Linux quand il a même un tout petit peu de swap disponible a été exercé beaucoup plus intensivement que ceux (chemins de gestion d'exceptions) dans lesquels il n'y a aucun swap disponible.
Cependant, je soupçonne que c'est toujours un hareng rouge.
Le fait de
free
signaler une mémoire 0 (ZERO) utilisée par le cache et les tampons est très inquiétant. Je soupçonne que lafree
sortie ... et peut-être votre problème d'application ici, sont causés par un module de noyau propriétaire qui interfère d'une manière ou d'une autre avec l'allocation de mémoire.Selon les pages de manuel de fork () / clone (), l'appel système fork () devrait retourner EAGAIN si votre appel provoquait une violation de la limite de ressources (RLIMIT_NPROC) ... cependant, cela ne dit pas si EAGAIN doit être retourné par d'autres violations de RLIMIT *. Dans tous les cas, si votre cible / hôte a une sorte de paramètres de sécurité étranges Vormetric ou autres (ou même si votre processus s'exécute sous une politique SELinux étrange), cela peut être à l'origine de cet échec -ENOMEM.
Il est peu probable que ce soit un problème Linux / UNIX normal. Il se passe quelque chose de non standard là-bas.
la source
Avez-vous essayé d'utiliser:
Je pensais que cela avait résolu exactement le même problème pour moi. Mais ensuite, mon processus a fini par être tué au lieu de ne pas apparaître, ce qui est encore pire.
Après quelques tests, j'ai constaté que cela ne se produisait que sur les anciennes versions de python: cela se produit avec 2.6.5 mais pas avec 2.7.2
Ma recherche m'avait conduit ici python-close_fds-issue , mais la désactivation de closed_fds n'avait pas résolu le problème. Cela vaut toujours la peine d'être lu.
J'ai trouvé que python fuyait des descripteurs de fichiers en gardant simplement un œil dessus:
Comme vous, je veux capturer la sortie de la commande, et je veux éviter les erreurs MOO ... mais il semble que le seul moyen soit pour les gens d'utiliser une version moins boguée de Python. Pas idéal ...
la source
J'ai vu du code bâclé qui ressemble à ceci:
Vous devriez vérifier si c'est ce qui se passe dans le code python. Errno n'est valide que si l'appel système en cours a échoué.
Modifié pour ajouter:
Vous ne dites pas combien de temps dure ce processus. Possibles consommateurs de mémoire
la source
errno
est réinitialisée plusieurs fois en cours de route.Peut-être que vous pouvez simplement
Cela fonctionne pour mon cas.
Référence: https://github.com/openai/gym/issues/110#issuecomment-220672405
la source