5

I am using Python websockets 4.0.1 on Ubuntu. I want to have 2 websocket servers running. I was able to get this to "kind of work" by creating 2 threads and independent event loops for each one. By "kind of work", I mean both websockets work and are responsive for about 30 seconds and then one of them stops. I have to restart the process to get them both to work again. If I only run one or the other of these 2 threads, the single websocket works forever.

What am I doing wrong and how can I have 2 websockets work forever with asyncio?

# Start VL WebSocket Task
class vlWebSocketTask (threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        # Main while loops
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        while True:
            try:
                print("Starting VL WebSocket Server...")
                startVLServer = websockets.serve(vlWebsocketServer, '192.168.1.3', 8777)
                asyncio.get_event_loop().run_until_complete(startVLServer)  
                asyncio.get_event_loop().run_forever()
            except Exception as ex:
                print(ex)
            time.sleep(5)

# Start IR WebSocket Task
class irWebSocketTask (threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        while True:
            try:
                print("Starting IR WebSocket Server...")
                startIRServer = websockets.serve(irWebsocketServer, '192.168.1.3', 8555)
                asyncio.get_event_loop().run_until_complete(startIRServer)  
                asyncio.get_event_loop().run_forever()
            except Exception as ex:
                print(ex)
            time.sleep(5)
        
# Initialize VL WebSocket Task
#VLWebSocketTask = vlWebSocketTask()
#VLWebSocketTask.start()
        
# Initialize IR WebSocket Task
IRWebSocketTask = irWebSocketTask()
IRWebSocketTask.start()

1 Answer 1

10

You don't need threads to run multiple asyncio tasks - allowing multiple agents to share the same event loop is the strong suit of asyncio. You should be able to replace both thread-based classes with code like this:

loop = asyncio.new_event_loop()
loop.run_until_complete(websockets.serve(vlWebsocketServer, '192.168.1.3', 8777))
loop.run_until_complete(websockets.serve(irWebsocketServer, '192.168.1.3', 8555))
loop.run_forever()

While it is not exactly wrong to mix threads and asyncio, doing so correctly requires care not to mix up the separate asyncio instances. The safe way to use threads for asyncio is with loop.run_in_executor(), which runs synchronous code in a separate thread without blocking the event loop, while returning an object awaitable from the loop.

Note: the above code was written prior to the advent of asyncio.run() and manually spins the event loop. In Python 3.7 and later one would probably write something like:

async def main():
    server1 = await websockets.serve(vlWebsocketServer, '192.168.1.3', 8777)
    server2 = await websockets.serve(irWebsocketServer, '192.168.1.3', 8555)
    await asyncio.gather(server1.wait_closed(), server2.wait_closed())

asyncio.run(main())
Sign up to request clarification or add additional context in comments.

6 Comments

Have you tried running your own code? This would yield something like TypeError: a coroutine was expected, got <websockets.server.Serve object at 0x7baca53e3a90>.
@AndyPan I don't remember if I tried this, but it's entirely possible that I didn't. It's also possible that serve was defined as an async def two years ago when this answer was written. Either way, loop.create_task can trivially be changed to asyncio.ensure_future with the desired effect. If you can confirm that that works, I'll change the answer accordingly.
First, apparently you should await on the returned object. This will give you the server object. Then use something like asyncio.create_task(server.wait_closed()).
@AndyPan Thanks, I've now amended the answer. Note that wait_closed() is a convenient await target when you run inside asyncio.run(), but passing wait_closed() to create_task() is unnecessary because once server() completes, the server runs regardless of whether you call wait_closed(). The pre-asyncio.run code in this answer uses run_forever() to keep the loop running and therefore doesn't need wait_closed(). In the edit I've included the modern way of accomplishing the same goal which does use wait_closed() as you'd expect.
Apologize for my arrogance. The return object of websockets.serve has not changed, but my understanding of how it works is inaccurate as well. As I cannot find a solution throughout the official docs, thanks for improving the answer.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.