Conversation
Special case access patterns for BaseState retrieval to allow for other types of state that have different semantics.
Update all unit test cases to use the new StateToken / BaseStateToken API
Create the EventProcessor class to manage the backend event queue. Move event processing logic out of the BaseState and into a separate module.
* No tasks start until `.start()` is called * add graceful shutdown timeout to allow tasks to finish before cancellation * use more keyword only parameters * move BaseState-specific processing to new BaseStateEventProcessor subclass * add test fixtures for `mock_event_processor` that can process simple registered events
make Event.substate_token no longer work, because we're deprecating `token` as an Event field, so we cannot rely on it under the covers.
Use the new mock_base_state_event_processor fixture to process arbitrary events and assert on emitted events or deltas.
…Handler This allows better control over which states and events are part of a given app and avoiding true global variables makes cleanup and testing much simpler.
remove null/default fields when serializing Event from the frontend and StateUpdate from the backend.
Fix issue with background task delta calculation not starting from the root state
remove extra `event_context` ContextVar being passed around
EventProcessor.enqueue now returns a Future that tracks the completion of the event (and can be used to cancel the event) EventProcessor.enqueue_stream_delta overrides the default emit_delta implementation and instead yields deltas directly to the caller as the event is processing.
The function () => params.current baked inside the ensureSocketConnected function was getting a stale reference and the early events (hydrate, on load, client state) were missing the query parameters in their router_data and thus on_load was not working correctly.
Store the state_full_name to substate mapping in RegistrationContext Make it easier to register / re-register select states and event handlers in a new RegistrationContext Store StatefulComponent cached components in RegistrationContext for easier resetting/dropping after compilation or for use in testing.
Update all associated tests to make assertions using the browser/app and not attempting to fetch the backend state directly. This makes the tests more robust, reduces state_manager related hacks, and makes the tests easier to eventually migrate to an external process using granian where direct state_manager access will not be available.
instead of telling the frontend to reload, just hydrate and run on_load internally before processing the user's requested event.
raise coverage bar back up to 72 at least
Add unit test cases for reflex.istate.manager.token
Greptile SummaryThis is a large, well-structured architectural PR that moves event queuing and serialization from the frontend to the backend, reducing round-trip latency for chained events. The core ideas — Key changes:
Issues found:
Confidence Score: 4/5Safe to merge after fixing the cross-token delta routing bug in One confirmed P1 logic bug:
Important Files Changed
Reviews (1): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile |
packages/reflex-core/src/reflex_core/_internal/event/processor/event_processor.py
Show resolved
Hide resolved
packages/reflex-core/src/reflex_core/_internal/event/processor/base_state_processor.py
Outdated
Show resolved
Hide resolved
| """ | ||
| if timeout is None: | ||
| return cls(drain_deadline=None) | ||
| return cls(drain_deadline=time.time() + timeout) | ||
|
|
||
| def __enter__(self) -> float: | ||
| """Enter the context and yield the remaining time. | ||
|
|
||
| Returns: | ||
| The remaining time in seconds until the overall timeout is reached, or 0 if the timeout | ||
| has already been reached. | ||
| """ | ||
| if self.drain_deadline is not None: | ||
| return max(0, self.drain_deadline - time.time()) | ||
| return 0 |
There was a problem hiding this comment.
DrainTimeoutManager.__enter__ returns 0 for no-deadline case, blocking no drain
When drain_deadline is None (i.e., graceful_shutdown_timeout=None), __enter__ returns 0. The callers in stop() guard drain/stop operations with if remaining_time > 0, so a None timeout means nothing is awaited before force-cancelling tasks. This is the intended behaviour for immediate shutdown, but returning 0 rather than a sentinel value (like None) means the semantics are implicit and easy to misread.
This is a style/readability concern — the current behaviour is functionally correct.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Use the delta's token when emitting to the processor queue. Return after emitting to the processor queue so the caller does not get deltas from unrelated tokens. Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…/base_state_processor.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Remove TODO now that issue is created in repo.
* Vendor the new async iterator `asyncio.as_completed` for < 3.13 * Alternative Queue.shutdown mechanism for < 3.13 * Alternative to `Task.get_context` for < 3.12 * from __future__ import annotations for new modules (so TYPE_CHECKING imports work) * bug with cls super() call on dataclass with slots=True * typing_extensions.deprecated
Join the queue that we None'd out to make sure all the tasks have flushed. Fix some import issues being reported on my branch.
If there is another event to process, chain to processEvent
…gistrationContext
This patch moves the primary responsibility of chaining/queueing events to the backend. Previously the frontend was responsible for sending events to the backend. This created additional round-trip latency when chaining events using
yield.Summary: Event Processing & State Management Overhaul
Contexts are King
Instead of relying on ClassVar, module globals, and other hacks to track reflex objects, this PR introduces contextvars to the code base and leverages them to manage global state safely from anywhere in the app.
New Modules (in
reflex_core._internal)registry.py— ContextVar-based registry forBaseStateclasses andEventHandlers, replacing implicit global lookups. Currently used for states, events and stateful components. Eventually will be used for DECORATED_PAGES and other weird globals.event/context.py—EventContext(inheritsBaseContext) holding per-event metadata: token, state manager ref, enqueue/emit/delta callbacksevent/processor/— NewBaseStateEventProcessorandEventProcessorclasses that own the full event lifecycle (previously spread acrossAppandStatemethods)context/base.py— GenericBaseContextContextVar wrapperBreaking Changes (0.9.0)
Event.tokenfield removed —Eventno longer carries atoken; the token lives inEventContextEvent.substate_tokenreplaced byEvent.state_clsproperty (resolved via the registry)Deltatype refined fromdict[str, Any]→dict[str, dict[str, Any]](nested by substate name)StateManager.create()no longer takes astate=arg — state classes are discovered from the registryStateTokenintroduced (reflex/istate/manager/token.py) — typed generic token replacing raw"client_token_substate"strings in all state managers (disk, memory, redis)EventHandlerSetVar.state_cls→.statefield rename to align with EventHandler change.fix_eventsremoved from sortof public event API, replaced byEvent.from_event_type()get_hydrate_eventremoved — replaced by internal rehydration; simulated pre-hydrated states removedApp._background_tasksreplaced byApp.event_processor._tasks(processor manages background task lifecycle in the same way as normal event tasks)AppHarness(reflex/testing.py) simplified —state_managerproperty and related methods removedState Manager Changes
StateToken[BaseState]instead of raw string tokensclose()old locks properlyOPLOCK_ENABLEDsupport in state manager close/testsFrontend (
state.js)Test Infrastructure
conftest.pyforEventProcessor, registry, and contextget_app/mock_appdependencies removed from tests in favor of the new processor-based approach