Skip to content

aiohttp socket leak and incorrect connection close #1568

Closed
@thehesiod

Description

Long story short

Issue 1

it appears for certain servers the ssl sockets are not released correctly.

Issue 2:

I believe aiohttp.client._RequestContextManager.__aexit__ () should not close() the connector on exception as this should be up to the client of aiohttp. Instead it should always opportunistically release the socket. Incidentally changing this to release also fixes #1 in my scenario.

Expected behaviour

Issue 1

With the sample script the number of open handles should stay relatively stable.

Issue 2

raising a custom exception from an aiohttp request contest should not cause the connector to be closed and a new one opened.

Actual behaviour

Issue 1

number of context handles grows very quickly with test script. Is resolved by changing https://github.com/KeepSafe/aiohttp/blob/master/aiohttp/connector.py#L392 to be abort(), or changing https://github.com/KeepSafe/aiohttp/blob/master/aiohttp/client.py#L555 to always release. However there appears to be an underlying issue with closing SSL sockets with certain servers.

Issue 2

tcpconnector is closed and re-opened when exception is raised from response context.

Steps to reproduce

Run sample script:

#!/usr/bin/env python3
import asyncio
import psutil
from aiohttp import web, ClientSession, TCPConnector
import ssl
from pympler.tracker import SummaryTracker

PORT = 8888

url = 'https://madis-data.ncep.noaa.gov/madisPublic1/data/archive/2001/07/01/LDAD/mesonet/netCDF/20010701_0800.gz'
# url = 'https://localhost:{}/'.format(PORT)
#url = 'https://localhost/20010701_0800.gz'

async def hello(request):
    return web.Response(status=304, content_type='application/octet-stream')


async def track_mem():
    tracker = SummaryTracker()
    proc = psutil.Process()
    while True:
        print("Number of open files: {} conns: {}".format(proc.num_fds(), len(proc.connections())))
        # tracker.print_diff()
        await asyncio.sleep(5)


async def server_startup(app):
    ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
    ssl_context.set_default_verify_paths()
    ssl_context.load_cert_chain('/tmp/cert', '/tmp/key')

    connector = TCPConnector(limit=1, conn_timeout=10, ssl_context=ssl_context)
    session = ClientSession(connector=connector)

    # start 10 workers
    for i in range(10):
        asyncio.ensure_future(worker(session))

    # asyncio.ensure_future(track_mem())


async def worker(session: ClientSession):
    loop = asyncio.get_event_loop()
    loop.set_debug(True)

    while True:
        try:
            headers = {'If-Modified-Since': 'Sun, 01 Jul 2001 10:24:33 GMT'}
            async with session.get(url, headers=headers) as response:
                print('.', end='', flush=True)
                raise Exception
        except:
            pass


def main():
    app = web.Application()
    app.router.add_get('/', hello)
    app.on_startup.append(server_startup)

    ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
    ssl_context.set_default_verify_paths()
    ssl_context.load_cert_chain('/tmp/cert', '/tmp/key')

    web.run_app(app, port=PORT, ssl_context=ssl_context)


if __name__ == '__main__':
    main()

keep track of open files:

watch -n0.5 "lsof | wc -l"

and note how the number of handles grows very quickly. It should remain stable. I was unable to reproduce this with the sample server included so it must be some kind of special behavior of the server this testcase hits.

Your environment

OSX and linux, python3.5.3 with latest aiohttp

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions