From ca724ff57f9b94a9867a483b9add0fbb6b2ad95c Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Sun, 19 Jul 2026 02:05:35 +0200 Subject: [PATCH 1/2] wip --- .../event/processor/event_processor.py | 5 +-- .../event/processor/test_event_processor.py | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/reflex-base/src/reflex_base/event/processor/event_processor.py b/packages/reflex-base/src/reflex_base/event/processor/event_processor.py index 7d9296fe4dd..23f6b7be1c8 100644 --- a/packages/reflex-base/src/reflex_base/event/processor/event_processor.py +++ b/packages/reflex-base/src/reflex_base/event/processor/event_processor.py @@ -393,8 +393,9 @@ async def enqueue( self._futures[txid] = tracked tracked.add_done_callback(self._try_clean_future) tracked.add_done_callback(self._on_future_done) - # If this context has a parent, register as a child of the parent's future. - if parent_future is not None: + # Register as a child of the parent's future; skip if the parent is + # already done (late-chained event) so the child runs instead of crashing. + if parent_future is not None and not parent_future.done(): parent_future.add_child(tracked) await queue.put(EventQueueEntry(event=event, ctx=ev_ctx)) return tracked diff --git a/tests/units/reflex_base/event/processor/test_event_processor.py b/tests/units/reflex_base/event/processor/test_event_processor.py index bcc1108be98..f4df7422356 100644 --- a/tests/units/reflex_base/event/processor/test_event_processor.py +++ b/tests/units/reflex_base/event/processor/test_event_processor.py @@ -2,6 +2,7 @@ import asyncio import contextlib +import dataclasses from typing import Any import pytest @@ -11,6 +12,7 @@ QueueShutDown, _stream_queue_until_done, ) +from reflex_base.event.processor.future import EventFuture from reflex_base.registry import RegistrationContext from reflex.event import Event, EventHandler @@ -458,6 +460,37 @@ async def test_chained_event_processed(token: str): assert _CALL_LOG == [{"value": "chained"}] +async def test_enqueue_child_of_done_parent_does_not_crash( + mock_event_processor: EventProcessor, + token: str, +): + """Regression: a late-chained event whose parent future already completed + still runs instead of crashing when registered as the parent's child. + + Args: + mock_event_processor: The event processor with mock root context. + token: The client token. + """ + async with mock_event_processor as ep: + done_parent = EventFuture(txid="parent-txid") + done_parent.set_result(None) + ep._futures["parent-txid"] = done_parent + + child_ctx = dataclasses.replace( + ep._root_context.fork(token=token), parent_txid="parent-txid" + ) + future = await ep.enqueue( + token, + Event.from_event_type(logging_event("late-child"))[0], + ev_ctx=child_ctx, + ) + await future + + assert _CALL_LOG == [{"value": "late-child"}] + # The child is not registered under the already-done parent. + assert done_parent.children == [] + + async def test_join_when_not_started(processor: EventProcessor): """join() when not started is a no-op (queue is None). From 91feef3fde6aae7f14715f0ce7c8569e78f855b8 Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Sun, 19 Jul 2026 02:17:23 +0200 Subject: [PATCH 2/2] typing --- tests/units/reflex_base/event/processor/test_event_processor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/units/reflex_base/event/processor/test_event_processor.py b/tests/units/reflex_base/event/processor/test_event_processor.py index f4df7422356..b4911e96fa0 100644 --- a/tests/units/reflex_base/event/processor/test_event_processor.py +++ b/tests/units/reflex_base/event/processor/test_event_processor.py @@ -476,6 +476,7 @@ async def test_enqueue_child_of_done_parent_does_not_crash( done_parent.set_result(None) ep._futures["parent-txid"] = done_parent + assert ep._root_context is not None child_ctx = dataclasses.replace( ep._root_context.fork(token=token), parent_txid="parent-txid" )