Search code examples
pythonwebsocketsocket.iopython-asynciopython-socketio

Socketio Server randomly delayed emit


A server utilizing python-socketio socketio.AsyncServer to handle very simple IO operations. Everything works:

  1. Client sends message to check status
  2. Server returns status

Problem asyncio.run(sio.emit('status', repr(cb))), does not seem to reach the client immediately, but in a randomly delayed fashion (varying between 0.5 to 5 seconds). The strange thing is a new asyncio.run(...) would cause the previous "task" to execute. In this case, stacking multiple asyncio.run(sio.emit(...)) would cause all the tasks to get executed, except for the very last one.

Below is a code snippet on the server side:

def callback_status(cb):
    print("Returning status: ", repr(cb)) #this gets executed just fine but the following is randomly delayed
    asyncio.run(sio.emit('status', repr(cb)))

@sio.on('message')
async def get_status(sid, message):
    get_some_status(callback_status) #not an async function

sio = socketio.AsyncServer()
app = web.Application()
sio.attach(app)

if __name__ == '__main__':
    web.run_app(app, host='0.0.0.0')
    

I have verified that this is not something concerning the client side as I have used another method to setup a server and the 'status' messages, once emitted, get received on the client side immediately. The reason why I am using socketio.AsyncServer is such that the client may utilize a WebSocket connection (as opposed to http connection) for persistent, lower-overhead bi-directional communication.


Solution

  • You can't stack asyncio.run() calls, that does not work, unfortunatelly.

    What might work, is to have your sync callback function schedule the async emit to happen later. Something like this:

    def callback_status(cb):
        print("Returning status: ", repr(cb))
        asyncio.run_coroutine_threadsafe(sio.emit('status', repr(cb)), loop)
    

    The only thing that you need to add to this is to set loop to the correct asyncio loop ahead of time. You can obtain the loop by calling asyncio.get_event_loop() from the asyncio thread. This expression will likely not work if used in the callback_status() function because my assumption is that your callback is invoked in the context of another thread, not the one running the async application.