Skip to content

Conversation

@benedikt-bartscher
Copy link
Contributor

this makes it much easier to write reusable code which can be used in background and "normal" events and avoids a lot of dirty workarounds to make it possible

we can also choose to make this the default instead of adding just another config option

@codspeed-hq
Copy link

codspeed-hq bot commented Oct 29, 2025

CodSpeed Performance Report

Merging #5938 will not alter performance

Comparing benedikt-bartscher:allow-state-context-manager-in-normal-events (4cf6303) with main (c4254ed)

Summary

✅ 8 untouched

@benedikt-bartscher benedikt-bartscher marked this pull request as ready for review October 29, 2025 19:22
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Greptile Summary

Adds optional config to allow async with self syntax in normal (non-background) event handlers, enabling code reuse between background tasks and regular event handlers.

Key changes:

  • Added new environment variable REFLEX_STATE_ALLOW_CONTEXTMANAGER_WITHOUT_BACKGROUND_TASK (defaults to False)
  • Modified BaseState.__aenter__ to return self directly when config is enabled, instead of raising TypeError
  • The context manager becomes a no-op in normal event handlers (which already hold state locks), maintaining safety while allowing syntax compatibility

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation is sound and addresses a valid use case. The changes are minimal, well-scoped, and maintain backward compatibility by defaulting to False. The approach is safe because normal event handlers already hold state locks, making the context manager a harmless no-op. No breaking changes or risky patterns introduced.
  • No files require special attention

Important Files Changed

File Analysis

Filename Score Overview
reflex/environment.py 5/5 Added new config option REFLEX_STATE_ALLOW_CONTEXTMANAGER_WITHOUT_BACKGROUND_TASK with default value False
reflex/state.py 5/5 Modified __aenter__ to allow async with self in normal event handlers when config option is enabled, returns self directly as a no-op

Sequence Diagram

sequenceDiagram
    participant EventHandler as Normal Event Handler
    participant BgTask as Background Task
    participant BaseState as BaseState
    participant StateProxy as StateProxy
    participant Config as Environment Config

    Note over EventHandler,StateProxy: Before this PR
    EventHandler->>BaseState: async with self
    BaseState->>BaseState: __aenter__()
    BaseState-->>EventHandler: TypeError: Only background task should use async with self
    
    BgTask->>StateProxy: async with self
    StateProxy->>StateProxy: __aenter__() - acquires lock
    StateProxy-->>BgTask: StateProxy (mutable)
    BgTask->>StateProxy: modify state
    BgTask->>StateProxy: __aexit__()
    StateProxy->>StateProxy: releases lock, emits update

    Note over EventHandler,StateProxy: After this PR (config enabled)
    EventHandler->>BaseState: async with self
    BaseState->>Config: check ALLOW_CONTEXTMANAGER config
    Config-->>BaseState: True
    BaseState-->>EventHandler: self (no-op, lock already held)
    EventHandler->>BaseState: modify state
    EventHandler->>BaseState: __aexit__() (no-op)

    BgTask->>StateProxy: async with self
    Note over StateProxy: Unchanged behavior
    StateProxy->>StateProxy: __aenter__() - acquires lock
    StateProxy-->>BgTask: StateProxy (mutable)
Loading

2 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link
Collaborator

@masenf masenf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah lets bring it without the config option. this is fine

@masenf
Copy link
Collaborator

masenf commented Oct 29, 2025

one other thing to keep in mind is that async with self does an implicit yield of interim update to the frontend. if we just allow async with self to return the real state in non-background tasks, then the behavior would be slightly different.

@benedikt-bartscher
Copy link
Contributor Author

yeah lets bring it without the config option. this is fine

I hoped for that answer

one other thing to keep in mind is that async with self does an implicit yield of interim update to the frontend. if we just allow async with self to return the real state in non-background tasks, then the behavior would be slightly different.

That's true, but I found that in many use cases that's not really an issue

@benedikt-bartscher
Copy link
Contributor Author

one other thing to keep in mind is that async with self does an implicit yield of interim update to the frontend. if we just allow async with self to return the real state in non-background tasks, then the behavior would be slightly different.

While reading this again: I was aware of that, but never really thought deeply about this. Is there a simple way to avoid the implicit yield? For some use-cases it could be nice to allow only locking the state for backend var modification without sending the implicit update to the frontend. But that's another topic and should not be part of this PR

@masenf
Copy link
Collaborator

masenf commented Oct 29, 2025

the implicit yield is an implementation detail of using async with app.modify_state inside the background task StateProxy; because it happens out-of-band or normal event processing, we always send a StateUpdate when using app.modify_state. There's not really an easy way to do that with the current mechanism for processing events.

But i'm inclined to paper over that, with maybe a TODO comment for now, since i'm planning to refactor the event processing pipeline this weekend and will make it handle this case, and possibly even make the implicit yield an option that can be disabled.

@benedikt-bartscher benedikt-bartscher changed the title add config option to allow state mutation with contextmanager from no… allow state mutation with contextmanager from normal (non-background) events Oct 30, 2025
@adhami3310 adhami3310 merged commit 62264f1 into reflex-dev:main Oct 30, 2025
46 of 47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants