From 3763baa912988ef7f233bbf0587442e4eda767ea Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 19:24:26 +0000 Subject: [PATCH 1/2] perf: drop task wrapper in EventNamespace.emit_update, keep flush tick Awaiting a freshly created task blocks the caller exactly like awaiting the coroutine directly, so the wrapper never provided the claimed non-blocking behavior -- it only added task creation/scheduling overhead (~4-7us, 2-3x) on every outgoing state update. The task wrapper did have one load-bearing side effect: it forced an event loop tick, giving the engineio writer task a chance to flush the queued packet before the caller resumes (important when a sync event handler blocks the loop right after yielding an interim update). Keep that guarantee with an explicit asyncio.sleep(0) after the emit, which is still ~2x cheaper than the task wrapper. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Sns7EuBPYvfGCxRSRZ1bUx --- reflex/app.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index 9364c547436..ed08532a77e 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -1712,11 +1712,15 @@ async def emit_update(self, update: StateUpdate, token: str) -> None: f"Attempting to send delta to disconnected client {token!r}" ) return - # Creating a task prevents the update from being blocked behind other coroutines. - await asyncio.create_task( - self.emit(str(constants.SocketEvent.EVENT), update, to=socket_record.sid), - name=f"reflex_emit_event|{token}|{socket_record.sid}|{time.time()}", - ) + # Await the emit directly: wrapping it in a task does not unblock the + # caller (awaiting the task blocks just the same) and only adds task + # creation/scheduling overhead on every update. + await self.emit(str(constants.SocketEvent.EVENT), update, to=socket_record.sid) + # The emit may complete without suspending (the packet is queued, not + # sent). Yield one loop tick so the websocket writer can flush the + # packet before the caller potentially blocks the event loop (e.g. a + # sync event handler resuming after a yield). + await asyncio.sleep(0) async def on_event(self, sid: str, data: Any): """Event for receiving front-end websocket events. From 846a0ee7a26baca45acbbbedba89f63402de3759 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 19:35:27 +0000 Subject: [PATCH 2/2] chore: add news fragment for #6734 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Sns7EuBPYvfGCxRSRZ1bUx --- news/6734.performance.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/6734.performance.md diff --git a/news/6734.performance.md b/news/6734.performance.md new file mode 100644 index 00000000000..671175ce461 --- /dev/null +++ b/news/6734.performance.md @@ -0,0 +1 @@ +Remove the per-update `asyncio.create_task` wrapper in `EventNamespace.emit_update`, cutting scheduling overhead roughly in half for every outgoing state update while keeping the event-loop tick that flushes interim updates before a sync handler resumes.