如何检测子进程何时在Windows

时间:2018-03-21 13:56:29

标签: python windows subprocess io-redirection

我有一个子进程,要么退出返回码,要么问某些东西并等待用户输入。

我想检测进程何时提出问题并立即退出。过程提出问题的事实足以让我决定系统的状态。

问题在于我无法阅读问题,因为子进程可能不会刷新标准输出。所以我不能依赖解析subprocess.Popen().stdout:当试图读取它时,它会因为首先读取输入而阻塞。

有点像这样

# ask.py, just asks something without printing anything if a condition is met
# here, we'll say that the condition is always met
input()

当然,实际的子进程是第三方二进制文件,我无法轻易修改它以添加必要的刷新调用,这将解决它。

我还可以尝试相当于unbufferWhat is the equivalent of unbuffer program on Windows?)的Windows,它名为winpty,它可能(可能)允许我检测输出并解决当前问题,但我我想保持简单,我想首先解决标准输入问题......

我试过......好吧,很多事情都行不通,包括尝试将假文件作为stdin参数传递,这不起作用,因为subprocess需要{{1}该文件,我们无法提供垃圾...

fileno

p = subprocess.Popen(["python","ask.py"],...) 与字符串一起使用也不起作用,因为您无法控制何时读取字符串以供应给子进程(可能通过系统管道)。

这些问题很有希望,但要么依赖标准输出,要么只适用于Linux

我目前正在做的是在超时时运行进程,如果达到超时,则我决定程序被阻止。但它花费了超时等待时间。如果我能在子进程读取communicate后立即做出决定,那就更好了。

我想知道是否存在本机python解决方案(可能使用stdin和Windows扩展)来检测stdin的读取。但是,本机解决方案不使用Python而是使用非Microsoft专有语言。

2 个答案:

答案 0 :(得分:2)

我想知道子进程是否读取用户输入是为了(ab)使用文件对象是有状态的这一事实:如果进程从其stdin读取数据,我们应该能够检测到stdin状态的变化。

程序如下:

  1. 创建一个临时文件,该文件将用作子进程的stdin
  2. 将一些数据写入文件
  3. 启动流程
  4. 等待一段时间让进程读取数据(或不是),然后使用tell()方法查明是否从文件中读取了任何内容
  5. 这是代码:

    import os
    import time
    import tempfile
    import subprocess
    
    # create a file that we can use as the stdin for the subprocess
    with tempfile.TemporaryFile() as proc_stdin:
        # write some data to the file for the subprocess to read
        proc_stdin.write(b'whatever\r\n')
        proc_stdin.seek(0)
    
        # start the thing
        cmd = ["python","ask.py"]
        proc = subprocess.Popen(cmd, stdin=proc_stdin, stdout=subprocess.PIPE)
    
        # wait for it to start up and do its thing
        time.sleep(1)
    
        # now check if the subprocess read any data from the file
        if proc_stdin.tell() == 0:
            print("it didn't take input")
        else:
            print("it took input")
    

    理想情况下,临时文件可能会被某种管道或不能将任何数据写入磁盘的东西所取代,但遗憾的是,如果没有真正的磁盘文件,我找不到让它工作的方法。

答案 1 :(得分:2)

如果我们不想让子进程处理用户输入,但在这种情况下简单地杀死它,解决方案可以是下一步:

  • 启动子进程,将 stdin 重定向到管道。
  • 管道服务器端我们在异步模式和主集管道中创建 缓冲区为0大小
  • 在启动子项之前 - 向此管道写入1个字节。
  • 因为管道缓冲区大小为0 - 操作不完整,直到另一个 方不读这个字节
  • 写完这个1字节后正在进行的操作(待定) - 启动子进程。
  • 终于开始等待完成第一个:写操作还是子进程?
  • 如果先写完成 - 这意味着,该子进程开始读取 来自 stdin - 所以在这一点上杀了它

c ++上的一种可能的实现

struct ReadWriteContext : public OVERLAPPED
{
    enum OpType : char { e_write, e_read } _op;
    BOOLEAN _bCompleted;

    ReadWriteContext(OpType op) : _op(op), _bCompleted(false)
    {
    }
};

VOID WINAPI OnReadWrite(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, OVERLAPPED* lpOverlapped)
{
    static_cast<ReadWriteContext*>(lpOverlapped)->_bCompleted = TRUE;
    DbgPrint("%u:%x %p\n", static_cast<ReadWriteContext*>(lpOverlapped)->_op, dwErrorCode, dwNumberOfBytesTransfered);
}

void nul(PCWSTR lpApplicationName)
{
    ReadWriteContext wc(ReadWriteContext::e_write), rc(ReadWriteContext::e_read);

    static const WCHAR pipename[] = L"\\\\?\\pipe\\{221B9EC9-85E6-4b64-9B70-249026EFAEAF}";

    if (HANDLE hPipe = CreateNamedPipeW(pipename, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, 
        PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, 1, 0, 0, 0, 0))
    {
        static SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
        PROCESS_INFORMATION pi;
        STARTUPINFOW si = { sizeof(si)};
        si.dwFlags = STARTF_USESTDHANDLES;
        si.hStdInput = CreateFileW(pipename, FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, 0);

        if (INVALID_HANDLE_VALUE != si.hStdInput)
        {
            char buf[256];

            if (WriteFileEx(hPipe, "\n", 1, &wc, OnReadWrite))
            {
                si.hStdError = si.hStdOutput = si.hStdInput;

                if (CreateProcessW(lpApplicationName, 0, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &si, &pi))
                {
                    CloseHandle(pi.hThread);

                    BOOLEAN bQuit = true;

                    goto __read;
                    do 
                    {
                        bQuit = true;

                        switch (WaitForSingleObjectEx(pi.hProcess, INFINITE, TRUE))
                        {
                        case WAIT_OBJECT_0:
                            DbgPrint("child terminated\n");
                            break;
                        case WAIT_IO_COMPLETION:
                            if (wc._bCompleted)
                            {
                                DbgPrint("child read from hStdInput!\n");
                                TerminateProcess(pi.hProcess, 0);
                            }
                            else if (rc._bCompleted)
                            {
__read:
                                rc._bCompleted = false;
                                if (ReadFileEx(hPipe, buf, sizeof(buf), &rc, OnReadWrite))
                                {
                                    bQuit = false;
                                }
                            }
                            break;
                        default:
                            __debugbreak();
                        }
                    } while (!bQuit);

                    CloseHandle(pi.hProcess);
                }
            }

            CloseHandle(si.hStdInput);

            // let execute pending apc
            SleepEx(0, TRUE);
        }

        CloseHandle(hPipe);
    }
}

另一种代码变体 - 使用事件完成,而不是apc。但这不会影响最终结果。这个代码变体给出了与第一个完全相同的结果:

void nul(PCWSTR lpApplicationName)
{
    OVERLAPPED ovw = {}, ovr = {};

    if (ovr.hEvent = CreateEvent(0, 0, 0, 0))
    {
        if (ovw.hEvent = CreateEvent(0, 0, 0, 0))
        {
            static const WCHAR pipename[] = L"\\\\?\\pipe\\{221B9EC9-85E6-4b64-9B70-249026EFAEAF}";

            if (HANDLE hPipe = CreateNamedPipeW(pipename, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, 
                PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, 1, 0, 0, 0, 0))
            {
                static SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
                PROCESS_INFORMATION pi;
                STARTUPINFOW si = { sizeof(si)};
                si.dwFlags = STARTF_USESTDHANDLES;
                si.hStdInput = CreateFileW(pipename, FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, 0);

                if (INVALID_HANDLE_VALUE != si.hStdInput)
                {
                    char buf[256];

                    if (!WriteFile(hPipe, "\n", 1, 0, &ovw) && GetLastError() == ERROR_IO_PENDING)
                    {
                        si.hStdError = si.hStdOutput = si.hStdInput;

                        if (CreateProcessW(lpApplicationName, 0, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &si, &pi))
                        {
                            CloseHandle(pi.hThread);

                            BOOLEAN bQuit = true;

                            HANDLE h[] = { ovr.hEvent, ovw.hEvent, pi.hProcess };

                            goto __read;
                            do 
                            {
                                bQuit = true;

                                switch (WaitForMultipleObjects(3, h, false, INFINITE))
                                {
                                case WAIT_OBJECT_0 + 0://read completed
__read:
                                    if (ReadFile(hPipe, buf, sizeof(buf), 0, &ovr) || GetLastError() == ERROR_IO_PENDING)
                                    {
                                        bQuit = false;
                                    }
                                    break;
                                case WAIT_OBJECT_0 + 1://write completed
                                    DbgPrint("child read from hStdInput!\n");
                                    TerminateProcess(pi.hProcess, 0);
                                    break;
                                case WAIT_OBJECT_0 + 2://process terminated
                                    DbgPrint("child terminated\n");
                                    break;
                                default:
                                    __debugbreak();
                                }
                            } while (!bQuit);

                            CloseHandle(pi.hProcess);
                        }
                    }

                    CloseHandle(si.hStdInput);
                }

                CloseHandle(hPipe);
                // all pending operation completed here.
            }

            CloseHandle(ovw.hEvent);
        }

        CloseHandle(ovr.hEvent);
    }
}