Skip to content

Serialize production frontend work sharing one .web directory - #7453

Open
FarhanAliRaza wants to merge 3 commits into
mainfrom
farhan/frontend-workspace-lock
Open

FarhanAliRaza wants to merge 3 commits into
mainfrom
farhan/frontend-workspace-lock

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Split out of #7054. This is the lock only; the opt-in build cache that motivated it is #7455 and is stacked on this.

Problem

Two production exports in the same project (or an export racing a reflex run --env prod start) share .web: the second one rewrites env.json, build/, and the compiled sources while the first is still compiling or zipping, so one of them ships the other's frontend. Review bots on #7054 flagged this for the cache, but it is a pre-existing race.

Changes

  • reflex/utils/build_lock.py: an exclusive, reentrant, per-.web file lock on .web/.reflex-build.lock (flock on POSIX, msvcrt.locking with a 100 ms poll on Windows). Nested calls in one thread reuse the lock; forked children drop inherited ownership; a symlinked or non-regular lock path is refused instead of silently proceeding unlocked.
  • export.export() holds it from configuration through ZIP creation. A backend-only export takes it only when .web already exists (it archives .web/backend), and never creates .web.
  • _run_prod / _run_preview hold it through compile and build and release it before serving.
  • Tests: spawned-process serialization, Windows contention/failure paths, fork behavior, symlink/flock failure propagation, and a real two-process export test where a backend ZIP waits for the compiler's temporary stateful-pages marker to be renamed.

Measurement

No speedup is claimed. Lock overhead on an unchanged bun export, repeat runs:

main this PR
total export 6.90 s 6.84 s
compile phase 0.46 s 0.44 s

This is pure complexity in service of correctness (and of #7455). If the cache is not wanted, the question for this one is only whether the race above is worth fixing.

Measurement setup

Five-page app from the blank template (Radix Themes + Tailwind v4 plugins), Linux x86_64, 4 vCPU, Python 3.14, Node 22.22, npm 10.9, bun 1.4.2. Each run is a fresh interpreter calling reflex.utils.export.export() the same way reflex export does; durations are the phase timings the export already reports to telemetry (compile includes the dependency install). Medians of 3 runs unless noted. Vite dominates every export (~6.5 s); these numbers isolate the part this PR touches.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • This change requires a documentation update

Checklist

  • Tests added for the change; the full tests/units suite passes locally except the 14 reflex_bench/drivers/test_browser.py cases that need a Playwright browser launch, which fail identically on untouched main in this sandbox
  • ruff check, ruff format --check, codespell, and pyright reflex tests pass locally
  • News fragment added for every package touched

https://claude.ai/code/session_018nWB9r2HcC5tkT72UxWgGj

Two exports (or an export and a prod/preview start) in the same project
used to race on .web: the second replaced build/ and env.json while the
first was still compiling or zipping, so one of them shipped the other's
frontend. Hold an exclusive, reentrant file lock on
.web/.reflex-build.lock from compilation through ZIP creation, release it
before serving, and never create .web for a backend-only export that has
nothing to protect.

Split out of #7054 (lock only; the opt-in build cache that needs it follows
separately).

Claude-Session: https://claude.ai/code/session_018nWB9r2HcC5tkT72UxWgGj
@FarhanAliRaza
FarhanAliRaza requested review from a team and Alek99 as code owners October 6, 2026 18:19
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 6, 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-10-06T18:24:53.157193Z b7d35ca 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.

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

Turn on auto-fix | Re-trigger cubic

Comment thread reflex/reflex.py
Comment thread tests/units/utils/test_export.py
Comment thread reflex/utils/export.py
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

if frontend or (backend and zipping and web_dir.is_dir())

P1 Badge Lock backend exports before checking for .web

When a backend-only export starts while .web is absent and a concurrent frontend export creates it immediately after this is_dir() check, the backend export proceeds under nullcontext(). build.zip_app() later globs .web/backend/*, so it can archive missing or partially written compiler artifacts while the frontend process believes it owns the workspace lock. The existence check and decision about whether generated backend files may be included must be synchronized to close this first-build race.

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

@greptile-apps

greptile-apps Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Critical risk] Adds file locking to production frontend builds and exports.

The previous backend ZIP race still needs to be fixed before merging.

What we checked:

  • Children cannot release the parent's lock: The child closes inherited descriptors without unlocking them. The inherited context only unlocks and closes its descriptor in the process that acquired it.

Summary

This PR adds a shared lock for production frontend work.

  • Exports hold it through compilation, build, and ZIP creation.
  • Production and preview startup release it before serving.
  • Tests cover process contention, Windows errors, fork ownership, and failed builds.
  • Documentation explains the lock's scope and persistent file.

Reviews (2) · Last reviewed commit: "Drop the PERF203 noqa that ruff no longe..."

Comment thread reflex/utils/export.py
@codspeed

codspeed Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 5.73%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
✅ 148 untouched benchmarks
⏩ 18 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ test_route_argument_extraction 121.4 µs 113.4 µs +7.11%
⚡ test_from_event_type[event_spec] 97.3 µs 93.2 µs +4.37%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing farhan/frontend-workspace-lock (a639b01) with main (d2528ac)

Open in CodSpeed

Footnotes

  1. 18 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. ↩

claude added 2 commits October 6, 2026 19:58
Brings in #7449, which drops Python 3.10 and moves ruff to py311.

Claude-Session: https://claude.ai/code/session_01PXcFKAgnzhgdnSvhuCmxWE
PERF203 only applies before Python 3.11, where try/except in a loop costs
per iteration. With ruff targeting py311 since #7449 the directive is
unused, which RUF100 rejects.

Claude-Session: https://claude.ai/code/session_01PXcFKAgnzhgdnSvhuCmxWE
@masenf masenf added the on deck PRs lined up to review / merge next label Oct 13, 2026

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

should consolidate locking implementation with existing json_file_lock so we don't have multiple code paths handling platform-specific file locking in slightly different ways

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

on deck PRs lined up to review / merge next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants