This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: asyncio.StreamReader hangs when reading from pipe and other process exits unexpectedly
Type: Stage: resolved
Components: Library (Lib) Versions: Python 3.8
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: asvetlov, jaswdr, kormang, takluyver, yselivanov
Priority: normal Keywords:

Created on 2021-04-11 12:17 by kormang, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (5)
msg390778 - (view) Author: Marko (kormang) Date: 2021-04-11 12:17
When using asyncio to read from pipe, if process on the other side of pipe crashes read operation hangs indefinitely.

Example:


import asyncio
import contextlib
import os
import signal
import time

prx, ctx = os.pipe()

read_transport = None
read_stream = None

async def _connect_read(loop):
    global read_transport
    global read_stream
    stream_reader = asyncio.StreamReader()
    def protocol_factory():
        return asyncio.StreamReaderProtocol(stream_reader)
    rpipe = os.fdopen(prx, mode='r')

    transport, _ = await loop.connect_read_pipe(protocol_factory, rpipe)
    read_transport = transport
    read_stream = stream_reader

def close():
    read_transport.close()

@contextlib.asynccontextmanager
async def connect():
    try:
        loop = asyncio.get_event_loop()
        await _connect_read(loop)
        yield
    finally:
        close()

cpid = os.fork()
if cpid == 0:
    os.kill(os.getpid(), signal.SIGKILL)
    os.write(ctx, b'A')
    time.sleep(10.0)
else:
    async def read_from_child():
        async with connect():
            input = await read_stream.read(1)
            print('Parent received: ', input)

    asyncio.run(read_from_child())
msg392830 - (view) Author: Jonathan Schweder (jaswdr) * Date: 2021-05-03 18:35
@kormang this is an expected behaviour, this is a problem even for the OS level, just because it is impossible to know when the reader needs to stop waiting, the best option here is to implement some timeout mechanism.
msg393180 - (view) Author: Marko (kormang) Date: 2021-05-07 09:54
@jaswdr Hm, this is was somewhat unexpected for me, since when reading directly from pipe, and EOF is sent.

Timeout was somewhat awkward in my case, since I don't know when other process will start sending, and how long it would take. On the other hand, I use asyncio loop, and can do this asynchronously, so I get notified when child process dies, by other means, and close the stream. So there are plenty of possible workarounds, but I'm not sure it is impossible to solve the problem on the library level yet. It would take more digging into implementation from my side, however.
msg406949 - (view) Author: Thomas Kluyver (takluyver) * Date: 2021-11-24 19:02
In the example script, I believe you need to close the write end of the pipe in the parent after forking:

cpid = os.fork()
if cpid == 0:
    # Write to pipe (child)
else:
    # Parent
    os.close(ctx)
    # Read from pipe

This is the same with synchronous code: os.read(prx, 1) also hangs. You only get EOF when nothing has the write end open any more. All the asyncio machinery doesn't really make any difference to this.

For a similar reason, the code writing (the child, in this case) should close the read end of the pipe after forking. If the parent goes away but the child still has the read end open, then trying to write to the pipe can hang (if the buffer is already full). If the child has closed the read end, trying to write will give you a BrokenPipeError.
msg407702 - (view) Author: Marko (kormang) Date: 2021-12-05 12:05
Thanks, takluyver!

You are right. Synchronous code that I was comparing it to had os.close(ctx), but I forgot to add it in the async example, so I thought it was a bug.

Closing this issue.
History
Date User Action Args
2022-04-11 14:59:44adminsetgithub: 87972
2021-12-05 12:05:52kormangsetstatus: open -> closed
resolution: not a bug
messages: + msg407702

stage: resolved
2021-11-24 19:02:40takluyversetnosy: + takluyver
messages: + msg406949
2021-05-07 09:54:28kormangsetmessages: + msg393180
2021-05-03 18:35:29jaswdrsetnosy: + jaswdr
messages: + msg392830
2021-04-16 20:23:14terry.reedysetnosy: + asvetlov, yselivanov
2021-04-11 12:17:49kormangcreate