Skip to content

fix(config): preserve preloaded rxconfig classes - #7144

Open
FarhanAliRaza wants to merge 6 commits into
reflex-dev:mainfrom
FarhanAliRaza:codex/config-reload-boundary
Open

FarhanAliRaza wants to merge 6 commits into
reflex-dev:mainfrom
FarhanAliRaza:codex/config-reload-boundary

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Classes defined in rxconfig.py (for example a rx.State subclass the app imports from its config module) lost their identity because the config module was executed more than once per process. The app module imports rxconfig, then App.__init__ called reload_config(), which re-executed rxconfig.py and produced a second copy of every class it defines. The result was a StateValueError for the duplicate substate, or _state pointing at a class the app never imported, and pickle round-trips that failed to resolve the original type.

The fix lives in the config loader and in App, so it applies to any ASGI server. No special backend entry point is needed; granian --interface asgi --factory myapp.myapp:app works unchanged.

  • App.__init__ now calls get_config() instead of reload_config(), reusing the config already loaded in the active RegistrationContext.
  • The first get_config() load of a context reuses an rxconfig module that project code already imported from the project root, instead of executing the file again. A module that _get_config() itself loaded is not reused, so separate contexts still get independent Config instances. reload_config() still always loads from disk.
  • reflex run drops its redundant reload_config() after persisting the host and port overrides; those values are already written through _set_persistent.
  • The unit-test autouse fixture now clears the Config slot on the context between tests as well as the App slot, so a mocked config from one test cannot leak into the next.

reflex/utils/exec.py is unchanged from main.

Tests

  • tests/units/test_app.py: classes defined in rxconfig.py keep their identity across App() construction, both when the config was loaded first and when the app module imported rxconfig first (the direct ASGI case).

Validation

  • Full unit suite, ruff and pyright pass.
  • A real app whose rxconfig.py defines an rx.State: direct granian --factory pkg.mod:app failed with StateValueError before and serves requests now; reflex run dev startup, State events, and hot reload of the app file and of rxconfig.py work.

@FarhanAliRaza
FarhanAliRaza requested a review from a team as a code owner September 14, 2026 21:59
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-14T22:03:46.895468Z fe952bc PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@greptile-apps

greptile-apps Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

The PR is not yet safe to merge because forked registration contexts can share one mutable Config instance instead of remaining independent.

Findings

  1. P1 Forks Share Mutable Config

Summary

This PR changes configuration loading and app construction to preserve the identity of classes defined in a preloaded rxconfig.py.

  • Reuses a project-imported configuration module during the first context load.
  • Makes App read the cached configuration instead of forcing a reload.
  • Removes a redundant CLI reload after persisting host and port overrides.
  • Isolates cached App and Config slots between unit tests and adds class-identity regressions.

Reviews (6) · Last reviewed commit: "fix(config): reuse an rxconfig the app a..."

Comment thread packages/reflex-base/src/reflex_base/config.py Outdated
Comment thread tests/units/test_app.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe952bca61

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/reflex-base/src/reflex_base/config.py Outdated
Comment thread packages/reflex-base/src/reflex_base/config.py Outdated
@codspeed

codspeed Bot commented Sep 14, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 40 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing FarhanAliRaza:codex/config-reload-boundary (5409e0e) with main (2f63cb3)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 8 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/reflex-base/src/reflex_base/config.py Outdated
Comment thread packages/reflex-base/src/reflex_base/config.py Outdated
Comment thread tests/units/test_app.py
Comment thread reflex/utils/exec.py Outdated
Comment thread reflex/utils/exec.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 6 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread tests/units/utils/test_exec.py Outdated

@masenf masenf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

does this break the mode where i invoke granian directly against my reflex app module? it seems i would need to change my invocation to granian --interface asgi --factory reflex.utils.exec:load_app.

this moves us farther away from the goal of asgi interoperability, because now a special shim is required to start the app.

is it not sufficient to just get the config inside App.__post_init__? then it could be transparently used by any asgi server

…backend factory shim

The first get_config() load of a context re-executed rxconfig.py even when the
app module had already imported it, so a direct ASGI invocation still got a
second copy of every class. Reuse the imported module for that first load and
drop the load_app factory; exec.py is back to its main version.
with _load_config_lock:
if ctx._config is None:
ctx._set_config(_get_config())
ctx._set_config(_imported_config(Path.cwd().resolve()) or _get_config())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Forks Share Mutable Config

When project code has already imported rxconfig, a forked RegistrationContext receives that module's existing mutable Config object. This conflicts with RegistrationContext.fork(), which resets _config and promises that the next get_config() call reloads it. The parent and fork can therefore share configuration mutations, allowing persistent overrides or other changes from one app to leak into the other.

Knowledge Base Used: Application lifecycle and configuration

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.

2 participants