MagicStack / uvloop

Ultra fast asyncio event loop.
Apache License 2.0
10.28k stars 539 forks source link

uvloop closes underlying sockets that were established outside upon end of asyncio.run() #464

Open Programmierus opened 2 years ago

Programmierus commented 2 years ago

This seem to be version/OS unrelated. Sample:

import asyncio
import socket
import uvloop

b = bytes('{"msg": "test"}', 'utf-8')

async def main_async(sock: socket.socket):
    _, writer = await asyncio.open_unix_connection(sock=sock)
    writer.write(b)
    await writer.drain()

if __name__ == '__main__':
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.connect('data/sockets/echo_server.sock')
    sock.sendall(b)
    uvloop.install()
    asyncio.run(main_async(sock))  # sock gets wrongly closed on asyncio.run() termination
    sock.sendall(b)  # Exception here OSError: [Errno 9] Bad file descriptor

Last line result in exception "OSError: [Errno 9] Bad file descriptor" because sock gets closed right after asyncio.run() terminates. It is wrong behavior, because sock was created outside of asyncio loop and has to be left untouched. The issue doesn't occur using regular asyncio loop.

ljluestc commented 1 year ago

To work around this issue, you might consider a few alternative approaches:

  1. Use Regular asyncio Event Loop: As you mentioned, the issue doesn't occur with the regular asyncio loop. If this is acceptable for your use case, you can consider sticking with the regular asyncio event loop instead of using uvloop.

  2. Wrap socket in a Context Manager: You can create a custom context manager that wraps the socket and ensures it's not prematurely closed. Here's an example of how you could do this:

import asyncio
import socket
import uvloop
from contextlib import contextmanager

b = bytes('{"msg": "test"}', 'utf-8')

@contextmanager
def open_socket():
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.connect('data/sockets/echo_server.sock')
    yield sock
    sock.close()

async def main_async(sock: socket.socket):
    _, writer = await asyncio.open_unix_connection(sock=sock)
    writer.write(b)
    await writer.drain()

if __name__ == '__main__':
    uvloop.install()
    with open_socket() as sock:
        asyncio.run(main_async(sock))
        sock.sendall(b)  # Should not result in "OSError: [Errno 9] Bad file descriptor"

Using a context manager ensures that the socket is properly closed when you're done with it, avoiding potential issues with the socket's lifecycle.

  1. Check for Updates: Sometimes, issues like this can be due to specific versions of libraries or platforms. Make sure you're using the latest versions of uvloop, asyncio, and other relevant libraries. If the issue is a known bug, there might be a fix available in newer versions.