在带有stdout设置的Windows上的python子进程等待孙子

时间:2019-03-14 10:34:58

标签: python windows subprocess pipe stdout

我有一个脚本,它是自动化测试套件的一部分。它在Windows上运行非常慢,但在Linux上却运行不了,我已经找到了原因。我们正在测试的进程(“坦率”)创建一个子进程(因此是一个孙子进程)。直到该子孙进程也结束(在Windows上-在Linux上不执行此操作),Python代码才会返回。如果没有父级,则孙子进程将在5秒后杀死自己(挂起以防其他进程与之对话)

我发现,如果不捕获标准输出,就可以通过这种方式停止通讯功能的挂起。但是我需要标准输出。我在某处读到,通信功能正在等待所有管道关闭。我知道孙子的stdout句柄是重复的,但是我无法更改正在测试的代码。

我一直在寻找解决方案。我尝试了一些创建标志(仍在代码中),但这没有帮助。

这是缩减测试-

import os
import sys
import threading
import subprocess


def read_from_pipe(process):
    last_stdout = process.communicate()[0]
    print (last_stdout)

CREATE_NEW_PROCESS_GROUP = 0x00000200
DETACHED_PROCESS = 0x00000008

# start process
command = 'frank my arguments'

cwd = "C:\\dev\\ui_test\\frank_test\\workspace\\report183"

p = subprocess.Popen(command,
                     stdout=subprocess.PIPE,
                     cwd=cwd)

# run thread to read from output
t = threading.Thread(target=read_from_pipe, args=[p])
t.start()
t.join(30)
print('finished')

有什么想法吗?

谢谢。

彼得。

1 个答案:

答案 0 :(得分:0)

在@eryksun和很多Google搜索提示之后,我有了很多相当复杂的代码!曾经,我考虑过作弊并执行os.system并将其重定向到临时文件,但是后来我意识到我们的测试代码允许命令超时。如果子进程没有死,os.system将会永远阻塞。

import os
import sys
import threading
import subprocess
import time
if os.name == 'nt':
    import msvcrt
    import ctypes

# See https://stackoverflow.com/questions/55160319/python-subprocess-waiting-for-grandchild-on-windows-with-stdout-set for details on Windows code
# Based on https://github.com/it2school/Projects/blob/master/2017/Python/party4kids-2/CarGame/src/pygame/tests/test_utils/async_sub.py

from ctypes.wintypes import DWORD
if sys.version_info >= (3,):
    null_byte = '\x00'.encode('ascii')
else:
    null_byte = '\x00'

def ReadFile(handle, desired_bytes, ol = None):
    c_read = DWORD()
    buffer = ctypes.create_string_buffer(desired_bytes+1)
    success = ctypes.windll.kernel32.ReadFile(handle, buffer, desired_bytes, ctypes.byref(c_read), ol)
    buffer[c_read.value] = null_byte
    return ctypes.windll.kernel32.GetLastError(), buffer.value


def PeekNamedPipe(handle):
    c_avail = DWORD()
    c_message = DWORD()
    success = ctypes.windll.kernel32.PeekNamedPipe(handle, None, 0, None, ctypes.byref(c_avail), ctypes.byref(c_message))
    return "", c_avail.value, c_message.value


def read_available(handle):
    buffer, bytesToRead, result = PeekNamedPipe(handle)
    if bytesToRead:
        hr, data = ReadFile(handle, bytesToRead, None)
        return data
    return b''

def read_from_pipe(process):
    if os.name == 'posix':
        last_stdout = process.communicate()[0]
    else:
        handle = msvcrt.get_osfhandle(process.stdout.fileno())
        last_stdout = b''
        while process.poll() is None:
            last_stdout += read_available(handle)
            time.sleep(0.1)
        last_stdout += read_available(handle)
    print (last_stdout)

# start process
command = 'frank my arguments'

cwd = "C:\\dev\\ui_test\\frank_test\\workspace\\report183"

p = subprocess.Popen(command,
                     stdout=subprocess.PIPE,
                     cwd=cwd)

# run thread to read from output
t = threading.Thread(target=read_from_pipe, args=[p])
t.start()
t.join(30)
print('finished')