[issue35754] When writing/closing a closed Popen.stdin, I get OSError vs. BrokenPipeError randomly or depending on bufsize

Eryk Sun report at bugs.python.org
Wed Jan 16 20:57:18 EST 2019


Eryk Sun <eryksun at gmail.com> added the comment:

If I'm right, we can reduce your example down as follows:

    import os
    import subprocess
    import time
    import ctypes

    RtlGetLastNtStatus = ctypes.WinDLL('ntdll').RtlGetLastNtStatus
    RtlGetLastNtStatus.restype = ctypes.c_ulong

    msys = os.path.normpath("C:/msys64/usr/bin")
    head = os.path.join(msys, "head.exe")

    p = subprocess.Popen(head, stdin=subprocess.PIPE,
            stdout=subprocess.PIPE, bufsize=0)

    # head.exe reads 1 KiB. It closes stdin if it finds 10 lines.
    p.stdin.write(b'\n' * 1024)

    # If we immediately fill up the pipe again plus 1 extra byte, 
    # i.e. 4097 bytes for the default queue size, then NPFS will 
    # internally queue a pending IRP. We're synchronous, so the 
    # I/O manager will wait for I/O completion. Meanwhile the child
    # has requested to close its end of the pipe. In this case,
    # NPFS will complete the pending IRP with STATUS_PIPE_BROKEN,
    # which maps to WinAPI ERROR_PIPE_BROKEN and C errno EPIPE.
    #
    # On the other hand, if we wait to give the child's close request
    # time to complete, then NPFS will fail our 4097 byte write 
    # immediately with STATUS_PIPE_CLOSING, which maps to WinAPI
    # ERROR_NO_DATA and C errno EINVAL.

    time.sleep(0.0) # STATUS_PIPE_BROKEN / ERROR_PIPE_BROKEN / EPIPE
    #time.sleep(0.5) # STATUS_PIPE_CLOSING / ERROR_NO_DATA / EINVAL

    try:
        p.stdin.write(b'\n' * 4097)
    except OSError:
        ntstatus = RtlGetLastNtStatus()
        if ntstatus == 0xC000_00B1:
           print('NT Status: STATUS_PIPE_CLOSING\n')
        elif ntstatus == 0xC000_014B:
            print('NT Status: STATUS_PIPE_BROKEN\n')
        else:
            print('NT Status: {}\n'.format(ntstatus, '#010x'))
        raise

This could be addressed by improving our exception handling to look at the C runtime's _doserrno [1] value for EINVAL errors, in order to map ERROR_NO_DATA to EPIPE instead of EINVAL. Only two NT status codes are mapped to ERROR_NO_DATA, and both are pipe related (STATUS_PIPE_CLOSING and STATUS_PIPE_EMPTY), so using EPIPE should be fine.

[1]: https://docs.microsoft.com/en-us/cpp/c-runtime-library/errno-doserrno-sys-errlist-and-sys-nerr?view=vs-2017

----------
nosy: +eryksun

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue35754>
_______________________________________


More information about the Python-bugs-list mailing list