Python subprocess.Popen“OSError:无法分配内存”
注意:这个问题最初是在这里问的,但即使可以接受的答案没有被发现,赏金时间已经过期。 我正在重复问这个问题,包括原始问题中提供的所有细节。
python脚本使用sched模块每60秒运行一组类function:
# sc is a sched.scheduler instance sc.enter(60, 1, self.doChecks, (sc, False))
该脚本作为使用此处的代码的守护程序进程运行。
许多作为doChecks一部分而调用的类方法使用subprocess模块来调用系统函数来获取系统统计信息:
ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE).communicate()[0]
在整个脚本崩溃之前,这一段时间运行良好,出现以下错误:
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
一旦脚本崩溃,服务器上的free -m的输出是:
$ free -m total used free shared buffers cached Mem: 894 345 549 0 0 0 -/+ buffers/cache: 345 549 Swap: 0 0 0
该服务器正在运行CentOS 5.3。 我无法在自己的CentOS盒子上复制,也没有任何其他用户报告相同的问题。
我已经尝试了很多东西来debugging这个原来的问题build议:
-
在Popen调用之前和之后loggingfree -m的输出。 内存使用量没有明显的变化,即脚本运行时内存不会逐渐耗尽。
-
我给Popen调用添加了close_fds = True,但是这并没有什么区别 – 脚本仍然崩溃,同样的错误。 build议在这里和这里 。
-
我检查了在RLIMIT_DATA和RLIMIT_AS上显示(-1,-1)的rlimits,如此处所示。
-
一篇文章提出,没有交换空间可能是原因,但交换实际上是按需提供(根据networking主机),这也被认为是一个虚假的原因。
-
进程正在closures,因为这是使用.communicate()的行为,由Python源代码和备注在这里备份。
整个检查可以在GitHub上find,这里使用从第442行定义的getProcesses函数。这由doChecks()从520行开始。
在崩溃之前,脚本使用strace运行,输出如下:
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) = ?
作为一般规则(即在香草核心中),由于对于上帝内存 dup_mm
情况 ( dup_mm
, dup_task_struct
, alloc_pid
, mpol_dup
, mm_init
等),或者由于security_vm_enough_memory_mm
在执行 过度使用政策时失败了。
首先检查分叉失败的进程的vmsize,然后将其与过度使用策略相关的可用内存量(物理和交换)进行比较(插入数字)。
在您的具体情况下,请注意Virtuozzo在超执行方面有额外的检查 。 而且,我不确定在你的容器内 ,你真正拥有多less控制权, 交换和过度使用configuration (为了影响执行结果)。
现在,为了实际前进,我会说你剩下两个select :
- 切换到更大的实例,或者
- 把一些编码工作, 更有效地控制脚本的内存占用
请注意 ,如果事实certificate这不是你,那么编码的努力可能是徒劳的,但是其他一些人在运行amock的同一台服务器上的不同实例中搭配使用。
在内存方面,我们已经知道subprocess.Popen
使用了fork
/ clone
,这意味着每当你调用它时,你就会再次请求更多的内存,就像Python已经吃光了一样 ,也就是在数百个额外的MB中,所有为了然后exec
一个可怕的10kB可执行文件,如free
或ps
。 在不利的过度使用政策的情况下,您很快会看到ENOMEM
。
没有这个父页表等替代fork
副本问题是vfork
和posix_spawn
。 但是如果你不想用vfork
/ posix_spawn
重写subprocess.Popen
suprocess.Popen
块,可以考虑在脚本开头(当Python的内存占用最suprocess.Popen
使用suprocess.Popen
一次,以产生一个shell脚本,然后运行free
/ ps
/ sleep
和其他与脚本并行的循环 ; 轮询脚本的输出或者同步读取它,如果你有其他的东西需要asynchronous处理的话,可能从一个单独的线程读取它 – 在Python中执行你的数据处理,但把分支留给下级进程。
但是 ,在你的具体情况下,你可以跳过调用ps
和free
。 无论您是select自己还是通过现有的库和/或包来访问它,您都可以直接从procfs
获取这些信息。 如果ps
和free
是你正在运行的唯一实用程序,那么你可以用subprocess.Popen
完成 。
最后,无论你做什么subprocess.Popen
关心,如果你的脚本泄漏内存,你仍然会打墙。 密切关注,并检查内存泄漏 。
互换可能不是以前build议的红鲱鱼。 在ENOMEM
之前有问题的Python进程有多大?
在内核2.6下, /proc/sys/vm/swappiness
控制内核转换到多大程度,并且overcommit*
文件多less,以及内核如何用一个眨眼和点头来分配内存。 就像你的Facebook关系状态一样, 这很复杂 。
…但交换实际上是根据需要(根据networking主机)…
但不是根据您的free(1)
命令的输出,它显示您的服务器实例不识别交换空间。 现在,您的虚拟主机当然可以比我更了解这个主题,但是我使用的虚拟RHEL / CentOS系统已经向客户操作系统报告了可用的交换。
修改Red Hat知识库文章15252 :
只要匿名内存和系统V共享内存的总和小于RAM的大约3/4,红帽企业版Linux 5系统就可以运行得很好,没有交换空间。 …. 4 GB的内存或更less的系统[build议]有至less2GB的交换空间。
将你的/proc/sys/vm
设置与一个普通的CentOS 5.3安装进行比较。 添加一个交换文件。 棘轮下来,看看你是否再活着。
看看free -m
的输出,在我看来,你实际上没有可用的交换内存。 我不确定在Linux下是否可以自动按需交换,但是我遇到了同样的问题,这里没有任何答案对我有帮助。 然而,添加一些交换内存,在我的情况下解决了这个问题,因为这可能会帮助其他人面临同样的问题,我发表我的答案如何添加1GB的交换(在Ubuntu 12.04,但它应该同样适用于其他发行版)。
您可以先检查是否有任何交换内存启用。
$sudo swapon -s
如果它是空的,这意味着你没有任何交换启用。 要添加1GB的交换:
$sudo dd if=/dev/zero of=/swapfile bs=1024 count=1024k $sudo mkswap /swapfile $sudo swapon /swapfile
fstab
下行添加到fstab
以使交换永久。
$sudo vim /etc/fstab /swapfile none swap sw 0 0
来源和更多信息可以在这里find。
我继续怀疑你的客户/用户有一些内核模块或驱动程序加载干扰clone()
系统调用(也许一些模糊的安全增强,像LIDS但更隐晦?),或者是某种程度上填补了一些内核fork()
/ clone()
操作所需的数据结构(进程表,页表,文件描述符表等)。
以下是fork(2)
手册页的相关部分:
错误 EAGAIN fork()不能分配足够的内存来复制父页表并为其分配任务结构 儿童。 EAGAIN由于遇到调用者的RLIMIT_NPROC资源限制,无法创build新进程。 至 超过此限制,该进程必须具有CAP_SYS_ADMIN或CAP_SYS_RESOURCEfunction。 由于内存紧张,ENOMEM fork()无法分配必要的内核结构。
我build议让用户在启动到股票,通用内核以及只加载一小部分模块和驱动程序(运行应用程序/脚本所需的最低限度)后尝试。 从那里,假设它在这种configuration下工作,他们可以在二者之间进行二进制search,并在configuration中发现问题。 这是标准的系统pipe理员故障排除101。
strace
的相关行是:
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7f12708) = -1 ENOMEM (Cannot allocate memory)
…我知道其他人已经谈到了交换和内存的可用性(我会build议你至less设置一个小的交换分区,具有讽刺意味的是,即使它在RAM磁盘上…通过Linux内核的代码path,当它即使可用的交换的一小部分已经比那些可用的交换为零的交换path(exception处理path)得到了更广泛的运用。
不过我怀疑这还是一条红鲱鱼。
free
正在报告0(ZERO)内存正在使用的caching和缓冲区是非常令人不安的事实。 我怀疑free
输出…和可能你的应用程序问题在这里,是由某些专有的内核模块,以某种方式干扰内存分配引起的。
根据fork()/ clone()的手册页,如果您的调用会导致资源限制违规(RLIMIT_NPROC),则fork()系统调用应该返回EAGAIN …但是,它不会说是否要返回EAGAIN受其他RLIMIT *违规。 无论如何,如果你的目标/主机有一些奇怪的Vormetric或其他安全设置(或者即使你的进程运行在一些奇怪的SELinux策略下),那么它可能会导致这个-ENOMEM失败。
这是一个普通的Linux / UNIX问题。 那里有一些不合标准的东西。
你有没有尝试过使用:
(status,output) = commands.getstatusoutput("ps aux")
我认为这已经为我解决了完全相同的问题。 但是后来我的过程最终被杀死了,而不是没有产卵,更糟的是..
经过一些testing,我发现这只发生在旧版本的python上:它发生在2.6.5而不是在2.7.2
我的search导致我在这里python-close_fds问题 ,但不设置closed_fds还没有解决这个问题。 这还是值得一读的。
我发现python泄露文件描述符只是保持眼睛:
watch "ls /proc/$PYTHONPID/fd | wc -l"
和你一样,我想捕获命令的输出,而且我想避免OOM错误…但是看起来人们只能使用较less的bug版本的Python。 不理想…
munmap(0xb7d28000,4096)= 0
写(2,“OSError”,7)= 7
我见过这样粗糙的代码:
serrno = errno; some_Syscall(...) if (serrno != errno) /* sound alarm: CATROSTOPHIC ERROR !!! */
你应该检查这是否是在Python代码中发生的事情。 Errno只有在系统调用失败时才有效。
编辑添加:
你没有说这个过程有多久。 可能的内存消费者
- 分叉的过程
- 未使用的数据结构
- 共享库
- 内存映射文件