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

我有一个Python脚本,它使用
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异常报告)”啊。那么,那个实验就不会太有趣了。将删除该实验性建议。也改变了我的假设。谢谢你,林格维,你的分析是对的。删除many
process.poll()
确实可以解决返回代码问题,这意味着它可能是返回代码计算中的竞争条件。我刚刚编辑了您创建的
logPipe
函数,因为它目前没有在日志文件中写入任何内容。仅供参考调用方代码设计用于调用非常长的程序,需要大量日志记录和相当长的执行时间,因此
Popen.communicate
不会飞到这里,我需要一些不会阻塞内存的东西,并且在消息释放到管道中时打印消息。@lyngvi:请,对p.stdout中的行使用
而不是对p.stdout.readlines()中的行使用
@seb\u fx:如果直接将输出重定向到文件(传递文件对象而不是管道),效果会更好。或者,如果您需要在Python中过滤这些行,那么看看它是否是某种特殊的Python构建?为什么在导入默认情况下应该出现在redhat上的stdlib模块时捕获异常?