[Python-Dev] Asynchronous context manager in a typical network server

Guido van Rossum guido at python.org
Fri Dec 18 11:59:09 EST 2015


I agree with Andrew that there are too many different scenarios and
requirements to make this a useful library function. Some notes on the
actual code you posted:

- Instead of calling signal.signal() yourself, you should use
loop.add_signal_handler(). It makes sure your signal handler doesn't run
while another handler is already running.

- If you add a handler for SIGINT you can control what happens when the
user hits ^C (again, ensuring the handler already running isn't interrupted
halfway through).

- I'm unclear on why you want a wait_forever() instead of using
loop.run_forever(). Can you clarify?

- In theory, instead of waiting for a Future that is cancelled by a
handler, you should be able to use asyncio.sleep() with a very large number
(e.g. a million seconds). Your handler could then just call loop.stop().
However, I just tested this and it raises "RuntimeError: Event loop stopped
before Future completed." so ignore this until we've fixed it. :-)

On Fri, Dec 18, 2015 at 5:58 AM, Szieberth Ádám <sziebadam at gmail.com> wrote:

> Hi Developers!
>
> This is my first post. Please excuse me my poor English. If anyone is
> interested, I wrote a small introduction on my homepage. Link is at the
> bottom.
>
> This post is about how to effectively implement the new asynchronous
> context
> manager in a typical network server.
>
> I would appreciate and welcome any confirmation or critics whether my
> thinking
> is right or wrong. Thanks in advance!
>
> So, a typical server main code I used to see around is like this:
>
>     srv = loop.run_until_complete(create_server(handler, host, port))
>     try:
>         loop.run_forever()
>     except KeyboardInterrupt:
>         pass
>     finally:
>         # other tear down code may be here
>         srv.close()
>         loop.run_until_complete(srv.wait_closed())
>     loop.close()
>
> Note that `create_server()` here is not necessary
> `BaseEventLoop.create_server()`.
>
> The above code is not prepared to handle `OSError`s or any other
> `Exception`s
> (including a `KeyboardInterrupt` by a rapid Ctr+C) when setting up the
> server,
> it just prints the traceback to the console which is not user friendly.
> Moreover, I would expect from a server to handle the SIGTERM signal as well
> and tell its clients that it stops serving when not force killed.
>
> How the main code should create server, maintain the serving, deal with
> errors
> and close properly both the connections and the event loop when exiting
> without letting pending tasks around is not trivial. There are many
> questions
> on SO and other places of the internet regarding of this problem.
>
> My idea was to provide a simple code which is robust in terms of these
> concerns by profiting from the new asynchronous context manager pattern.
>
> The code of the magic methods of a typical awaitable `CreateServer` object
> seems rather trivial:
>
>     async def __aenter__(self):
>         self.server = await self
>         return self.server
>
>     async def __aexit__(self, exc_type, exc_value, traceback):
>         # other tear down code may be here
>         self.server.close()
>         await self.server.wait_closed()
>
> However, to make it work, a task has to be created:
>
>     async def server_task():
>         async with CreateServer(handler, host, port) as srv:
>             await asyncio.Future()  # wait forever
>
> I write some remarks regarding the above code to the end of this post. Note
> that `srv` is unreachable from outside which could be a problem in some
> cases.
> What is unavoidable: this task has to get cancelled explicitely by the main
> code which should look like this:
>
>     srvtsk = loop.create_task(server_task())
>
>     signal.signal(signal.SIGTERM, lambda si, fr:
> loop.call_soon(srvtsk.cancel))
>
>     while True:
>         try:
>             loop.run_until_complete(srvtsk)
>         except KeyboardInterrupt:
>             srvtsk.cancel()
>         except asyncio.CancelledError:
>             break
>         except Exception as err:
>             print(err)
>             break
>     loop.close()
>
> Note that when `CancelledError` gets raised, the tear down process is
> already
> done.
>
> Remarks:
>
> * It would be nice to have an `asyncio.wait_forever()` coroutine for dummy
>   context bodies.
> * Moreover, I also imagined an
> `BaseEventLoop.create_context_task(awithable,
>   body_coro_func=None)` method. The `body_coro_func` should default to
>   `asyncio.wait_forever()`, otherwise it should get whatever is returned by
>   `__aenter__` as a single argument. The returned Task object should also
>   provide a reference to that object.
>
> Best regards,
> Ádám
>
> (http://szieberthadam.github.io/)
> _______________________________________________
> Python-Dev mailing list
> Python-Dev at python.org
> https://mail.python.org/mailman/listinfo/python-dev
> Unsubscribe:
> https://mail.python.org/mailman/options/python-dev/guido%40python.org
>



-- 
--Guido van Rossum (python.org/~guido)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20151218/104964e6/attachment.html>


More information about the Python-Dev mailing list