Skip to content

Keep disk state reads, sidecar stats and redis version lookup off the event loop - #7065

Open
FarhanAliRaza wants to merge 3 commits into
mainfrom
claude/blockbuster-reflex-analysis-06qhgm
Open

Keep disk state reads, sidecar stats and redis version lookup off the event loop#7065
FarhanAliRaza wants to merge 3 commits into
mainfrom
claude/blockbuster-reflex-analysis-06qhgm

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

What

I ran a representative app under blockbuster (patched into a recording mode so one run collects every blocking call instead of raising on the first) through AppHarness, driving hydrate/on_load, plain events, computed vars, yield streaming, background tasks, uploads, client storage, rx.session/rx.asession, rx.call_script, redirects and re-hydration, once per state manager and once in prod mode with the backend serving the exported frontend. The memory state manager is clean on the event path. Three Reflex-internal blocking calls showed up in the other configurations, fixed here:

  1. Disk state manager read state files on the loop. StateManagerDisk.load_state did exists() + open() + pickle.load synchronously on every cache miss, once per substate. The read now runs in a worker thread like the write already did. The per-write states_directory.exists() on the loop is gone too; the thread handles FileNotFoundError by recreating the directory.

  2. Precompressed static serving stat'd sidecars on the loop. PrecompressedStaticFiles._select_sidecar called Path.stat() inside the sync file_response hook, once per accepted encoding per request. Starlette runs its own path lookup in a thread; the sidecar selection now runs in a thread from get_response, which then finishes the response (Vary/Content-Encoding, conditional 304 check). This also replaces the previous special-case re-route for the 404.html fallback, since the same path covers it.

  3. redis-py resolved its version per connection. redis-py >= 7 calls importlib.metadata.version("redis") in every new Connection.__init__ unless the version is passed in, which walks sys.path and reads METADATA on the loop. It fired 10 times in one short session (pool growth, lock pubsub, keyspace notifications, token manager). get_redis() now pins it via driver_info on 7.x (lib_version on older releases, imported dynamically so the min-version pyright check stays clean).

Each fix has a regression test that fails on the previous code by recording filesystem/metadata calls made on the event loop thread.

scripts/blockbuster_audit.py is the audit itself (dev per state manager, --prod, and a pytest-plugin mode) so this can be re-run after touching istate/, the upload path or static serving. Blockbuster only sees syscall-shaped blocking; CPU work on the loop (pickle, deepcopy of defaults, get_type_hints per event) is out of its scope.

Not changed: rx.get_upload_dir() does a mkdir(exist_ok=True) per call from handlers (microseconds; caching it would change behaviour if the directory is removed at runtime), and the docs still show with rx.session() inside event handlers far more often than rx.asession(), which for user apps is the biggest real blocker.

Testing

  • tests/units/istate, tests/units/test_state.py, tests/units/utils, tests/units/test_health_endpoint.py: all pass except tests/units/istate/test_data.py::test_reflex_url_serializes_when_nested_in_router_data, which fails identically on main in this environment.
  • ruff check/ruff format clean; pyright clean on the changed files.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CMzuxXkRe8q3t4sUxjrggy


Generated by Claude Code

Review in cubic

Runs a representative app in-process via AppHarness with blockbuster in a
recording mode, drives the common event paths with Playwright per state
manager (and prod static serving), and reports every blocking call that ran
on the backend event loop with its stack.
… event loop

Found with the blockbuster audit:

- StateManagerDisk.load_state opened and unpickled the state file
  synchronously on every cache miss, once per substate. Move the read into a
  worker thread like the write already was, and drop the per-write
  states_directory.exists() by handling FileNotFoundError in the thread.
- PrecompressedStaticFiles stat'd each sidecar candidate on the loop inside
  file_response, per accepted encoding per request. Starlette runs its own
  lookup in a thread; do the sidecar selection there too, from get_response,
  and finish the response (Vary/Content-Encoding, conditional check) after.
- redis-py >= 7 resolves its own version through importlib.metadata for every
  new connection, scanning sys.path and reading METADATA on the loop. Create
  the client with the version pinned (driver_info on 7.x, lib_version before).

Each change has a regression test that fails on the previous code.
@FarhanAliRaza
FarhanAliRaza requested a review from a team as a code owner September 7, 2026 21:41
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 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-07T21:45:09.562166Z 106a871 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.

@codspeed-hq

codspeed-hq Bot commented Sep 7, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 32 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing claude/blockbuster-reflex-analysis-06qhgm (ea0d227) with main (c49a85d)

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.

@greptile-apps

greptile-apps Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR moves three sources of blocking work away from the backend event loop and adds regression coverage:

  • Runs disk-state reads and recovery writes in worker threads.
  • Performs precompressed sidecar selection off-loop while preserving conditional static responses.
  • Supplies redis-py's library version when constructing asynchronous connections.
  • Adds an event-loop blocking audit utility and targeted unit tests.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete correctness, security, or repository-rule violations identified.

The disk-state, static-serving, and Redis changes preserve their existing contracts while moving the identified blocking operations off the event loop, and targeted regression tests cover each optimization.

Important Files Changed

Filename Overview
reflex/istate/manager/disk.py Moves state-file reads and directory-recovery writes off the event loop without changing missing or invalid state handling.
reflex/utils/precompressed_staticfiles.py Defers sidecar filesystem inspection to a worker thread and centralizes conditional response handling.
reflex/utils/prerequisites.py Passes redis-py's library version through the version-appropriate connection argument.
scripts/blockbuster_audit.py Adds a reusable development and pytest audit for recording blocking calls on active event loops.
tests/units/istate/manager/test_disk.py Verifies state-file reads and writes do not execute on the event-loop thread.
tests/units/utils/test_precompressed_staticfiles.py Verifies sidecar stats occur off-loop while retaining compressed and fallback serving behavior.
tests/units/utils/test_prerequisites.py Verifies Redis connections use the supplied driver version without performing per-connection metadata lookup.

Reviews (1): Last reviewed commit: "Name news fragment after PR #7065" | Re-trigger Greptile

@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: 106a871815

ℹ️ 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".

ad hoc; ``uv run`` would re-sync them away, hence the direct interpreter)::

uv pip install blockbuster aiosqlite
REFLEX_STATE_MANAGER_MODE=memory .venv/bin/python scripts/blockbuster_audit.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Run the audit through uv

Anyone following these instructions will invoke the virtualenv interpreter directly, bypassing the repository's required environment-management workflow. Use uv run --with blockbuster --with aiosqlite scripts/blockbuster_audit.py (and its pytest equivalent) so the extra dependencies remain available without violating the explicit prohibition on bare Python execution.

AGENTS.md reference: AGENTS.md:L11-L13

Useful? React with 👍 / 👎.

@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.

4 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="news/7065.performance.md">

<violation number="1" location="news/7065.performance.md:1">
P3: The middle item of the list has no verb: 'precompressed static file serving stats sidecar files off the loop' is an orphaned noun phrase, while the other two clauses each have a subject and verb. Give it one, e.g. '...sidecar files are resolved off the loop'.</violation>
</file>

<file name="scripts/blockbuster_audit.py">

<violation number="1" location="scripts/blockbuster_audit.py:14">
P2: Run the audit through uv instead of documenting direct `.venv/bin/python` execution. Use `uv run --with blockbuster --with aiosqlite ...` for these commands and the equivalent pytest invocation so the extra dependencies remain available while following the repository's required environment workflow.</violation>

<violation number="2" location="scripts/blockbuster_audit.py:121">
P2: On Windows, `root` never prefixes the backslash-separated frame filenames, so the report misclassifies every Reflex and test finding as `3RDPARTY`. Normalize frame paths with `Path.resolve()` and compare them using `Path.relative_to()` or equivalent platform-independent logic.</violation>

<violation number="3" location="scripts/blockbuster_audit.py:375">
P1: When `reflex.db` exists in the caller's working directory, this unconditional unlink deletes it before the audit and can destroy unrelated data. Use an isolated database under `args.root`, configure both URLs to it, and only remove that isolated path.</violation>
</file>

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

Re-trigger cubic

args.root.mkdir(parents=True, exist_ok=True)
os.environ.setdefault("REFLEX_DB_URL", "sqlite:///reflex.db")
os.environ.setdefault("REFLEX_ASYNC_DB_URL", "sqlite+aiosqlite:///reflex.db")
Path("reflex.db").unlink(missing_ok=True)

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: When reflex.db exists in the caller's working directory, this unconditional unlink deletes it before the audit and can destroy unrelated data. Use an isolated database under args.root, configure both URLs to it, and only remove that isolated path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/blockbuster_audit.py, line 375:

<comment>When `reflex.db` exists in the caller's working directory, this unconditional unlink deletes it before the audit and can destroy unrelated data. Use an isolated database under `args.root`, configure both URLs to it, and only remove that isolated path.</comment>

<file context>
@@ -0,0 +1,405 @@
+    args.root.mkdir(parents=True, exist_ok=True)
+    os.environ.setdefault("REFLEX_DB_URL", "sqlite:///reflex.db")
+    os.environ.setdefault("REFLEX_ASYNC_DB_URL", "sqlite+aiosqlite:///reflex.db")
+    Path("reflex.db").unlink(missing_ok=True)
+
+    activate()
</file context>

Returns:
Number of distinct findings written.
"""
root = str(REPO_ROOT) + "/"

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.

P2: On Windows, root never prefixes the backslash-separated frame filenames, so the report misclassifies every Reflex and test finding as 3RDPARTY. Normalize frame paths with Path.resolve() and compare them using Path.relative_to() or equivalent platform-independent logic.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/blockbuster_audit.py, line 121:

<comment>On Windows, `root` never prefixes the backslash-separated frame filenames, so the report misclassifies every Reflex and test finding as `3RDPARTY`. Normalize frame paths with `Path.resolve()` and compare them using `Path.relative_to()` or equivalent platform-independent logic.</comment>

<file context>
@@ -0,0 +1,405 @@
+    Returns:
+        Number of distinct findings written.
+    """
+    root = str(REPO_ROOT) + "/"
+    venv = root + ".venv/"
+
</file context>


uv pip install blockbuster aiosqlite
REFLEX_STATE_MANAGER_MODE=memory .venv/bin/python scripts/blockbuster_audit.py
REFLEX_STATE_MANAGER_MODE=disk .venv/bin/python scripts/blockbuster_audit.py

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.

P2: Run the audit through uv instead of documenting direct .venv/bin/python execution. Use uv run --with blockbuster --with aiosqlite ... for these commands and the equivalent pytest invocation so the extra dependencies remain available while following the repository's required environment workflow.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/blockbuster_audit.py, line 14:

<comment>Run the audit through uv instead of documenting direct `.venv/bin/python` execution. Use `uv run --with blockbuster --with aiosqlite ...` for these commands and the equivalent pytest invocation so the extra dependencies remain available while following the repository's required environment workflow.</comment>

<file context>
@@ -0,0 +1,405 @@
+
+    uv pip install blockbuster aiosqlite
+    REFLEX_STATE_MANAGER_MODE=memory .venv/bin/python scripts/blockbuster_audit.py
+    REFLEX_STATE_MANAGER_MODE=disk .venv/bin/python scripts/blockbuster_audit.py
+    REFLEX_STATE_MANAGER_MODE=redis REFLEX_REDIS_URL=redis://localhost:6379 \\
+        .venv/bin/python scripts/blockbuster_audit.py
</file context>

Comment thread news/7065.performance.md
@@ -0,0 +1 @@
Keep blocking filesystem and metadata work off the backend event loop: the disk state manager now reads state files in a worker thread, precompressed static file serving stats sidecar files off the loop, and the redis client is created with its library version pinned so redis-py no longer scans `sys.path` on every new connection.

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.

P3: The middle item of the list has no verb: 'precompressed static file serving stats sidecar files off the loop' is an orphaned noun phrase, while the other two clauses each have a subject and verb. Give it one, e.g. '...sidecar files are resolved off the loop'.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At news/7065.performance.md, line 1:

<comment>The middle item of the list has no verb: 'precompressed static file serving stats sidecar files off the loop' is an orphaned noun phrase, while the other two clauses each have a subject and verb. Give it one, e.g. '...sidecar files are resolved off the loop'.</comment>

<file context>
@@ -0,0 +1 @@
+Keep blocking filesystem and metadata work off the backend event loop: the disk state manager now reads state files in a worker thread, precompressed static file serving stats sidecar files off the loop, and the redis client is created with its library version pinned so redis-py no longer scans `sys.path` on every new connection.
</file context>
Suggested change
Keep blocking filesystem and metadata work off the backend event loop: the disk state manager now reads state files in a worker thread, precompressed static file serving stats sidecar files off the loop, and the redis client is created with its library version pinned so redis-py no longer scans `sys.path` on every new connection.
Keep blocking filesystem and metadata work off the backend event loop: the disk state manager now reads state files in a worker thread, precompressed static file serving stats sidecar files are resolved off the loop, and the redis client is created with its library version pinned so redis-py no longer scans `sys.path` on every new connection.

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.

1 participant