Repository navigation
Serialize production frontend work sharing one .web directory - #7453
Conversation
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
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
All reported issues were addressed across 8 files
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
💡 Codex ReviewLine 69 in b7d35ca .web
When a backend-only export starts while ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
[Medium impact] The change reaches production export and frontend-start workflows that share a `.web` directory. The reported import failure in stacked PR #7455 still needs to be addressed before the two PRs can merge together. SummaryThis PR makes production exports and startup builds wait when they share
Reviews (4) · Last reviewed commit: "Keep the export race test's lock directo..." · Reviewed by Greptile |
Merging this PR will not alter performance
|
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
left a comment
There was a problem hiding this comment.
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
The production build lock had its own copy of the platform-specific locking (flock / msvcrt) next to the sidecar-based json_file_lock, with different Windows polling, a different failure mode and a lock file inside .web. Keep one implementation: json_file_lock becomes file_lock, usable for files and directories, and gains the reentrancy and fork handling the build lock needed. Exports and production/preview startup now lock the .web directory through it. The sidecar lives in REFLEX_DIR, so locking never creates .web and the lock inode survives .web being replaced. Backend-only exports therefore always take the lock instead of skipping it when .web is absent, which closes the race the review bots flagged. The lock is released explicitly on Windows before the descriptor closes. Claude-Session: https://claude.ai/code/session_017XAFCkYB3Z8w3S6pgJrLCh
There was a problem hiding this comment.
All reported issues were addressed across 11 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
View guided diff | Turn on auto-fix | Re-trigger cubic
The backend ZIP walks the app root, and REFLEX_DIR sat inside it, so the exporter archived its own held lock file. Windows refuses reads of a byte-range-locked file, failing the zip with PermissionError. Claude-Session: https://claude.ai/code/session_01PXcFKAgnzhgdnSvhuCmxWE
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 prodstart) share.web: the second one rewritesenv.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
path_ops.json_file_lock(from Prevent concurrent reflex.json update corruption #7052) becomespath_ops.file_lock: one implementation of the platform-specific locking (flockon POSIX,msvcrt.lockingon Windows) for files and directories, instead of a second copy for builds. It gains what the build lock needs: nested calls from the same thread reuse the lock, other threads and processes wait, and a forked child drops inherited ownership and waits for its parent. On Windows the lock is released explicitly before the descriptor closes.json_file_lockwas never released, so there is no deprecation shim. Serialize concurrent frontend dependency installs #7051 makes the same rename and locksget_web_dir()through it, so the two converge.REFLEX_DIRkeyed by the resolved.webpath, not a file inside.web. Locking never creates.web, and the lock inode survives.webbeing replaced byreflex init. As withjson_file_lock, an unwritable lock directory or an unsupported filesystem logs at debug and proceeds unlocked.export.export()holds it from configuration through ZIP creation. Backend-only exports always take it too, since they archive.web/backend; the earlier "only when.webexists" condition existed to avoid creating.web, which the sidecar makes moot, so the race the review bots flagged on that line is closed._run_prod/_run_previewhold it through compile and build and release it before serving.Measurement
No speedup is claimed. Lock overhead on an unchanged bun export, repeat runs:
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
blanktemplate (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 callingreflex.utils.export.export()the same wayreflex exportdoes; durations are the phase timings the export already reports to telemetry (compileincludes 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
Checklist
tests/units/utils,test_reflex.py,test_prerequisites.pyandtest_workflow_gates.pypass locallyruff check,ruff format --check,codespell, andpyright reflex testspass locallyhttps://claude.ai/code/session_018nWB9r2HcC5tkT72UxWgGj
https://claude.ai/code/session_017XAFCkYB3Z8w3S6pgJrLCh