Skip to content

"Assertion `loop->watchers[w->fd] == w' failed." with multiprocessing and OS pipes #317

Description

@momocow

Hi, I encountered the libuv assertion error while running multiprocessing and communicating with child processes via OS pipes.

I've prepared a minimal reproducible case.

I'm using kchmck/aiopipe to create pipes, which actually wraps file descriptors from os.pipe() into asyncio StreamReaders/StreamWrites via loop.connect_read_pipe() and loop.connect_write_pipe().

Here, I have decapsulated wrapper classes from aiopipe into functions in the aiopipe_decap.py for easier code tracing.

# aiopipe_decap.pyimportasyncioimportosfromasyncioimport (BaseTransport, StreamReader, StreamReaderProtocol,
StreamWriter, get_running_loop)
fromcontextlibimportasynccontextmanager, contextmanagerasyncdef_open_reader(fd):
rx=StreamReader()
transport, _=awaitget_running_loop().connect_read_pipe(
lambda: StreamReaderProtocol(rx),
os.fdopen(fd))
returntransport, rxasyncdef_open_writer(fd):
rx=StreamReader()
transport, proto=awaitget_running_loop().connect_write_pipe(
lambda: StreamReaderProtocol(rx),
os.fdopen(fd, "w"))
tx=StreamWriter(transport, proto, rx, get_running_loop())
returntransport, tx@asynccontextmanagerasyncdefopen_stream(fd, mode):
transport, stream=await_open_reader(fd) ifmode=="r" \
elseawait_open_writer(fd)
try:
yieldstreamfinally:
try:
transport.close()
exceptOSError:
# The transport/protocol sometimes closes the fd before this is reached.pass# Allow event loop callbacks to run and handle closed transport.awaitasyncio.sleep(0)
@contextmanagerdefdetach(fd):
os.set_inheritable(fd, True)
try:
yieldfinally:
os.close(fd)

And here is the reproducible snippet, which creates a pipe connection between a child process and the master process. Once the child starts up, it will start a loop to send messages back to the master.

You can use the following environment variables to control some of the behaviors in this snippet.

  • U: enable uvloop if this variable is set (default: False)
  • R: number of messages to send (default: 1)
  • M: content of messages (default: "a")
# uv.pyimportasyncioimportosfrommultiprocessingimportProcessfromaiopipe_decapimport*importuvloopasyncdefchild_task(fd, message, repeat):
asyncwithopen_stream(fd, "w") astx:
foriinrange(repeat):
tx.write(message)
awaittx.drain()
tx.write_eof()
defchild_main(fd, message, repeat):
asyncio.run(child_task(fd, message, repeat))
asyncdefmain(*, message=b"a", repeat=1):
rfd, tfd=os.pipe()
withdetach(tfd):
proc=Process(target=child_main, args=(tfd, message, repeat))
proc.start()
count=0asyncwithopen_stream(rfd, "r") asrx:
whileTrue:
msg=awaitrx.read(1)
ifnotmsg:
breakcount+=1assertmsg==messageassertcount==repeatif__name__=="__main__":
ifos.getenv("U", ""):
uvloop.install()
rp=int(os.getenv("R", "1"))
msg=os.getenv("M", "a").encode()
asyncio.run(main(message=msg, repeat=rp))

Here's my result on my NAS (in Ubuntu 16.04 container). I found the assertion error related to the number of repeated times, the more the number is, the more chance to trigger the error. In the log shows that 9216 is a magic number but I doubt it's depending on different environments.

(venv) $ U=1 R=9210 python uv.py
(venv) $ U=1 R=9211 python uv.py
(venv) $ U=1 R=9212 python uv.py
(venv) $ U=1 R=9213 python uv.py
(venv) $ U=1 R=9214 python uv.py
(venv) $ U=1 R=9215 python uv.py
(venv) $ U=1 R=9216 python uv.py
(venv) $ U=1 R=9217 python uv.py
python: src/unix/core.c:932: uv__io_stop: Assertion `loop->watchers[w->fd] == w' failed.
Traceback (most recent call last):
File "uv.py", line 85, in <module>
asyncio.run(main(message=msg, repeat=rp))
File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete
File "uv.py", line 77, in main
assert count == repeat
AssertionError
(venv) $ U=1 R=9218 python uv.py
python: src/unix/core.c:932: uv__io_stop: Assertion `loop->watchers[w->fd] == w' failed.
Traceback (most recent call last):
File "uv.py", line 85, in <module>
asyncio.run(main(message=msg, repeat=rp))
File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete
File "uv.py", line 77, in main
assert count == repeat
AssertionError
(venv) $ U=1 R=9219 python uv.py
python: src/unix/core.c:932: uv__io_stop: Assertion `loop->watchers[w->fd] == w' failed.
Traceback (most recent call last):
File "uv.py", line 85, in <module>
asyncio.run(main(message=msg, repeat=rp))
File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete
File "uv.py", line 77, in main
assert count == repeat
AssertionError
(venv) $ U=1 R=9220 python uv.py
python: src/unix/core.c:932: uv__io_stop: Assertion `loop->watchers[w->fd] == w' failed.
Traceback (most recent call last):
File "uv.py", line 85, in <module>
asyncio.run(main(message=msg, repeat=rp))
File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete
File "uv.py", line 77, in main
assert count == repeat
AssertionError

It works like a charm with vanilla asyncio.

(venv) $ R=9210 python uv.py
(venv) $ R=9211 python uv.py
(venv) $ R=9212 python uv.py
(venv) $ R=9213 python uv.py
(venv) $ R=9214 python uv.py
(venv) $ R=9215 python uv.py
(venv) $ R=9216 python uv.py
(venv) $ R=9217 python uv.py
(venv) $ R=9218 python uv.py
(venv) $ R=9219 python uv.py
(venv) $ R=9220 python uv.py

With even larger number of repeated times.

  • R=100K
(venv) $ R=100000 python uv.py
(venv) $ U=1 R=100000 python uv.py
python: src/unix/core.c:932: uv__io_stop: Assertion `loop->watchers[w->fd] == w' failed.
Traceback (most recent call last):
File "uv.py", line 44, in <module>
asyncio.run(main(message=msg, repeat=rp))
File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete
File "uv.py", line 36, in main
assert count == repeat
AssertionError
  • R=1M
(venv) $ R=1000000 python uv.py
(venv) $ U=1 R=1000000 python uv.py
python: src/unix/core.c:932: uv__io_stop: Assertion `loop->watchers[w->fd] == w' failed.
Traceback (most recent call last):
File "uv.py", line 44, in <module>
asyncio.run(main(message=msg, repeat=rp))
File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete
File "uv.py", line 36, in main
assert count == repeat
AssertionError
  • uvloop version: 0.14.0
  • Python version: Python 3.7.3
  • Platform: Linux-4.2.8-x86_64-with-debian-stretch-sid'
  • Can you reproduce the bug with PYTHONASYNCIODEBUG in env?: Yes
(venv) $ PYTHONASYNCIODEBUG=1 U=1 R=100000 python uv.py
python: src/unix/core.c:932: uv__io_stop: Assertion `loop->watchers[w->fd] == w' failed.
Traceback (most recent call last):
File "uv.py", line 44, in <module>
asyncio.run(main(message=msg, repeat=rp))
File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete
File "uv.py", line 36, in main
assert count == repeat
AssertionError
  • Does uvloop behave differently from vanilla asyncio? How?: Vanilla asyncio works like a charm while uvloop raise the assertion error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions