You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 5a8c6ed
Browse filesBrowse the repository at this point in the historyBrowse files
### A3-11: `sync=True` LocalStorage: the #7493 boot echo writes back the stale value a tab read at connect time, so a change made in another tab while tabs are booting starts an endless cross-tab storage ping-pong (MEDIUM, regression vs a2, not vs 0.9.12; pending verification)
178
+
### A3-11: `sync=True` LocalStorage: the #7493 boot echo writes back the stale value a tab read at connect time, so a change made in another tab while tabs are booting starts an endless cross-tab storage ping-pong (MEDIUM, regression vs a2, not vs 0.9.12; CONFIRMED and widened by independent verifier; fix PR reflex#7505)
179
179
- Item `a3_hydration` (inbox 1). `src/bootecho`, `scripts/run_storm.sh a3 dev 8 3142 8142 S 6`: tab 0 sets the synced `be_theme` 5× 250 ms
180
180
apart while 6 more tabs load in the same browser context. a3: endless ping-pong dev 6/9 (+3/3 long runs), prod 2/5; a2 0/15; 0.9.12
181
181
9/10. Long runs: 8k–39k websocket frames per 5 s for 60 s, tabs stuck on mixed s2–s4 (the user's last value s5 is lost), backend
182
182
~50–70 % CPU. Two-tab form with inbound messages held: transient revert 1/2 (a2 0/2). Cause: `reflex/state.py:2401` + `2420-2422`
183
183
re-mark the applied browser values dirty after the snapshot → the boot delta carries the value read at CONNECT → `state.js:896/1076`
184
184
`localStorage.setItem` of the stale value → `storage` event in the other tabs (`state.js:1267`) → `update_vars_internal` → deltas write
185
185
back again. #7493 restored 0.9.12's boot echo, and with it 0.9.12's storm; a2 was immune because it echoed nothing.
186
+
-**Verification (`verify_hydration`, own app + drivers incl. stock headful Chromium over raw CDP with real background tabs, 100 ms RTT
187
+
proxy): CONFIRMED, wider than reported.** One user change and 3 tabs suffice: returning user, browser restart restoring 3 tabs 300 ms
188
+
apart, one click in the foreground tab → endless ping-pong a3 3/3 (+60 s run: 22–27k frames per 5 s, no decay), a2 0/3, 0.9.12 3/3;
189
+
Playwright 7 restored tabs: a3 stormed every time a tab booted across the click, a2 0/7, 0.9.12 6/6; prod + Redis 9 workers 2/2. With 2
190
+
tabs the user's own tab flips back to the old value 3–53 times (1/5 runs looped > 10 s). Effects: the clicked tab shows the old value
191
+
again, tabs end on mixed values, localStorage ends on the OLD value in 3/10 storms, a later click is swallowed, one client with 3 tabs
192
+
keeps the backend at ~72 % CPU; nothing logged. Not a Playwright artefact (no timers in the storm path). The explorer's localhost Part S
193
+
rate depends on machine load (verifier: a3 0/7 on localhost; the CONNECT→echo window is ~10 ms there, ~1 RTT on a real network).
194
+
Same mechanism as 0.9.12 (whose window is wider). Realistic trigger: `sync=True` LocalStorage, real network, ≥3 tabs, a change within
195
+
~1 RTT of another tab booting (session restore, reload, new tab). Fix location: the boot echo — keep #7493's re-marking for `get_delta`
196
+
overrides, but never write back an unchanged echo (the verifier's scratch patch of the compiled state.js: 0 storms).
186
197
-**Fix prototyped (10-07, at the maintainer's request)** on branch `claude/a3-11-storage-echo` (frontend only, `state.js`): do not write
187
198
an echoed local/session storage value back (cookies still renew `max_age`); on a storage event send the value stored now, not
188
199
`e.newValue`; resync a synced var whose echo crossed a newer stored value. Storm driver vs published a3: dev 0 storms in 11 runs
189
200
(a3 3/4), prod 0/7; `on_load` stamps (A3-12) 0 storms in 16 runs, all converge (a3 4/4 storms). 3 Playwright regression tests fail on main. PR [reflex-dev/reflex#7505](https://github.com/reflex-dev/reflex/pull/7505).
190
201
191
-
### A3-12: `sync=True` LocalStorage written concurrently by several tabs (an on_load that stamps a synced var, a browser session restore) loops forever between the tabs (MEDIUM, pre-existing on 0.9.12, a2 and a3; pending verification)
202
+
### A3-12: `sync=True` LocalStorage written concurrently by several tabs (an on_load that stamps a synced var, a browser session restore) loops forever between the tabs (MEDIUM, pre-existing on 0.9.12, a2 and a3; CONFIRMED by independent verifier; fixed by the same PR reflex#7505)
192
203
- Item `a3_hydration` (inbox 2). `src/syncstamp` (`Stamp.last = rx.LocalStorage("", name="ss_last", sync=True)` set to a per-tab value in
per 5 s; the control `/same` (same value from every tab) stays quiet. The volume crashed the Playwright driver in 4/12 runs.
206
+
-**Verification (`verify_hydration`): CONFIRMED, pre-existing.** Also triggers with no user action: 4 quiet tabs on `/doc/<slug>` pages
207
+
whose `on_load` stamps a synced var, then one dev backend reload (a save, deploy or restart): every tab reconnects, re-runs on_load and
208
+
the loop never ends (a3, a2, 0.9.12 2/2 each); 6 restored background tabs 2/2 each; explorer `/stamp 6` 2/2 each; 20–65k frames per
209
+
5 s, tabs end on 2–3 values. Fixing the storage-sync path alone ends the loop but leaves a tab on a stale value unless it re-reads
210
+
localStorage when a sync answer disagrees — which #7505 does (0 storms, all converge in 16 runs).
195
211
196
212
### A3-13: #7493 re-sends client-storage vars and their dependent computed vars in a second boot delta, so storage-dependent computed vars run twice per page load (LOW, performance; same as 0.9.12, a2 ran them once)
197
213
- Item `a3_events_tp` (inbox 2). `events/src/bootdup` (core only): on each reload with a stored value a3 sends 4 deltas and evaluates every
Copy file name to clipboardExpand all lines: prerelease_testing/2026-10-07-a3/RELEASE_PLAN.md
+7-9Lines changed: 7 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,6 @@
1
1
# Release plan after the 0.10.0a3 re-verification (reflex / reflex-base 0.10.0a3 + reflex-enterprise 0.9.7a5)
2
2
3
-
**Status: DRAFT** — written at 2026-10-07 ~22:25 UTC after every exploration item finished; the independent verifications of
4
-
A3-06..A3-10 are in; A3-11/A3-12's is still running and may change the triage below. Rubric (testing skill): fix before release =
3
+
**Status: FINAL (pending #7505 CI/review)** — every exploration item and all four independent verifications (A3-01..A3-12) are in. Rubric (testing skill): fix before release =
5
4
confirmed regression vs the previous stable (0.9.12), security-relevant, significant user impact, or trivially small. Evidence:
6
5
[FINDINGS.md](./FINDINGS.md). Supersedes [../2026-10-07/RELEASE_PLAN.md](../2026-10-07/RELEASE_PLAN.md) for everything it re-checked.
7
6
@@ -23,13 +22,12 @@ No upgrade regression 0.9.12 → a3 or a2 → a3; F-002/F-003/F-004 stay fixed;
23
22
24
23
## Fix before release
25
24
26
-
-**A3-11** (MEDIUM, regression vs a2, NOT vs 0.9.12; verification running) — `sync=True` LocalStorage: #7493's boot echo writes back the
27
-
value a tab read at connect time, so changing the value in one tab while other tabs boot starts an endless cross-tab ping-pong (tens of
28
-
thousands of frames per 5 s, backend 50–70 % CPU, the user's last value lost). It does not meet the strict "regression vs the previous
29
-
stable" arm (0.9.12 storms 9/10), but #7493 reintroduced it in a release that had removed it, it burns server CPU indefinitely, and the
30
-
fix is local to the boot echo (do not re-send a client-storage value the browser itself just sent, or have the frontend skip an echo of
31
-
the value it sent at boot). **Maintainer decision:** fix in 0.10.0, or ship with 0.9.12 parity and file it with A3-12.
32
-
**Decided 10-07:** fix it — prototype on `claude/a3-11-storage-echo` (frontend only, also ends the A3-12 ping-pong): [reflex-dev/reflex#7505](https://github.com/reflex-dev/reflex/pull/7505).
25
+
-**A3-11 + A3-12** (MEDIUM; A3-11 regression vs a2, not vs 0.9.12; A3-12 pre-existing; both CONFIRMED by an independent verifier, A3-11
26
+
wider than first reported: one change with 3 tabs over a real network, e.g. a session restore, loops forever at ~72 % backend CPU and
27
+
can leave localStorage on the old value; A3-12 is set off by any dev save/deploy/restart with ≥4 tabs whose on_load stamps a synced
28
+
var). **Decided 10-07: fix in 0.10.0** — [reflex-dev/reflex#7505](https://github.com/reflex-dev/reflex/pull/7505) (frontend only: no
29
+
write-back of unchanged echoes, storage events send the stored value, synced vars resync when an echo crossed a newer value; 0 storms
30
+
and full convergence in 26 runs dev/prod; 3 Playwright regression tests). The verifier independently arrived at the same design.
33
31
-**A3-01** (LOW, regression vs a2; CONFIRMED) — trivially small arm: in `BaseStateMeta.__setattr__`, push the "kept default" undo entry
34
32
before `_keep_client_storage` / `_accepts_default` can raise (so a rejected `mock.patch.object` / pytest-mock patch round-trips), and
35
33
make `__delattr__` and the identity restore pop only an entry that the same patch pushed. The docs already promise the round trip.
0 commit comments