Skip to main content
https://travis-ci.org/cjrh/aiorun.svg?branch=master https://coveralls.io/repos/github/cjrh/aiorun/badge.svg?branch=master https://img.shields.io/pypi/pyversions/aiorun.svg https://img.shields.io/github/tag/cjrh/aiorun.svg https://img.shields.io/badge/install-pip%20install%20aiorun-ff69b4.svg https://img.shields.io/pypi/v/aiorun.svg https://img.shields.io/badge/calver-YYYY.MM.MINOR-22bfda.svg

🏃 aiorun

Here’s the big idea (how you use it):

import asyncio
from aiorun import run

async def main():
    # Put your application code here
    await asyncio.sleep(1.0)

if __name__ == '__main__':
    run(main())

This package provides a run() function as the starting point of your asyncio-based application. The run() function will run forever. If you want to shut down when main() completes, just call loop.stop() inside it: that will initiate shutdown.

🤔 Why?

The run() function will handle everything that normally needs to be done during the shutdown sequence of the application. All you need to do is write your coroutines and run them.

So what the heck does run() do exactly?? It does these standard, idiomatic actions for asyncio apps:

  • creates a Task for the given coroutine (schedules it on the event loop),

  • calls loop.run_forever(),

  • adds default (and smart) signal handlers for both SIGINT and SIGTERM that will stop the loop;

  • and when the loop stops (either by signal or called directly), then it will…

  • …gather all outstanding tasks,

  • cancel them using task.cancel(),

  • resume running the loop until all those tasks are done,

  • wait for the executor to complete shutdown, and

  • finally close the loop.

All of this stuff is boilerplate that you will never have to write again. So, if you use aiorun this is what you need to remember:

  • Spawn all your work from a single, starting coroutine

  • When a shutdown signal is received, all currently-pending tasks will have CancelledError raised internally. It’s up to you whether you want to handle this inside each coroutine with a try/except or not.

  • If you want to protect coros from cancellation, see shutdown_waits_for() further down.

  • Try to have executor jobs be shortish, since the shutdown process will wait for them to finish. If you need a long-running thread or process tasks, use a dedicated thread/subprocess and set daemon=True instead.

There’s not much else to know for general use. aiorun has a few special tools that you might need in unusual circumstances. These are discussed next.

🖥️ What about TCP server startup?

You will see in many examples online that for servers, startup happens in several run_until_complete() phases before the primary run_forever() which is the “main” running part of the program. How do we handle that with aiorun?

Let’s recreate the echo client & server examples from the Standard Library documentation:

Client:

# echo_client.py
import asyncio
from aiorun import run

async def tcp_echo_client(message):
    # Same as original!
    reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
    print('Send: %r' % message)
    writer.write(message.encode())
    data = await reader.read(100)
    print('Received: %r' % data.decode())
    print('Close the socket')
    writer.close()
    asyncio.get_event_loop().stop()  # Exit after one msg like original

message = 'Hello World!'
run(tcp_echo_client(message))

Server:

import asyncio
from aiorun import run

async def handle_echo(reader, writer):
    # Same as original!
    data = await reader.read(100)
    message = data.decode()
    addr = writer.get_extra_info('peername')
    print("Received %r from %r" % (message, addr))
    print("Send: %r" % message)
    writer.write(data)
    await writer.drain()
    print("Close the client socket")
    writer.close()

async def main():
    server = await asyncio.start_server(handle_echo, '127.0.0.1', 8888)
    print('Serving on {}'.format(server.sockets[0].getsockname()))
    try:
        # Wait for cancellation
        while True:
            await asyncio.sleep(10)
    except asyncio.CancelledError:
        server.close()
        await server.wait_closed()

run(main())

It works the same as the original examples, except you see this when you hit CTRL-C on the server instance:

$ python echo_server.py
Running forever.
Serving on ('127.0.0.1', 8888)
Received 'Hello World!' from ('127.0.0.1', 57198)
Send: 'Hello World!'
Close the client socket
^CStopping the loop
Entering shutdown phase.
Cancelling pending tasks.
Cancelling task:  <Task pending coro=[...snip...]>
Running pending tasks till complete
Waiting for executor shutdown.
Leaving. Bye!

Task gathering, cancellation, and executor shutdown all happen automatically.

💨 Do you like uvloop?

import asyncio, aiorun

async def main():
    <snip>

if __name__ == '__main__':
    run(main(), use_uvloop=True)

Note that you have to pip install uvloop yourself.

🛡️ Smart shield for shutdown

It’s unusual, but sometimes you’re going to want a coroutine to not get interrupted by cancellation during the shutdown sequence. You’ll look in the official docs and find asyncio.shield().

Unfortunately, shield() doesn’t work in shutdown scenarios because the protection offered by shield() only applies if the specific coroutine inside which the shield() is used, gets cancelled directly.

Let me explain: if you do a conventional shutdown sequence (like aiorun is doing internally), this is the sequence of steps:

  • tasks = all_tasks(), followed by

  • group = gather(*tasks), and then

  • group.cancel()

The way shield() works internally is it creates a secret, inner task—which also gets included in the all_tasks() call above! Thus it also receives a cancellation signal just like everything else.

Therefore, we have an alternative version of shield() that works better for us: shutdown_waits_for(). If you’ve got a coroutine that must not be cancelled during the shutdown sequence, just wrap it in shutdown_waits_for()!

Here’s an example:

import asyncio
from aiorun import run, shutdown_waits_for

async def corofn():
    await asyncio.sleep(60)
    print('done!')

async def main():
    try:
        await shutdown_waits_for(corofn())
    except asyncio.CancelledError
        print('oh noes!')

run(main())

If you hit CTRL-C before 60 seconds has passed, you will see oh noes! printed immediately, and then after 60 seconds (since start), done! is printed, and thereafter the program exits.

Behind the scenes, all_tasks() would have been cancelled by CTRL-C, except ones wrapped in shutdown_waits_for() calls. In this respect, it is loosely similar to asyncio.shield(), but with special applicability to our shutdown scenario in aiorun().

Be careful with this: the coroutine should still finish up at some point. The main use case for this is short-lived tasks that you don’t want to write explicit cancellation handling.

Oh, and you can use shutdown_waits_for() as if it were asyncio.shield() too. For that use-case it works the same. If you’re using aiorun, there is no reason to use shield().

Release files for graingert-aiorun 2018.9.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for graingert-aiorun 2018.9.1
File Size Uploaded
graingert-aiorun-2018.9.1.tar.gz 14.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for graingert-aiorun 2018.9.1
File Interpreter ABI Platform
graingert_aiorun-2018.9.1-py2.py3-none-any.whl Python 2, Python 3 none any Details

Total release size: 39.5 kB

Release files / graingert-aiorun-2018.9.1.tar.gz

Download URL graingert-aiorun-2018.9.1.tar.gz
Size 14.4 kB
Tags Source
SHA-256 checksum
How to use checksums
4859e4b13807af55fb9c467d523e949c54379cd848f9702d95438500982b5f67
BLAKE2b-256 checksum
How to use checksums
b2667e05c65f246e7adfd6bec3215e969a79d30836b5765208cf4d32ca897c91
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via python-requests/2.21.0

Release files / graingert_aiorun-2018.9.1-py2.py3-none-any.whl

Download URL graingert_aiorun-2018.9.1-py2.py3-none-any.whl
Size 25.1 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
e451a9f1f0976ff59d90a30830a70a85a104e70577137daa182bd78e6888c049
BLAKE2b-256 checksum
How to use checksums
eb40e15e0c6ab4f9506e87d26548e5cac05b68ea1da8545cdc95cde553746f79
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via python-requests/2.21.0

Release history Release notifications | RSS feed

This release

2018.9.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page