perf(ui): drop dead-code parent_state clone in apply_delta/update_room_state (#246)#312
Merged
Conversation
…m_state (#246) Follow-up to PR #311. Ivvor reports memory spikes on cold open dropped from the 16GB-crashing pre-#311 level to 3-4GB-but-stable. Still big — this PR chips at the next per-event allocation source. `apply_delta_inner` and `update_room_state_inner` previously cloned the full `room_data.room_state` to pass as the `parent_state` argument to `apply_delta` / `merge`. That argument is dead-code at the macro level: - Every field's `summarize` / `delta` impl in `common/src/room_state/` takes `_parent_state` (underscore-prefixed = unused). - `freenet-scaffold-macro` 0.2.2 generates an `apply_delta` that ignores its outer `_parent_state` arg entirely; it computes a fresh `let self_clone = self.clone();` *per field* and uses that as the per-field parent. The caller-side full-state clone never reaches code that reads it. Each ingestion still does the macro-internal per-field clones (those ARE needed — fields like `MembersV1::apply_delta` read sibling fields via parent_state), but the redundant outer clone is gone. Saves one full-state clone per network delta / state update on the open-time hot path. Test coverage - New `merge_with_default_sentinel_parent_matches_merge_with_self_clone_parent` empirically pins the invariant: merging with `&default()` as parent_state produces byte-identical post-merge state to merging with `&self.clone()`. If a future refactor starts reading the outer `parent_state` (or wires it through to a field's summarize/delta), this test fails loudly and the optimization needs revisiting. - Full `cargo test -p river-ui --bins`: 242/242 pass (was 241; one new test). - WASM check green. Scope - UI-only change. Single file. No delegate/contract WASM modified, no migration entry needed. No riverctl republish needed. Honest sizing - This is a ~10% reduction per merge (the macro-internal per-field clones still dominate). It won't bring the 3-4GB residual spike down to MB; that would require fixing freenet-scaffold-macro to hoist its per-field self.clone() outside the field loop, which is a separate- crate PR. Worth landing this win on its own merits and keeping the bigger refactor as a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ro forwarding Round-1 review of #312 (skeptical reviewer) flagged the `merge_with_default_sentinel_parent_matches_merge_with_self_clone_parent` test was tautological on near-default data — `state_a` only differed from `ChatRoomStateV1::default()` in `recent_messages.messages`, so a hypothetical macro regression that forwards outer `_parent_state` to field-level `apply_delta` would still see equivalent values across the two paths and the test would silently pass. Strengthen `state_a` to genuinely diverge from `default()` in fields real-world `apply_delta` impls actually read: - non-default `Configuration` (`max_members = 3`, `max_recent_messages = 5` vs the default 200 / 100) — `MembersV1::apply_delta`, `MessagesV1::apply_delta`, `MemberInfoV1::apply_delta` all read these. - a non-owner `AuthorizedUserBan` — `MembersV1::apply_delta` reads `parent_state.bans` for ban-sweep. Also tightened the doc comment on the `merge` call site at the top of `update_room_state_inner` to spell out that safety on the merge path depends on BOTH the macro's `apply_delta` ignoring outer `_parent_state` AND every field-level `summarize`/`delta` impl declaring it unused (via `_parent_state`). The previous comment just deferred to the `apply_delta_inner` rationale, which only proves one of the two facts. Tests: 242/242 pass. The strengthened test still passes (substitution is correct today), and it would now FAIL if any of: - a future `freenet-scaffold-macro` revision starts forwarding outer `_parent_state` to field `apply_delta` calls, - any field's `summarize`/`delta` starts reading its `parent_state` arg, - the call sites get switched back to `&self.clone()` for parent_state (the test exercises BOTH paths against the same expected end-state). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sanity
commented
May 25, 2026
Contributor
Author
sanity
left a comment
There was a problem hiding this comment.
PR Review (Light tier — appropriate for a 1-file, +83/-5 perf change)
- Tier: Light — single-file UI-only perf change. Codex external pass + skeptical-reviewer adversarial read.
- CI: in progress on
c556b795at review time. Perno-merge-with-red-ci, must be green on the current HEAD before merge.
Findings
| Reviewer | Result |
|---|---|
| Codex external | Clean, no findings. Verified the substitution is safe under current macro + field impls. |
| Skeptical | Confirmed the substitution is correct. One Medium finding: the round-1 regression test was tautological on near-default data — state_a only diverged from default() in recent_messages.messages, so a hypothetical macro regression that forwards outer _parent_state to a field's apply_delta would still see equivalent values across both paths and the test would silently pass. |
Round 2 (c556b795) — addresses the Medium finding
Round-2 commit is purely test + comment, zero production-code changes (verified via diff-of-the-diff). Per the skill's "trivial mechanical findings" allowance, a Light re-check is sufficient — that's what this comment is.
- Test now sets
state_awith a non-defaultConfiguration(max_members = 3vs default 200,max_recent_messages = 5vs default 100) AND a non-ownerAuthorizedUserBan. Those are the exact fieldsMembersV1::apply_delta,MessagesV1::apply_delta, andMemberInfoV1::apply_deltaread fromparent_state. The test now genuinely discriminates the regression class it claims to guard. - Comment on the
mergecall site spelled out that safety on the merge path depends on BOTH the macro'sapply_deltaignoring outer_parent_stateAND every field-levelsummarize/deltadeclaring it unused — the previous comment deferred to theapply_delta_innerrationale, which only covered one of those two facts.
Non-blocking observations (skeptical's other points)
- Scope: similar
&room_state.clone()patterns exist incli/src/api.rs:1656, 1750, 1902, 1961and a few other UI sites. Deliberately deferred — this PR targets the open-time hot path. Worth a follow-up if the CLI's allocation profile turns out to matter. - Macro version pinning: the optimization's safety is tied to
freenet-scaffold-macro0.2.2. The Cargo.toml currently uses caret semver"0.2.2". The regression test catches any future macro change that would break the substitution, so a hard=0.2.2pin isn't required — but worth keeping the test green-on-CI as the guard. ChatRoomStateV1::default()cost: trivial — constructs 9 default sub-field types, all small. Strictly cheaper than cloning a realroom_statethat's grown beyond defaults (which is the cold-open scenario).
Verdict
Ready to merge pending CI green on c556b795. Substitution provably safe under current code. Round-1 finding addressed. Honest sizing: ~10% per-merge reduction (macro-internal per-field clones still dominate); won't bring 3-4 GB residual spike down to MB but every win compounds.
[AI-assisted - Claude]
sanity
added a commit
that referenced
this pull request
May 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #311 / #246. Ivvor reports memory spikes on cold open dropped from the 16GB-crashing pre-#311 level to 3-4GB-but-stable. Real user-visible win on the "doesn't crash my iPad anymore" axis, but 3-4GB is still big — this PR chips at the next per-event allocation source.
What changes
apply_delta_innerandupdate_room_state_innerinroom_synchronizer.rseach cloned the fullroom_data.room_stateto pass as theparent_stateargument toapply_delta/merge. That argument is dead-code at the macro level:summarize/deltaimpl incommon/src/room_state/takes_parent_state(underscore-prefixed = unused, verified acrossban.rs,configuration.rs,direct_messages.rs,member_info.rs,member.rs,message.rs,secret.rs,upgrade.rs,version.rs).freenet-scaffold-macro0.2.2 generates anapply_deltathat ignores its outer_parent_statearg entirely. It computes a freshlet self_clone = self.clone();per field and uses that as the per-field parent.So the caller-side full-state clone never reaches code that reads it. Substituting
&ChatRoomStateV1::default()is provably equivalent. Saves one full-state clone per network delta / state update.Test coverage
New regression test
merge_with_default_sentinel_parent_matches_merge_with_self_clone_parentempirically pins the invariant — it runsmergeboth ways and asserts byte-equality of the post-merge state. If a future refactor starts reading the outerparent_state(in the macro, or in a field'ssummarize/delta), this test fails and the optimization needs revisiting.cargo test -p river-ui --bins: 242/242 pass. WASM check green.cargo fmt --checkclean.Honest sizing — what this does NOT fix
This is a ~10% per-merge reduction. The dominant allocation source remains the macro-internal
let self_clone = self.clone();per field insideapply_delta(~9× full-state clones per merge for ChatRoomStateV1). Fixing that would mean modifyingfreenet-scaffold-macroto hoistself_cloneoutside the field loop — single shared clone reused across all fields — which is a separate-crate PR.This PR is worth landing on its own merits (provably safe, focused diff, real-but-bounded reduction), with the bigger macro refactor tracked as a follow-up if the residual spike still bothers users after this lands.
Deploy notes
UI-only change. Single file:
ui/src/components/app/freenet_api/room_synchronizer.rs. No delegate/contract WASM modified, no migration entry needed, no riverctl republish needed.🤖 Generated with Claude Code