Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ProcessPoolExecutor in background cause freeze after keep-alive timeout #1883

Closed
voronin-opensoft opened this issue May 11, 2017 · 14 comments
Closed

Comments

@voronin-opensoft
Copy link

voronin-opensoft commented May 11, 2017

Long story short

If we run heavy task in ProcessPoolExecutor in background, aiohttp don't handle request after some timeout. It happens after ~75 seconds. It's equal keepalive_timeout value at https://github.com/aio-libs/aiohttp/blob/2.0.7/aiohttp/web_protocol.py#L82
If we change keepalive_timeout value we get freeze after specific time.

Expected behaviour

Handle all requests.

Actual behaviour

Freeze on request after keep-alive timeout.

Steps to reproduce

We set keep-alive timeout to 1 second for faster bug reproducing.

  1. Run server
import asyncio
import logging
from multiprocessing import Manager
from concurrent.futures import ProcessPoolExecutor

from aiohttp.web import Application, Response, run_app


logging.basicConfig(level=logging.DEBUG)


async def index(request):
    body = '''<!DOCTYPE html>
    <html>
    <body>
    <script>
        function request(path) {
            var xhr = new XMLHttpRequest();
            
            xhr.open('GET', path, false);
            xhr.onload = function (e) {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    console.log(xhr.responseText);
                }
            }
            xhr.send(null);
        }
        
        function start() {
            return request('/start');
        }
        
        function stop() {
            return request('/stop');
        }
    </script>
    <button style="width: 200px; height: 100px;" onclick="start()"> Start </button>
    <button style="width: 200px; height: 100px;" onclick="stop()"> Stop </button>
    </body>
    </html>
    '''
    return Response(body=body, content_type='document')


async def start(request):
    await request.app['worker'].queue.put('data')
    return Response(body='Start')


async def stop(request):
    request.app['worker'].stop_event.set()
    return Response(body='Stop')


def hard_work(stop_event):
    while True:
        if stop_event.is_set():
            break


class Worker:

    async def start(self, loop):
        self.queue = asyncio.Queue(loop=loop)

        while True:
            data = await self.queue.get()

            executor = ProcessPoolExecutor()
            manager = Manager()
            stop_event = manager.Event()
            self.stop_event = stop_event

            hard_future = loop.run_in_executor(executor, hard_work, stop_event)

            try:
                await asyncio.wait_for(hard_future, timeout=999999, loop=loop)
            except asyncio.TimeoutError:
                print('Time out')
                stop_event.set()
            else:
                print('Done')


def get_app():
    app = Application(handler_args={'keepalive_timeout': 1})

    app.router.add_get('/', index)
    app.router.add_get('/start', start)
    app.router.add_get('/stop', stop)


    async def start_worker(app):
        app.loop.set_debug(True)

        worker = Worker()
        app['worker'] = worker
        app.loop.create_task(worker.start(app.loop))

    app.on_startup.append(start_worker)

    return app


def main():
    app = get_app()
    run_app(app, host='0.0.0.0', port=5000)


if __name__ == '__main__':
    main()
  1. Make first /start request. Wait for keep-alive timeout. Make second /stop request. aiohttp will not handle second /stop request.
import time
import asyncio

import aiohttp


async def do_requests():
    session = aiohttp.ClientSession()

    print('GET /start')
    response = await session.get('http://127.0.0.1:5000/start')
    print('Response:', await response.text())

    wait = 3
    print('Sleep in {} seconds'.format(wait))
    await asyncio.sleep(wait)

    print('GET /stop')
    response = await session.get('http://127.0.0.1:5000/stop')
    print('Response:', await response.text())


def main():
    loop = asyncio.get_event_loop()
    loop.run_until_complete(do_requests())


if __name__ == '__main__':
    main()

Your environment

Debian 8 Jessie
Python 3.5.3
aiohttp == 2.0.7

@fafhrd91
Copy link
Member

I do not think this is aiohttp problem.

here is what is going on:

  1. requests opens connection and make /start request
  2. connection remains open
  3. aiohttp drops connection after 75 seconds
  4. requests could not detect connection dropped and use it to send data. aiohttp does not get any requests

to confirm, you can make /stop request with curl after 80 seconds
or you can force close connection on aiohttp side.

@fafhrd91
Copy link
Member

try aiohttp.client

@voronin-opensoft
Copy link
Author

This works with curl cause it closes connections. Same bug can be reproduced with Chrome and Firefox cause they use keep-alive. We can set small keep-alive timeout for easier bug reproducing.
If we use app = Application(handler_args={'keepalive_timeout': None}) there is no bug.

@voronin-opensoft
Copy link
Author

Yes. We can reproduce it using aiohttp.ClientSession()

@fafhrd91
Copy link
Member

I see. It seems related how tcp works in general.

@fafhrd91
Copy link
Member

fix in #1884
I am not sure about correctness

@voronin-opensoft
Copy link
Author

It works in Chrome, Firefox, requests but not in aiohttp.client

@fafhrd91
Copy link
Member

this seems bug in asyncio default event loop. works with uvloop.

@1st1 ^^^

@fafhrd91
Copy link
Member

async-tokio loop does not work as well. seems special treatment is needed for HUP event

@1st1
Copy link
Contributor

1st1 commented May 12, 2017

this seems bug in asyncio default event loop

would be great if someone could submit a bug report on bugs.python.org.

@fafhrd91
Copy link
Member

will do, but I want to play with implementation first

@asvetlov
Copy link
Member

@fafhrd91 could you provide more details?

@fafhrd91
Copy link
Member

If I rember right, this is problem of the default event loop, it does not react on socket hup event after timeout or something like that. So aiohttp can not close socket until it tries to write to it something. But uvloop works right. This is reproducible.

@asvetlov
Copy link
Member

The issue is stale.
Sorry, closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants