1

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.

Hedge
  • 13
  • 2
  • "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." Could you explain this, please? – Paul Cornelius Dec 28 '22 at 02:48
  • For e.g., if `get_status` is called repeatedly, then the `asyncio.run(sio.emit('status', repr(cb)))` "queue" of tasks gets cleared up much quicker. As if it knows that new tasks are coming and it needs to clear up the "queue". One can imagine a list where, at the arrival of a new item, the top item will get popped, or in this case, the message will get _emitted_. I can't imagine the actual implementation specifically wait for items to then execute old items. Note, however, after all the popping, that the final task would still stay behind and would again be delayed by some few seconds. – Hedge Dec 28 '22 at 06:42

1 Answers1

0

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.

Miguel Grinberg
  • 65,299
  • 14
  • 133
  • 152
  • Your `loop` variable is `None`. Did you read my notes regarding how to set the loop variable? – Miguel Grinberg Dec 30 '22 at 20:10
  • Pardon my reply that was made without sufficient testing/research (hence I deleted it). Your suggestion works great. On another note, I actually rewrote everything in plain `WebSocket` and calling `asyncio.run(_websocket.send_str(repr(cb)))` works swiftly with no delay. I do wonder what I might be missing though? – Hedge Dec 31 '22 at 07:15
  • I cannot comment on code you are not showing. Stacking asyncio.run calls is a terrible idea, even if it sometimes works so I would advise you against it. – Miguel Grinberg Dec 31 '22 at 19:36