Package: graingert-aiorun
🏃 aiorunHere’s the big idea (how you use it):importasynciofromaiorunimportrunasyncdefmain():# Put your application code hereawaitasyncio.sleep(1.0)if__name__=='__main__':run(main())This package provides arun()function as the starting point
of yourasyncio-based application. Therun()function will
run forever. If you want to shut down whenmain()completes, just
callloop.stop()inside it: that will initiate shutdown.🤔 Why?Therun()function will handleeverythingthat 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 doesrun()do exactly?? It does these standard,
idiomatic actions for asyncio apps:creates aTaskfor the given coroutine (schedules it on the
event loop),callsloop.run_forever(),adds default (and smart) signal handlers for bothSIGINTandSIGTERMthat will stop the loop;andwhenthe loop stops (either by signal or called directly), then it will……gather all outstanding tasks,cancel them usingtask.cancel(),resume running the loop until all those tasks are done,wait for theexecutorto complete shutdown, andfinally close the loop.All of this stuff is boilerplate that you will never have to write
again. So, if you useaiorunthis is whatyouneed to remember:Spawn all your work from a single, starting coroutineWhen a shutdown signal is received,allcurrently-pending tasks
will haveCancelledErrorraised internally. It’s up to you whether
you want to handle this inside each coroutine with
atry/exceptor not.If you want to protect coros from cancellation, seeshutdown_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 setdaemon=Trueinstead.There’s not much else to know for general use.aiorunhas 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
severalrun_until_complete()phases before the primaryrun_forever()which is the “main” running part of the program. How do we handle that withaiorun?Let’s recreate theecho client & serverexamples from the Standard Library documentation:Client:# echo_client.pyimportasynciofromaiorunimportrunasyncdeftcp_echo_client(message):# Same as original!reader,writer=awaitasyncio.open_connection('127.0.0.1',8888)print('Send:%r'%message)writer.write(message.encode())data=awaitreader.read(100)print('Received:%r'%data.decode())print('Close the socket')writer.close()asyncio.get_event_loop().stop()# Exit after one msg like originalmessage='Hello World!'run(tcp_echo_client(message))Server:importasynciofromaiorunimportrunasyncdefhandle_echo(reader,writer):# Same as original!data=awaitreader.read(100)message=data.decode()addr=writer.get_extra_info('peername')print("Received%rfrom%r"%(message,addr))print("Send:%r"%message)writer.write(data)awaitwriter.drain()print("Close the client socket")writer.close()asyncdefmain():server=awaitasyncio.start_server(handle_echo,'127.0.0.1',8888)print('Serving on{}'.format(server.sockets[0].getsockname()))try:# Wait for cancellationwhileTrue:awaitasyncio.sleep(10)exceptasyncio.CancelledError:server.close()awaitserver.wait_closed()run(main())It works the same as the original examples, except you see this
when you hitCTRL-Con the server instance:$pythonecho_server.pyRunningforever.Servingon('127.0.0.1',8888)Received'Hello World!'from('127.0.0.1',57198)Send:'Hello World!'Closetheclientsocket^CStoppingtheloopEnteringshutdownphase.Cancellingpendingtasks.Cancellingtask:<Taskpendingcoro=[...snip...]>RunningpendingtaskstillcompleteWaitingforexecutorshutdown.Leaving.Bye!Task gathering, cancellation, and executor shutdown all happen
automatically.💨 Do you likeuvloop?importasyncio,aiorunasyncdefmain():<snip>if__name__=='__main__':run(main(),use_uvloop=True)Note that you have topip install uvloopyourself.🛡️ Smart shield for shutdownIt’s unusual, but sometimes you’re going to want a coroutine to not get
interrupted by cancellationduring the shutdown sequence. You’ll look in
the official docs and findasyncio.shield().Unfortunately,shield()doesn’t work in shutdown scenarios because
the protection offered byshield()only applies if the specific coroutineinside whichtheshield()is used, gets cancelled directly.Let me explain: if you do a conventional shutdown sequence (likeaiorunis doing internally), this is the sequence of steps:tasks = all_tasks(), followed bygroup =gather(*tasks), and thengroup.cancel()The wayshield()works internally is it creates asecret, innertask—which also gets included in theall_tasks()call above! Thus
it also receives a cancellation signal just like everything else.Therefore, we have an alternative version ofshield()that works better for
us:shutdown_waits_for(). If you’ve got a coroutine that mustnotbe
cancelled during the shutdown sequence, just wrap it inshutdown_waits_for()!Here’s an example:importasynciofromaiorunimportrun,shutdown_waits_forasyncdefcorofn():awaitasyncio.sleep(60)print('done!')asyncdefmain():try:awaitshutdown_waits_for(corofn())exceptasyncio.CancelledErrorprint('oh noes!')run(main())If you hitCTRL-Cbefore60 seconds has passed, you will seeoh 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 byCTRL-C,exceptones wrapped inshutdown_waits_for()calls.  In this respect, it
is loosely similar toasyncio.shield(), but with special applicability
to our shutdown scenario inaiorun().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 useshutdown_waits_for()as if it wereasyncio.shield()too. For that use-case it works the same.  If you’re usingaiorun, there
is no reason to useshield().