Python子流程:读取返回代码有时与返回代码不同
我有一个Python脚本,它使用Python子流程:读取返回代码有时与返回代码不同,python,subprocess,Python,Subprocess,我有一个Python脚本,它使用subprocess.Popen调用另一个Python脚本。我知道被调用的代码总是返回10,这意味着它失败了 我的问题是,打电话的人只有大约75%的时间读10。另外25%的代码读取0,并将调用的程序失败代码错误为成功。同样的命令,同样的环境,显然是随机发生的 环境:Python 2.7.10、Linux Redhat 6.4。这里给出的代码是一个(非常)简化的版本,但我仍然可以使用它重现问题 这就是所谓的脚本constant_return.py: #!/usr/b
subprocess.Popen
调用另一个Python脚本。我知道被调用的代码总是返回10,这意味着它失败了
我的问题是,打电话的人只有大约75%的时间读10。另外25%的代码读取0,并将调用的程序失败代码错误为成功。同样的命令,同样的环境,显然是随机发生的
环境:Python 2.7.10、Linux Redhat 6.4。这里给出的代码是一个(非常)简化的版本,但我仍然可以使用它重现问题
这就是所谓的脚本constant_return.py:
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
"""
Simplified called code
"""
import sys
if __name__ == "__main__":
sys.exit(10)
这是调用方代码:
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
"""
Simplified version of the calling code
"""
try:
import sys
import subprocess
import threading
except Exception, eImp:
print "Error while loading Python library : %s" % eImp
sys.exit(100)
class BizarreProcessing(object):
"""
Simplified caller class
"""
def __init__(self):
"""
Classic initialization
"""
object.__init__(self)
def logPipe(self, isStdOut_, process_):
"""
Simplified log handler
"""
try:
if isStdOut_:
output = process_.stdout
logfile = open("./log_out.txt", "wb")
else:
output = process_.stderr
logfile = open("./log_err.txt", "wb")
#Read pipe content as long as the process is running
while (process_.poll() == None):
text = output.readline()
if (text != '' and text.strip() != ''):
logfile.write(text)
#When the process is finished, there might still be lines remaining in the pipe
output.readlines()
for oneline in output.readlines():
if (oneline != None and oneline.strip() != ''):
logfile.write(text)
finally:
logfile.close()
def startProcessing(self):
"""
Launch process
"""
# Simplified command line definition
command = "/absolute/path/to/file/constant_return.py"
# Execute command in a new process
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
#Launch a thread to gather called programm stdout and stderr
#This to avoid a deadlock with pipe filled and such
stdoutTread = threading.Thread(target=self.logPipe, args=(True, process))
stdoutTread.start()
stderrThread = threading.Thread(target=self.logPipe, args=(False, process))
stderrThread.start()
#Wait for the end of the process and get process result
stdoutTread.join()
stderrThread.join()
result = process.wait()
print("returned code: " + str(result))
#Send it back to the caller
return (result)
#
# Main
#
if __name__ == "__main__":
# Execute caller code
processingInstance = BizarreProcessing()
aResult = processingInstance.startProcessing()
#Return the code
sys.exit(aResult)
下面是我在bash中键入的内容,以执行调用方脚本:
{1..100}中res的
做
/path/to/caller/script.py
echo$?>>/tmp/returncodelist.txt
完成
它似乎以某种方式与我读取被调用程序输出的方式相连接,因为当我使用process=subprocess.Popen(command,shell=True,stdout=sys.stdout,stderr=sys.stderr)创建子流程时,它读取正确的返回代码(但不再按照我想要的方式记录…)并删除所有线程内容
知道我做错了什么吗
非常感谢您的帮助logPipe还在检查进程是否处于活动状态,以确定是否有更多数据需要读取。这是不正确的-您应该通过查找零长度读取或使用output.readlines()检查管道是否已达到EOF。I/O管道的寿命可能比进程长
这大大简化了logPipe:更改logPipe如下:
def logPipe(self, isStdOut_, process_):
"""
Simplified log handler
"""
try:
if isStdOut_:
output = process_.stdout
logfile = open("./log_out.txt", "wb")
else:
output = process_.stderr
logfile = open("./log_err.txt", "wb")
#Read pipe content as long as the process is running
with output:
for text in output:
if text.strip(): # ... checks if it's not an empty string
logfile.write(text)
finally:
logfile.close()
其次,在process.wait()之后才加入日志线程,原因相同-I/O管道可能会比进程寿命长
我认为在幕后发生的事情是,有一个信号管在某个地方被释放和错误处理——可能被误解为进程终止条件。这是因为管道的一端或另一端正在关闭,而没有冲洗。SIGPIPE有时会在更大的应用中造成麻烦;可能是Python库吞下了它,或者用它做了一些幼稚的事情
edit正如@Blackjack所指出的,Python会自动阻止SIGPIPE。所以,这就排除了信号管渎职的可能性。第二个理论是:Popen.poll()背后的文档说明:
检查子进程是否已终止。设置并返回返回代码
属性
如果您选择了这个(例如,strace-f-o strace.log./caller.py
),这似乎是通过wait4(WNOHANG)完成的。有两个线程在WNOHANG中等待,一个线程在正常情况下等待,但只有一个调用会正确返回进程退出代码。如果subprocess.poll()的实现中没有锁定,则很可能存在分配process.resultcode的竞争,或者可能无法正确分配process.resultcode。将Popen.waits/polls限制为单个线程应该是避免这种情况的好方法。请参见man waitpid
编辑如果可以将所有stdout/stderr数据保存在内存中,则subprocess.communicate()更易于使用,并且根本不需要日志管道或后台线程
状态为:“*SIGPIPE*被忽略(因此管道和套接字上的写入错误可以作为普通Python异常报告)”啊。那么,那个实验就不会太有趣了。将删除该实验性建议。也改变了我的假设。谢谢你,林格维,你的分析是对的。删除manyprocess.poll()
确实可以解决返回代码问题,这意味着它可能是返回代码计算中的竞争条件。我刚刚编辑了您创建的logPipe
函数,因为它目前没有在日志文件中写入任何内容。仅供参考调用方代码设计用于调用非常长的程序,需要大量日志记录和相当长的执行时间,因此Popen.communicate
不会飞到这里,我需要一些不会阻塞内存的东西,并且在消息释放到管道中时打印消息。@lyngvi:请,对p.stdout中的行使用:
而不是对p.stdout.readlines()中的行使用:
@seb\u fx:如果直接将输出重定向到文件(传递文件对象而不是管道),效果会更好。或者,如果您需要在Python中过滤这些行,那么看看它是否是某种特殊的Python构建?为什么在导入默认情况下应该出现在redhat上的stdlib模块时捕获异常?