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.

Title: loop.sock_recv failure because of delayed callback handling
Type: Stage:
Components: asyncio Versions: Python 3.7, Python 3.6
Status: open Resolution:
Dependencies: Superseder:
Assigned To: asvetlov Nosy List: asvetlov, kyuupichan, yselivanov
Priority: normal Keywords:

Created on 2018-09-24 21:40 by kyuupichan, last changed 2022-04-11 14:59 by admin.

Messages (7)
msg326291 - (view) Author: Neil Booth (kyuupichan) * Date: 2018-09-24 21:40
In certain circumstances the coroutine loop.sock_recv() registers a callback internally, which is called on e.g. task cancellation.  The callback assumes a file descriptor that was open and valid at the time the callback was registered is still open and valid when the callback is called, and this need not be the case.  For example, when client code handling exceptions (in particular, cancellation) closes the socket itself.

I have reports of this error for Python 3.6 and 3.7 by a user of my library which uses asyncio, and that bad things happen on both Windows (the event loop dies) and Linux (a traceback happens but no loop death).
It likely happens in earlier versions of Python too but my library requires >= Python 3.6

I hope the above description in addition to pointing out the problematic lines in asyncio make the bug clear.  I tried to produce a simplified testcase but it doesn't trigger the issue on Linux.  I am told it does trigger it on Windows (I don't have Windows to test on).

Here are the problematic lines in where the FD is registered; the callback above them assumes the DF remains valid (but the socket might have been closed in the meantime):

The same problem is evident in sock_recv_into, and perhaps other calls.

Here is code I am told triggers the problem on Windows; it seems to require something a little more complex to trigger reliably on Unix platforms:

import asyncio
import socket

class MyProtocol(asyncio.Protocol):

    def connection_made(self, transport):
        transport.write(b'123')   # just in case a write is needed

port = 6666

async def connect_and_recv(loop, sock):
        await loop.sock_connect(sock, ('', port))
        while True:
            await loop.sock_recv(sock, 20)
    except asyncio.CancelledError:

async def main(loop):
    server = await loop.create_server(MyProtocol, '', port)
    sock = socket.socket()
    task = loop.create_task(connect_and_recv(loop, sock))
    await asyncio.sleep(0.1)
    await asyncio.sleep(0.1)

loop = asyncio.get_event_loop()

Here are backtraces from a client application doing things more complex but similar in spirit to the snippet above:

  File "C:\Users\User\AppData\Local\Programs\Python\Python36-32\lib\asyncio\", line 455, in run_until_complete
  File "C:\Users\User\AppData\Local\Programs\Python\Python36-32\lib\asyncio\", line 422, in run_forever
  File "C:\Users\User\AppData\Local\Programs\Python\Python36-32\lib\asyncio\", line 1398, in _run_once
    event_list =
  File "C:\Users\User\AppData\Local\Programs\Python\Python36-32\lib\", line 323, in select
    r, w, _ = self._select(self._readers, self._writers, [], timeout)
  File "C:\Users\User\AppData\Local\Programs\Python\Python36-32\lib\", line 314, in _select
    r, w, x =, w, w, timeout)
OSError: [WinError 10038] An operation was attempted on something that is not a socket

and one for Linux:

Exception in callback BaseSelectorEventLoop._sock_recv(<Future cancelled>, True, <socket.socke...2049, proto=0>, 5)
handle: <Handle BaseSelectorEventLoop._sock_recv(<Future cancelled>, True, <socket.socke...2049, proto=0>, 5)>
Traceback (most recent call last):
  File "/usr/lib/python3.6/asyncio/", line 127, in _run
  File "/usr/lib/python3.6/asyncio/", line 378, in _sock_recv
  File "/usr/lib/python3.6/asyncio/", line 342, in remove_reader
    return self._remove_reader(fd)
  File "/usr/lib/python3.6/asyncio/", line 279, in _remove_reader
    key = self._selector.get_key(fd)
  File "/usr/lib/python3.6/", line 189, in get_key
    return mapping[fileobj]
  File "/usr/lib/python3.6/", line 70, in __getitem__
    fd = self._selector._fileobj_lookup(fileobj)
  File "/usr/lib/python3.6/", line 224, in _fileobj_lookup
    return _fileobj_to_fd(fileobj)
  File "/usr/lib/python3.6/", line 41, in _fileobj_to_fd
    raise ValueError("Invalid file descriptor: {}".format(fd))
ValueError: Invalid file descriptor: -1
msg326292 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2018-09-24 21:44
Can you reproduce this with uvloop?

Andrew: I think we need to "lock" sockets from closing in sock_recv and friends the same way we do it in uvloop.
msg326293 - (view) Author: Neil Booth (kyuupichan) * Date: 2018-09-24 21:47
This seems related:
msg326296 - (view) Author: Neil Booth (kyuupichan) * Date: 2018-09-24 22:22
My library user reports:

I can't reproduce the issue with uvloop on linux. (looks like uvloop does not work on windows atm)
msg327307 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2018-10-07 20:45
Yuri, do you mean `socket._io_refs` manipulations?
msg327309 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2018-10-07 21:00
msg327310 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2018-10-07 21:04
Looks like a hack but we have no choice
Date User Action Args
2022-04-11 14:59:06adminsetgithub: 78976
2018-10-09 08:57:24asvetlovsetassignee: asvetlov
2018-10-07 21:04:39asvetlovsetmessages: + msg327310
2018-10-07 21:00:57yselivanovsetmessages: + msg327309
2018-10-07 20:45:11asvetlovsetmessages: + msg327307
2018-09-24 22:22:15kyuupichansetmessages: + msg326296
2018-09-24 21:47:01kyuupichansetmessages: + msg326293
2018-09-24 21:44:35yselivanovsetmessages: + msg326292
2018-09-24 21:40:22kyuupichancreate