Bug report
Bug description:
BaseSelectorEventLoop._sock_accept doesn't check whether its future is already done before using it, unlike the others (_sock_recv, _sock_recv_into, _sock_recvfrom etc). When _sock_accept calls with already cancelled future, it will raise InvalidStateError and connection is dropped instead of being left for the next calls
def _sock_accept(self, fut, sock):
fd = sock.fileno()
try:
conn, address = sock.accept()
conn.setblocking(False)
except (BlockingIOError, InterruptedError):
self._ensure_fd_no_transport(fd)
handle = self._add_reader(fd, self._sock_accept, fut, sock)
fut.add_done_callback(
functools.partial(self._sock_read_done, fd, handle=handle))
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
fut.set_exception(exc)
else:
fut.set_result((conn, address))
Reproducer:
import asyncio
import select
import socket
async def main():
errors = []
loop = asyncio.get_running_loop()
loop.set_exception_handler(lambda l, c: errors.append(c))
lsock = socket.socket()
lsock.setblocking(False)
lsock.bind(("127.0.0.1", 0))
lsock.listen()
acc = loop.create_task(loop.sock_accept(lsock))
await asyncio.sleep(0)
client = socket.socket()
client.connect(lsock.getsockname())
select.select([lsock], [], [])
loop.call_soon(acc.cancel)
await asyncio.sleep(0)
await asyncio.sleep(0)
print(errors)
try:
await asyncio.wait_for(loop.sock_accept(lsock), 0.5)
print("ALIVE")
except TimeoutError:
print("DEAD")
asyncio.run(main())
Expected:
Actually:
[{'message': 'Exception in callback BaseSelectorEventLoop._sock_accept()', 'exception': InvalidStateError('invalid state'), 'handle': <Handle cancelled>}]
DEAD
Proposed fix is to add the guard:
def _sock_accept(self, fut, sock):
if fut.done():
return
fd = sock.fileno()
try:
conn, address = sock.accept()
conn.setblocking(False)
except (BlockingIOError, InterruptedError):
self._ensure_fd_no_transport(fd)
handle = self._add_reader(fd, self._sock_accept, fut, sock)
fut.add_done_callback(
functools.partial(self._sock_read_done, fd, handle=handle))
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
fut.set_exception(exc)
else:
fut.set_result((conn, address))
I have a fix ready
CPython versions tested on:
CPython main branch
Operating systems tested on:
macOS
Linked PRs
Bug report
Bug description:
BaseSelectorEventLoop._sock_acceptdoesn't check whether its future is already done before using it, unlike the others (_sock_recv,_sock_recv_into,_sock_recvfrometc). When_sock_acceptcalls with already cancelled future, it will raiseInvalidStateErrorand connection is dropped instead of being left for the next callsReproducer:
Expected:
Actually:
Proposed fix is to add the guard:
I have a fix ready
CPython versions tested on:
CPython main branch
Operating systems tested on:
macOS
Linked PRs