how to stop() the last loop among multiple nested asyncio loops?

2k views Asked by At

I need at times more than one asyncio coroutine, where the routines then would be nested : coroutine B running in coroutine A, C in B and so on. The problem is with stopping a given loop. For example, using loop.stop() in the last top loop such as loop 'C' kills actually all asyncio coroutines - and not just this loop 'C'. I suspect that stop() actually kills coroutine A, and by doing so it annihilates all other dependent routines. The nested routines are call_soon_threadsafe, and all routines begin as run_forever.

I tried using specific loop names, or 'return', or 'break' (in the while loop inside the coroutine) but nothing exits the loop - except stop() which then kills non-specifically all loops at once.

My problem I described here is actually related to an earlier question of mine... python daemon server crashes during HTML popup overlay callback using asyncio websocket coroutines ...which I thought I had solved - till running into this loop.stop() problem.

below now my example code for Python 3.4.3 where I try to stop() the coroutine_overlay_websocket_server loop as soon as it is done with the websocket job. As said, my code at the current state breaks all running loops. Thereafter the fmDaemon recreates a new asyncio loop that knows nothing of what was computed before :

import webbrowser
import websockets
import asyncio

class fmDaemon( Daemon):

    # Daemon - see : http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
    # Daemon - see : http://www.jejik.com/files/examples/daemon3x.py

    def __init__( self, me):
        self.me = me

    def run( self):

        while True:

            @asyncio.coroutine
            def coroutine_daemon_websocket_server( websocket, path):

                msg = yield from websocket.recv()

                if msg != None:
                    msg_out = "{}".format( msg)
                    yield from websocket.send( msg_out)
                    self.me = Function1( self.me, msg)

            loop = asyncio.get_event_loop()
            loop.run_until_complete( websockets.serve( coroutine_daemon_websocket_server, self.me.IP, self.me.PORT))
            loop.run_forever()


def Function1( me, msg):

    # doing some stuff :
    # creating HTML file,
    # loading HTML webpage with a webbrowser call,
    # awaiting HTML button press signal via websocket protocol :

    @asyncio.coroutine
    def coroutine_overlay_websocket_server( websocket, path):

        while True:
            msg = yield from websocket.recv()
            msg_out = "{}".format( msg)
            yield from websocket.send( msg_out)

            if msg == 'my_expected_string':
                me.flags['myStr'] = msg
                break

        loop.call_soon_threadsafe( loop.stop)

    loop = asyncio.get_event_loop()
    loop.call_soon_threadsafe( asyncio.async, websockets.serve( coroutine_overlay_websocket_server, me.IP, me.PORT_overlay))
    loop.run_forever()
    loop.call_soon_threadsafe( loop.close)

    # program should continue here...

My two questions : 1) Is there a way to exit a given coroutine without killing a coroutine lower down? 2) Or, alternatively, do you know of a method for reading websocket calls that does not make use of asyncio ?

1

There are 1 answers

1
dano On

I'm still a little confused by what you're trying to do, but there's definitely no need to try to nest event loops - your program is single-threaded, so when you call asyncio.get_event_loop() multiple times, you're always going to get the same event loop back. So you're really not creating two different loops in your example; both fmDaemon.run and Function1 use the same one. That's why stopping loop inside Function1 also kills the coroutine you launched inside run.

That said, there's no reason to try to create two different event loops to begin with. Function1 is being called from a coroutine, and wants to call other coroutines, so why not make it a coroutine, too? Then you can just call yield from websockets.serve(...) directly, and use an asyncio.Event to wait for coroutine_overlay_websocket_server to complete:

import webbrowser
import websockets
import asyncio

class fmDaemon( Daemon):
    def __init__( self, me):
        self.me = me

    def run( self):
        @asyncio.coroutine
        def coroutine_daemon_websocket_server(websocket, path):
            msg = yield from websocket.recv()

            if msg != None:
                msg_out = "{}".format( msg)
                yield from websocket.send( msg_out)
                self.me = Function1( self.me, msg)

        loop = asyncio.get_event_loop()
        loop.run_until_complete(websockets.serve(coroutine_daemon_websocket_server, 
                                                 self.me.IP, 
                                                 self.me.PORT))
        loop.run_forever()


@asyncio.coroutine
def Function1(me, msg):
    @asyncio.coroutine
    def coroutine_overlay_websocket_server(websocket, path):

        while True:
            msg = yield from websocket.recv()
            msg_out = "{}".format( msg)
            yield from websocket.send( msg_out)

            if msg == 'my_expected_string':
                me.flags['myStr'] = msg
                break

        event.set() # Tell the outer function it can exit.

    event = asyncio.Event()
    yield from websockets.serve(coroutine_overlay_websocket_server, me.IP, me.PORT_overlay))
    yield from event.wait() # This will block until event.set() is called.