-
Notifications
You must be signed in to change notification settings - Fork 577
merge-tree: split reset arms + previousProps accessor backed by WeakMap #27414
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
0e7f5ab
8d34c55
5c914f9
0c08285
7ac273f
c976b85
3138872
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1426,7 +1426,7 @@ export class MergeTree { | |||||||||||||
| refSeq: this.collabWindow.currentSeq, | ||||||||||||||
| }; | ||||||||||||||
| if (previousProps) { | ||||||||||||||
| _segmentGroup.previousProps = []; | ||||||||||||||
| _segmentGroup.previousProps = new WeakMap(); | ||||||||||||||
| } | ||||||||||||||
| this.pendingSegments.push(_segmentGroup); | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -1438,7 +1438,7 @@ export class MergeTree { | |||||||||||||
| throw new Error("All segments in group should have previousProps or none"); | ||||||||||||||
| } | ||||||||||||||
| if (previousProps) { | ||||||||||||||
| _segmentGroup.previousProps!.push(previousProps); | ||||||||||||||
| _segmentGroup.previousProps!.set(segment, previousProps); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const segmentGroups = (segment.segmentGroups ??= new SegmentGroupCollection(segment)); | ||||||||||||||
|
|
@@ -2449,7 +2449,6 @@ export class MergeTree { | |||||||||||||
| ) { | ||||||||||||||
| throw new Error("Rollback op doesn't match last edit"); | ||||||||||||||
| } | ||||||||||||||
| let i = 0; | ||||||||||||||
| for (const segment of pendingSegmentGroup.segments) { | ||||||||||||||
| const segmentSegmentGroup = segment?.segmentGroups?.pop(); | ||||||||||||||
| assert( | ||||||||||||||
|
|
@@ -2476,7 +2475,11 @@ export class MergeTree { | |||||||||||||
| { op: removeOp, rollback: true }, | ||||||||||||||
| ); | ||||||||||||||
| } /* op.type === MergeTreeDeltaType.ANNOTATE */ else { | ||||||||||||||
| const props = pendingSegmentGroup.previousProps![i]; | ||||||||||||||
| const props = pendingSegmentGroup.previousProps!.get(segment); | ||||||||||||||
| assert( | ||||||||||||||
| props !== undefined, | ||||||||||||||
| "Segment missing previousProps entry on annotate rollback", | ||||||||||||||
| ); | ||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deep Review: This new The repo ships Run
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| // If the segment has been removed by a concurrent operation, we can't use | ||||||||||||||
| // position-based annotateRange because findRollbackPosition returns a position | ||||||||||||||
|
|
@@ -2505,7 +2508,6 @@ export class MergeTree { | |||||||||||||
| { op: annotateOp, rollback: true }, | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
| i++; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } else { | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| import { DoublyLinkedList, walkList } from "@fluidframework/core-utils/internal"; | ||
|
|
||
| import type { SegmentGroup, ISegmentLeaf } from "./mergeTreeNodes.js"; | ||
| import type { PropertySet } from "./properties.js"; | ||
|
|
||
| export class SegmentGroupCollection { | ||
| private readonly segmentGroups: DoublyLinkedList<SegmentGroup>; | ||
|
|
@@ -48,13 +49,21 @@ export class SegmentGroupCollection { | |
| walkList(this.segmentGroups, (sg) => segmentGroups.enqueueOnCopy(sg.data, this.segment)); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the previousProps entry paired with this collection's segment within the given | ||
| * segmentGroup, or undefined if the group has no previousProps or no entry exists for the segment. | ||
| */ | ||
| public previousPropsForSegment(segmentGroup: SegmentGroup): PropertySet | undefined { | ||
| return segmentGroup.previousProps?.get(this.segment); | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deep Review: The PR description states the accessor "encapsulates the … lookup" and that callers become a clean one-liner, but the two natural call sites that landed in the same PR continue to access Blast radius is narrow — Either (a) migrate |
||
|
|
||
| private enqueueOnCopy(segmentGroup: SegmentGroup, sourceSegment: ISegmentLeaf): void { | ||
| this.enqueue(segmentGroup); | ||
| if (segmentGroup.previousProps) { | ||
| // duplicate the previousProps for this segment | ||
| const index = segmentGroup.segments.indexOf(sourceSegment); | ||
| if (index !== -1) { | ||
| segmentGroup.previousProps.push(segmentGroup.previousProps[index]); | ||
| // duplicate the previousProps entry for the destination segment, keyed off the source's entry | ||
| const sourceProps = segmentGroup.previousProps.get(sourceSegment); | ||
| if (sourceProps !== undefined) { | ||
| segmentGroup.previousProps.set(this.segment, sourceProps); | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deep Review: The new Distinct from the reissue-path concern on Add a |
||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deep Review: This hunk replaces
previousProps: segmentGroup.previousProps?.slice(0)with a freshly constructedWeakMapcarrying only the current segment's entry — the PR description identifies the old.slice(0)as a latent inconsistency (defensive copy of the props array, but the surrounding loop only ever wrote a single segment to the new group, so the array shape was already mispaired post-copy). The PR ships with no test changes.Closest existing coverage (
resetPendingSegmentsToOp.spec.ts:189-211,client.rollback.spec.ts:206-245and:658-688) doesn't inspect per-segmentpreviousPropsentries. Prior bugs in this exact reset/rebase path were fuzz-discovered, not unit-discovered (#11946, #22069), so existing unit tests are an unreliable safety net for semantic regressions here.Add a focused regression in
resetPendingSegmentsToOp.spec.ts(orclient.rollback.spec.ts) that constructs a pending annotate group with multiple segments, triggersresetPendingDeltaToOpsreissue, and asserts each new per-segmentSegmentGrouphas apreviousPropsWeakMap containing exactly the entry for its own segment.