RuntimeError: File descriptor 8 is used by transport

RuntimeError: File descriptor 8 is used by transport

最小演示示例:

import asyncio

async def main():
    c1_reader, c1_writer = await asyncio.open_connection(host='google.com', port=80)
    c1_socket = c1_writer.get_extra_info('socket')
    c1_socket.close()

    c2_reader, c2_writer = await asyncio.open_connection(host='google.com', port=80)

asyncio.run(main())

运行 这个程序给出了这个错误:

$ python3 asyncio_fd_used.py
Traceback (most recent call last):
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/selector_events.py", line 469, in _sock_connect
    sock.connect(address)
BlockingIOError: [Errno 36] Operation now in progress

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "asyncio_fd_used.py", line 11, in <module>
    asyncio.run(main())
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 579, in run_until_complete
    return future.result()
  File "asyncio_fd_used.py", line 9, in main
    c2_reader, c2_writer = await asyncio.open_connection(host='google.com', port=80)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/streams.py", line 77, in open_connection
    lambda: protocol, host, port, **kwds)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 941, in create_connection
    await self.sock_connect(sock, address)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/selector_events.py", line 463, in sock_connect
    self._sock_connect(fut, sock, address)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/selector_events.py", line 477, in _sock_connect
    self.add_writer(fd, self._sock_connect_cb, fut, sock, address)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/selector_events.py", line 333, in add_writer
    self._ensure_fd_no_transport(fd)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/selector_events.py", line 244, in _ensure_fd_no_transport
    f'File descriptor {fd!r} is used by transport '
RuntimeError: File descriptor 8 is used by transport <_SelectorSocketTransport fd=8 read=polling write=<idle, bufsize=0>>

只是为了解释为什么我在做低级别 socket.close() 而不是异步级别 writer.close():我正在尝试一些代码来发送 RST 数据包。但我可以想象人们打电话给 socket.close() 的其他原因,甚至可能是无意的。

问题是低级套接字被关闭了,但是asyncio并不知道它并认为它仍然是打开的。由于某种原因(性能?)asyncio 记住了套接字文件描述符(fileno)。

当打开一个新连接时,操作系统会为它提供相同的文件描述符编号,并且 asyncio 开始恐慌,因为它具有与前一个连接关联的完全相同的 fd 编号。

解决方案:告诉 asyncio 套接字已关闭:)

import asyncio

async def main():
    c1_reader, c1_writer = await asyncio.open_connection(host='google.com', port=80)
    c1_socket = c1_writer.get_extra_info('socket')
    c1_socket.close()
    c1_writer.close()  # <<< here

    c2_reader, c2_writer = await asyncio.open_connection(host='google.com', port=80)

asyncio.run(main())

此代码运行时未引发错误。