Search code examples
python-3.xpython-asyncioaiohttp

Closing asyncio event loop in Python causes exception at end


For https requests using asyncio and aiohttp in Python 3.4 on Windows I'll need to use 2 event loops. A ProactorEventLoop for running shell commands, and the default event loop for HTTPS requests. The ProactorEventLoop does not work for HTTPS commands, unfortunately.

The following code below shows what happens when I use a newly created default event loop and try to close it at the end on Windows. I get exceptions at the end if I call loop.close at the end as shown below:

> Traceback (most recent call last):
>  File "C:\BuildUtilities\p3.4env0\lib\site-packages\aiohttp\connector.py", line 56, in __del__
>    self.close()
>  File "C:\BuildUtilities\p3.4env0\lib\site-packages\aiohttp\connector.py", line 97, in close
>    transport.close()
>  File "C:\Python34\Lib\asyncio\selector_events.py", line 375, in close
>    self._loop.remove_reader(self._sock_fd)
>  File "C:\Python34\Lib\asyncio\selector_events.py", line 155, in remove_reader
>    key = self._selector.get_key(fd)
> AttributeError: 'NoneType' object has no attribute 'get_key'

Commenting it out removes the exception and I don't know why. The one and only

import asyncio
import aiohttp

@asyncio.coroutine
def get_body(url):
    response = yield from aiohttp.request('GET', url)
    return (yield from response.read_and_close())

#loop = asyncio.ProactorEventLoop()
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

f = asyncio.async( get_body('https://www.google.com') )
try:
    loop.run_until_complete(f)
except Exception as e:
    print(e)

if f.result():
    print(f.result())

loop.close()

Thanks, greenaj


Solution

  • Update: it looks like the issue is fixed in the github version (0.7.2). It doesn't produce the error. As @danj.py said, it is fixed by "Get rid of __del__ in connector" commit.


    It is not ProactorEventLoop or Windows specific. I can reproduce the error on Ubuntu with the default event loop:

    #!/usr/bin/env python3
    import asyncio
    import aiohttp # $ pip install aiohttp
    
    @asyncio.coroutine
    def get_body(url):
        response = yield from aiohttp.request('GET', url)
        return (yield from response.read_and_close())
    
    loop = asyncio.get_event_loop()
    body = loop.run_until_complete(get_body('https://stackoverflow.com/q/23283502'))
    print(len(body), type(body), body[:200])
    loop.close()
    

    It might be a bug in aiohttp because the usage seems correct.

    There is no error if the request is made without aiohttp:

    #!/usr/bin/env python3
    import asyncio
    from contextlib import closing
    from urllib.parse import urlsplit
    
    @asyncio.coroutine
    def get_body(url):
        # parse url
        url = urlsplit(url)
        path = '/' * (not url.path) + url.path + '?' * bool(url.query) + url.query
        # open connection
        reader, writer = yield from asyncio.open_connection(
            host=url.hostname,
            port=url.port or (443 if url.scheme == 'https' else 80),
            ssl=(url.scheme == 'https'))
        with closing(writer):
            # send request
            writer.write(b'GET ' + path.encode('ascii') + b' HTTP/1.1\r\n'
                         b'Host: ' + url.netloc.encode('ascii') + b'\r\n'
                         b'Connection: close\r\n\r\n')
            # read headers
            while True:
                line = yield from reader.readline()
                line = line.rstrip(b'\n\r')
                print(line.decode('latin-1'))
                if not line:
                    break
            # read body
            body = yield from reader.read()
        return body
    
    loop = asyncio.get_event_loop()
    body = loop.run_until_complete(get_body('https://stackoverflow.com/q/23283502'))
    print(len(body), type(body), body[:200])
    loop.close()
    

    Note: the examples are not completely equivalent e.g., the latter doesn't follow redirects.