Skip to content

Prevent concurrent reflex.json update corruption - #7052

Open
Alek99 wants to merge 2 commits into
mainfrom
codex/lock-reflex-json-updates
Open

Alek99 wants to merge 2 commits into
mainfrom
codex/lock-reflex-json-updates

Conversation

@Alek99

@Alek99 Alek99 commented Sep 6, 2026

Copy link
Copy Markdown
Member

What changed

  • Serialize JSON read-modify-write operations with a cross-process hard lock stored under the stable per-user Reflex data directory and keyed by the normalized target path. Frontend reinitialization acquires the same locks before replacing .web.
  • Write complete JSON documents through an exclusive same-directory staging file, flush and fsync it, then atomically replace the target while retaining existing permissions and normal umask behavior for new files.
  • Send only the version-check timestamp delta so a stale caller snapshot cannot replay unrelated metadata over a concurrent update.
  • Keep filelock and other write-only imports off CLI import paths.

Why

Concurrent Reflex commands could read the same reflex.json snapshot and then overwrite or interleave their writes, losing disjoint keys or leaving malformed JSON. A sidecar beside reflex.json is not sufficient because frontend initialization can delete and recreate .web while another process still owns the old lock inode; the stable hashed lock identity survives that rebuild.

Validation

  • uv run pytest tests/units/utils/test_path_ops.py tests/units/test_prerequisites.py tests/units/utils/test_build.py -q (116 passed)
  • uv run ruff format --check .
  • uv run ruff check .
  • uv run pyright reflex tests
  • uv run python scripts/check_min_deps.py reflex
  • uv lock --check
  • git diff --check origin/main

The concurrency regression pauses one process inside update_json_file after its read, optionally deletes and recreates .web, positively verifies a second public updater is denied the same lock before JSON loading, then confirms both disjoint updates survive. Failure coverage includes serialization, flush, fsync, replacement, malformed and missing files, a hard process exit, and readers observing only complete JSON.

Residual tradeoffs

An uncatchable process exit during staging can leave a hidden temporary file beside the target, but that partial document is never installed at the public path. Persistent zero-byte hashed lock files intentionally remain in the Reflex data directory so lock identity is never split by unlinking.

Review in cubic

@Alek99
Alek99 requested a review from a team as a code owner September 6, 2026 07:59
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@greptile-apps

greptile-apps Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes shared JSON metadata updates atomic and serializes concurrent writers using stable per-user file locks.

  • Writes complete documents to same-directory staging files before atomic replacement.
  • Preserves existing permissions and normal umask behavior.
  • Protects frontend directory rebuilding from deleting active JSON staging files.
  • Avoids replaying stale metadata during version-check timestamp updates.
  • Adds concurrency, failure-path, normalization, and permission regression coverage.

Confidence Score: 5/5

The PR appears safe to merge; the previously outstanding cleanup race is fully protected and no new actionable failures were identified.

Frontend cleanup now acquires the same stable locks used by JSON writers before deleting and recreating .web, so active staging files cannot be removed during replacement. The earlier hardcoded-directory finding is resolved, and the new lock ordering, path normalization, and fixed test subprocess invocation do not introduce actionable issues.

Important Files Changed

Filename Overview
reflex/utils/path_ops.py Adds stable cross-process locking and atomic staged replacement for JSON read-modify-write operations.
reflex/utils/frontend_skeleton.py Locks both managed JSON targets while the frontend directory is destructively rebuilt, fully addressing the prior staging-file cleanup race.
reflex/utils/prerequisites.py Sends only the version-check timestamp delta so stale snapshots cannot overwrite unrelated concurrent metadata.
reflex/constants/base.py Defines the JSON lock directory as a shared constant, satisfying the previously reported repository rule.
tests/units/utils/test_path_ops.py Adds broad regression coverage for concurrency, cleanup coordination, atomicity, failures, path aliases, and permissions.

Reviews (2): Last reviewed commit: "Protect JSON staging during web initiali..." | Re-trigger Greptile

@codspeed

codspeed Bot commented Sep 6, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 32 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing codex/lock-reflex-json-updates (2240413) 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.

Comment thread reflex/utils/path_ops.py
Comment thread reflex/utils/path_ops.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 7 files

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

Re-trigger cubic

Comment thread reflex/utils/path_ops.py
Comment thread reflex/utils/path_ops.py
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