Remarque: Cette question a été posée à l'origine ici mais le délai de validité de la prime a expiré même si aucune réponse acceptable n'a été trouvée. Je pose à nouveau cette question, y compris 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 est exécuté en tant que processus démonisé utilisant le code ici .
Un certain nombre de méthodes de classe appelées dans le cadre de doChecks utilisent le module 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 correctement pendant un certain temps avant que le script entier 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 le script bloqué est la suivante:
$ 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 parviens pas à reproduire sur mes propres boîtes CentOS ni avec aucun autre utilisateur signalant le même problème.
J'ai essayé un certain nombre de choses pour résoudre ce problème, comme suggéré dans la question initiale:
Consignation de la sortie de free -m avant et après l'appel Popen. Il n’ya pas de changement significatif dans l’utilisation de la mémoire, c’est-à-dire que la mémoire n’est pas utilisée progressivement au cours de l’exécution du script.
J'ai ajouté close_fds = True à l'appel Popen mais cela ne faisait aucune différence - le script se plantait toujours avec la même erreur. Suggéré ici et ici .
J'ai vérifié les valeurs limites qui indiquaient (-1, -1) à la fois sur RLIMIT_DATA et sur RLIMIT_AS comme suggéré ici .
n article a suggéré que le fait de ne pas avoir d'espace d'échange pourrait en être la cause, mais que l'échange est en fait disponible sur demande (selon l'hébergeur Web), ce qui a également été suggéré comme cause fictive ici .
Les processus sont en cours de fermeture parce que c'est le comportement d'utiliser .communicate () comme sauvegardé par le Python et commentaires ici .
La totalité des vérifications peut être trouvée sur le site GitHub here avec la fonction getProcesses définie à la ligne 442. Elle est appelée 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) = ?
En règle générale (c'est-à-dire dans les noyaux Vanilla), fork
/clone
échecs avec ENOMEM
se produisent spécifiquement à cause de soit une condition honnête de mémoire insuffisante envers Dieu (dup_mm
, dup_task_struct
, alloc_pid
, mpol_dup
, mm_init
etc. croak), ou parce que security_vm_enough_memory_mm
vous avez échoué while imposer le règle de sur-engagement .
Commencez par vérifier la taille de vms du processus dont la tentative a échoué, puis comparez-la à la quantité de mémoire libre (physique et swap) en ce qui concerne la stratégie de surcharge (connectez les nombres).
Dans votre cas particulier, notez que Virtuozzo a vérifications supplémentaires in contrôle de dépassement des engagements . De plus, je ne suis pas sûr du contrôle que vous avez réellement, à partir de dans votre conteneur, sur configuration d'échange et de surcommit (afin d'influer sur le résultat de l'application .)
Maintenant, pour avancer, je dirais que vous êtes à gauche avec deux options:
[~ # ~] note [~ # ~] que l'effort de codage peut être totalement inutile s'il s'avère que ce n'est pas vous, mais qu'un autre type est colocalisé dans un cas différent sur le même serveur que vous exécutez amock.
Au niveau de la mémoire, nous savons déjà que subprocess.Popen
utilise fork
/clone
sous le capot , ce qui signifie que chaque fois que vous l'appelez, vous êtes demandez à nouveau comme beaucoup de mémoire en tant que Python mange déjà, c’est-à-dire dans les centaines de Mo supplémentaires, le tout pour ensuite exec
un exécutable de 10 Ko tel que free
ou ps
. Dans le cas d'une politique de surengagement trop défavorable, vous verrez bientôt ENOMEM
.
Les alternatives à fork
qui n’ont pas ce problème de copie des tables de pages parent, etc. sont vfork
et posix_spawn
. Mais si vous ne souhaitez pas réécrire des morceaux de subprocess.Popen
en termes de vfork
/posix_spawn
, pensez à utiliser suprocess.Popen
_ seulement une 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 ensuite free
/ps
/sleep
et toute autre chose dans une boucle parallèle à votre script; Interrogez la sortie du script ou lisez-la de manière synchrone, éventuellement à partir d'un thread séparé, si vous avez d'autres tâches à prendre en charge de manière asynchrone. Réalise le traitement de vos données dans Python, mais laissez la charge au processus subordonné.
[~ # ~] cependant [~ # ~], dans votre cas particulier, vous pouvez ignorer l'invocation de ps
et de free
tout à fait; que les informations sont facilement disponibles dans Python directement à partir de procfs
, que vous choisissiez d'y accéder vous-même ou via bibliothèques et/ou packages existants . Si ps
et free
étaient les seuls utilitaires que vous utilisiez, vous pouvez alors supprimer de subprocess.Popen
complètement.
Enfin, quoi que vous fassiez en tant que subprocess.Popen
_ est concerné, si votre script perd de la mémoire, vous finirez par vous heurter au mur. Gardez un œil dessus et recherchez les fuites de mémoire .
En regardant la sortie de free -m
il me semble que vous ne disposez pas de mémoire d'échange. Je ne sais pas si sous Linux le swap sera toujours disponible automatiquement à la demande, mais je rencontrais le même problème et aucune des réponses ici ne m'a vraiment aidé. L'ajout de mémoire d'échange a toutefois résolu le problème dans mon cas. Ainsi, puisque cela pourrait aider d'autres personnes confrontées au même problème, je publie ma réponse sur la manière d'ajouter un échange de 1 Go (sous Ubuntu 12.04, mais cela devrait fonctionner de la même manière pour d'autres distributions.)
Vous pouvez d’abord vérifier si une mémoire de swap est activée.
$Sudo swapon -s
s'il est vide, cela signifie que vous n'avez activé aucun échange. Pour ajouter un échange de 1 Go:
$Sudo dd if=/dev/zero of=/swapfile bs=1024 count=1024k
$Sudo mkswap /swapfile
$Sudo swapon /swapfile
Ajoutez la ligne suivante au fstab
pour rendre le swap permanent.
$Sudo vim /etc/fstab
/swapfile none swap sw 0 0
La source et plus d'informations peuvent être trouvées ici .
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 en permutation; les fichiers overcommit*
Déterminent la quantité et la précision avec lesquelles le noyau peut répartir la mémoire avec un clin d'œil et un nœud. Comme votre statut de relation facebook, c'est compliqué .
... mais le swap est effectivement disponible sur demande (selon l'hébergeur Web) ...
mais pas en fonction du résultat de votre commande free(1)
, qui ne montre aucun espace d'échange reconnu par votre instance de serveur. Votre hébergeur en sait peut-être beaucoup plus que moi sur ce sujet, mais les systèmes RHEL/CentOS virtuels que j'ai utilisés ont signalé que l'échange est disponible pour le système d'exploitation invité.
Adaptation Article 15252 de la base de connaissances de Red Hat :
Un système Red Hat Enterprise Linux 5 fonctionne parfaitement sans espace de swap tant que la somme de la mémoire anonyme et de la mémoire partagée du système V est inférieure à environ 3/4 de la quantité de RAM. .... Les systèmes dotés de 4 Go de RAM ou moins [sont recommandés] avec un minimum de 2 Go d'espace d'échange.
Comparez vos paramètres /proc/sys/vm
À une installation standard de CentOS 5.3. Ajouter un fichier d'échange. Déclenchez swappiness
et voyez si vous vivez plus longtemps.
Je continue de penser que votre client/utilisateur a un module de noyau ou un pilote chargé qui interfère avec l'appel système clone()
(peut-être une amélioration de sécurité obscure, quelque chose comme LIDS mais plus obscure?) Ou des structures de données du noyau nécessaires au fonctionnement de fork()
/clone()
(table de processus, tables de page, tables de descripteur de fichier, etc.).
Voici la partie pertinente de la page de manuel fork(2)
:
ERREURS EAGAIN fork () ne peut pas allouer suffisamment de mémoire pour copier les tables de pages du parent et allouer une structure de tâches pour l'enfant . EAGAIN Il n'a pas été possible de créer un nouveau processus car la limite de ressources RLIMIT_NPROC de l'appelant a été rencontrée. Pour que Dépasse cette limite, le processus doit disposer de la capacité CAP_SYS_ADMIN ou CAP_SYS_RESOURCE. ENOMEM fork () n'a pas pu allouer les structures de noyau nécessaires car la mémoire est saturée. ____.]
Je suggère que l'utilisateur l'essaie après avoir démarré 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 celle-ci et la configuration qui présente le problème. Il s’agit du dépannage standard de l’administrateur système 101.
La ligne pertinente dans votre strace
est:
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7f12708) = -1 ENOMEM (Cannot allocate memory)
... Je sais que d’autres ont parlé de swap et de disponibilité de la mémoire (et je vous recommanderais de configurer au moins une petite partition de swap, même si elle se trouve sur un RAM disque ... le Les chemins de code passant par le noyau Linux, même s'il ne dispose que d'un infime échange, ont été exercés de manière beaucoup plus étendue que ceux (chemins de traitement des exceptions) dans lesquels aucun échange n'est disponible.
Cependant, je soupçonne qu'il s'agit toujours d'un hareng rouge.
Le fait que free
rapporte 0 (ZERO) mémoire utilisée par le cache et les tampons est très inquiétant. Je soupçonne que la sortie de free
... et peut-être votre problème d'application ici, sont causés par un module de noyau propriétaire qui interfère d'une certaine manière avec l'allocation de mémoire.
Selon les pages de manuel de fork ()/clone (), l’appel système fork () doit renvoyer EAGAIN si votre appel entraîne une violation des limites de ressources (RLIMIT_NPROC) ... Cependant, il n’indique pas si EAGAIN doit être renvoyé. par d'autres violations RLIMIT *. Dans tous les cas, si votre cible/hôte possède une sorte de paramètres de sécurité Vormetric ou autres bizarres (ou même si votre processus s'exécute sous une règle SELinux étrange), cela peut être à l'origine de l'échec de -ENOMEM.
Il est peu probable que ce soit un problème normal sous Linux/UNIX. Vous avez quelque chose de non standard qui se passe là-bas.
Pour une solution facile, vous pourriez
echo 1 > /proc/sys/vm/overcommit_memory
si vous êtes certain que votre système dispose de suffisamment de mémoire. Voir heuristique sur la validation de Linux .
Avez-vous essayé d'utiliser:
(status,output) = commands.getstatusoutput("ps aux")
Je pensais que cela avait résolu exactement le même problème pour moi. Mais ensuite, mon processus a été 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 arrive avec 2.6.5 mais pas avec 2.7.2
Ma recherche m'avait conduit ici python-close_fds-issue , mais la réinitialisation de closed_fds n'avait pas résolu le problème. Cela vaut toujours la peine d'être lu.
J'ai trouvé que python laissait filtrer les descripteurs de fichiers en gardant simplement un œil dessus:
watch "ls /proc/$PYTHONPID/fd | wc -l"
Comme vous, je souhaite capturer le résultat de la commande et éviter les erreurs de MOO ... mais il semble que le seul moyen consiste pour les utilisateurs à utiliser une version moins boguée de Python. Pas idéal ...
munmap (0xb7d28000, 4096) = 0
write (2, "OSError", 7) = 7
J'ai vu du code bâclé qui ressemble à ceci:
serrno = errno;
some_Syscall(...)
if (serrno != errno)
/* sound alarm: CATROSTOPHIC ERROR !!! */
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é.
Édité pour ajouter:
Vous ne dites pas combien de temps dure ce processus. Consommateurs possibles de mémoire