128 votes

Python subprocess.Popen "OSError : [Errno 12] Impossible d'allouer de la mémoire".

Note : Cette question a été posée à l'origine aquí mais le temps de prime a expiré même si une réponse acceptable n'a pas été effectivement trouvée. Je pose à nouveau cette question en incluant tous les détails fournis dans la question originale.

Un script python exécute un ensemble de fonctions de classe toutes les 60 secondes à l'aide de la fonction calendrier module :

# sc is a sched.scheduler instance
sc.enter(60, 1, self.doChecks, (sc, False))

Le script est exécuté comme un processus démonisé en utilisant le code aquí .

Un certain nombre de méthodes de classe qui sont appelées dans le cadre de doChecks utilisent la balise sous-processus pour appeler les fonctions du système afin d'obtenir les statistiques du système :

ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE).communicate()[0]

Cela fonctionne bien pendant un certain temps avant que l'ensemble du script ne se plante 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 fonctionne sous CentOS 5.3. Je ne parviens pas à reproduire le problème 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 déboguer ce problème, comme suggéré dans la question originale :

  1. 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 progressivement utilisée au fur et à mesure de l'exécution du script.

  2. 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. Suggestion : aquí y aquí .

  3. J'ai vérifié les rlimits qui montrent (-1, -1) à la fois sur RLIMIT_DATA et RLIMIT_AS comme suggéré. aquí .

  4. Un article a suggéré que le fait de ne pas avoir d'espace d'échange pourrait être la cause, mais l'espace d'échange est en fait disponible à la demande (selon l'hébergeur) et cela a également été suggéré comme une fausse cause. aquí .

  5. Les processus sont fermés car c'est le comportement de l'utilisation de .communicate(), comme le confirment le code source et les commentaires de Python. aquí .

L'intégralité des contrôles peut être consultée à l'adresse suivante GitHub ici avec la fonction getProcesses définie à la ligne 442. Celle-ci 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)                           = ?

102voto

vladr Points 34562

En règle générale (c'est-à-dire dans les noyaux vanille), fork / clone défaillances avec ENOMEM se produisent spécifiquement en raison soit une condition de perte de mémoire honnête à Dieu. ( dup_mm , dup_task_struct , alloc_pid , mpol_dup , mm_init etc. croassent), ou parce que security_vm_enough_memory_mm vous avez échoué tandis que application de le site politique de surengagement .

Commencez par vérifier la taille de la mémoire virtuelle (vmsize) du processus qui n'a pas réussi à bifurquer, au moment de la tentative de bifurcation, puis comparez-la à la quantité de mémoire libre (physique et swap) en fonction de la politique d'overcommit (branchez les chiffres).

Dans votre cas particulier, notez que Virtuozzo a contrôles supplémentaires en surengager l'exécution . De plus, je ne suis pas sûr du degré de contrôle que vous avez réellement, de sur votre conteneur, sur configuration de swap et overcommit (afin d'influencer le résultat de l'exécution).

Maintenant, pour aller de l'avant, je dirais que vous êtes Il ne reste que deux options :

  • passer à une instance plus importante, ou
  • faire un effort de codage dans contrôler plus efficacement la mémoire de votre script. Empreinte au sol

NOTE que l'effort de codage peut être inutile s'il s'avère que ce n'est pas vous, mais un autre gars situé dans une instance différente sur le même serveur que vous qui fait tourner amock.

En ce qui concerne la mémoire, nous savons déjà que subprocess.Popen utilise fork / clone sous le capot ce qui signifie qu'à chaque fois que vous l'appelez, vous êtes demandant encore une fois autant de mémoire que Python consomme déjà c'est-à-dire des centaines de Mo supplémentaires, tout cela pour ensuite exec un minuscule exécutable de 10kB tel que free o ps . Dans le cas d'une politique de surengagement défavorable, vous verrez bientôt ENOMEM .

Alternatives aux fork qui n'ont pas ce problème de copie des tables de pages parentales etc. sont vfork y posix_spawn . Mais si vous n'avez pas envie de réécrire des morceaux de subprocess.Popen en termes de vfork / posix_spawn envisagez d'utiliser suprocess.Popen une seule fois, au début de votre script (lorsque l'empreinte mémoire de Python est minimale), pour génère un shell script qui s'exécute ensuite free / ps / sleep et tout le reste dans une boucle parallèlement à 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 choses à faire de manière asynchrone -- faites vos calculs de données en Python mais laissez la bifurcation au processus subordonné.

CEPENDANT, dans votre cas particulier, vous pouvez éviter d'invoquer ps y free tout à fait ; que Les informations sont facilement disponibles en Python, directement auprès de l'Office national de l'énergie. procfs que vous choisissiez d'y accéder vous-même ou par l'intermédiaire de bibliothèques et/ou paquets existants . Si ps y free étaient les seuls utilitaires que vous exécutiez, alors vous pouvez se débarrasser de subprocess.Popen complètement .

Enfin, quoi que vous fassiez en ce qui concerne subprocess.Popen est concerné, si votre script fuit la mémoire, vous finirez quand même par vous heurter à un mur. Gardez un oeil dessus, et vérifier les fuites de mémoire .

20voto

Nima Points 2873

En regardant la sortie de free -m il me semble que vous n'avez pas de mémoire d'échange disponible. Je ne suis pas sûr que sous Linux, l'espace de pagination soit toujours disponible automatiquement à la demande, mais j'avais le même problème et aucune des réponses données ici ne m'a vraiment aidé. L'ajout d'un peu de mémoire swap a cependant résolu le problème dans mon cas, donc puisque 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 swap de 1 Go (sur Ubuntu 12.04 mais cela devrait fonctionner de manière similaire pour d'autres distributions).

Vous pouvez d'abord vérifier si une mémoire d'échange est activée.

$sudo swapon -s

s'il est vide, cela signifie que vous n'avez pas de swap activé. Pour ajouter un swap de 1GB :

$sudo dd if=/dev/zero of=/swapfile bs=1024 count=1024k
$sudo mkswap /swapfile
$sudo swapon /swapfile

Ajoutez la ligne suivante au fichier fstab pour rendre l'échange permanent.

$sudo vim /etc/fstab

     /swapfile       none    swap    sw      0       0 

La source et de plus amples informations peuvent être trouvées aquí .

12voto

user1587329 Points 91

Pour une réparation facile, vous pouvez

echo 1 > /proc/sys/vm/overcommit_memory

si vous êtes sûr que votre système a assez de mémoire. Voir heuristique Linux over commit .

9voto

pilcrow Points 20628

Le swap n'est peut-être pas 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é avec laquelle le noyau se tournera vers le swap, et overcommit* Le noyau peut répartir la mémoire avec un clin d'œil et un signe de tête. Comme votre statut relationnel sur Facebook, c'est compliqué .

...mais le swap est en fait disponible à la demande (selon l'hébergeur)...

mais pas selon la sortie de votre free(1) qui montre qu'aucun espace de pagination n'est reconnu par votre instance de serveur. Maintenant, votre hôte web peut certainement en savoir beaucoup plus que moi sur ce sujet, mais les systèmes virtuels RHEL/CentOS que j'ai utilisés ont signalé la présence d'espace de pagination disponible pour le système d'exploitation invité.

Adaptation du site Article 15252 de la KB de Red Hat :

Un système Red Hat Enterprise Linux 5 fonctionnera très bien sans espace de pagination du moment 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. .... Systèmes avec 4 Go de RAM ou moins [qu'il est recommandé d'avoir] un minimum de 2 Go d'espace d'échange.

Comparez votre /proc/sys/vm à une installation CentOS 5.3 ordinaire. Ajouter un fichier swap. Réduire swappiness et voir si vous vivez plus longtemps.

5voto

Jim Dennis Points 5454

Je continue à penser que votre client/utilisateur a chargé un module du noyau ou un pilote qui qui interfère avec le clone() appel système (peut-être une obscure amélioration de la sécurité, quelque chose comme LIDS mais en plus obscur ?) ou remplit d'une manière ou d'une autre certaines du noyau qui sont nécessaires pour fork() / clone() pour fonctionner (tableau des processus, page tables, tables de descripteurs de fichiers, etc).

Voici la partie pertinente de la fork(2) page de manuel :

ERRORS
       EAGAIN fork() cannot allocate sufficient memory to copy the parent's page tables and allocate a task  structure  for  the
              child.

       EAGAIN It  was not possible to create a new process because the caller's RLIMIT\_NPROC resource limit was encountered.  To
              exceed this limit, the process must have either the CAP\_SYS\_ADMIN or the CAP\_SYS\_RESOURCE capability.

       ENOMEM fork() failed to allocate the necessary kernel structures because memory is tight.

Je suggère de demander à l'utilisateur d'essayer après avoir démarré dans un noyau générique standard et avec seulement un ensemble minimal de modules et de pilotes chargés (le 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. C'est le dépannage standard de l'administrateur système 101.

La ligne correspondante 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 l'espace de pagination et de la disponibilité de la mémoire (et je vous recommanderais de mettre en place au moins une petite partition d'espace de pagination, ironiquement même si c'est sur un disque RAM ... les chemins de code à travers le noyau Linux quand il a même un tout petit peu d'espace de pagination disponible ont été exercés de manière beaucoup plus extensive que ceux (chemins de gestion des exceptions) dans lesquels il n'y a aucun espace de pagination disponible.

Cependant, je soupçonne qu'il s'agit encore d'un faux-fuyant.

Le fait que free rapporte 0 (ZERO) mémoire utilisée par le cache et les tampons est très perturbant. Je soupçonne que le free ... et peut-être le problème de votre application ici, sont causés par un module propriétaire du noyau qui interfère avec l'allocation de mémoire d'une manière ou d'une autre.

Selon les pages de manuel de fork()/clone(), l'appel système fork() doit retourner EAGAIN si votre appel provoque une violation de la limite de ressources (RLIMIT_NPROC) ... cependant, cela ne dit pas si EAGAIN doit être retourné par d'autres violations RLIMIT*. Dans tous les cas, si votre cible/hôte a une sorte de Vormetric bizarre ou d'autres paramètres de sécurité (ou même si votre processus est exécuté sous une politique SELinux bizarre), alors cela pourrait causer cet échec -ENOMEM.

Il est peu probable qu'il s'agisse d'un problème normal de Linux/UNIX. Il y a quelque chose de non standard qui se passe ici.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X