-
Notifications
You must be signed in to change notification settings - Fork 577
perf(cell): use DoublyLinkedList for pendingMessageIds with list node on metadata #27415
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 1 commit
e4f82d7
d12ef64
382f2d7
e131c9f
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3,7 +3,11 @@ | |||||
| * Licensed under the MIT License. | ||||||
| */ | ||||||
|
|
||||||
| import { assert, unreachableCase } from "@fluidframework/core-utils/internal"; | ||||||
| import { | ||||||
| DoublyLinkedList, | ||||||
| assert, | ||||||
| unreachableCase, | ||||||
| } from "@fluidframework/core-utils/internal"; | ||||||
| import type { | ||||||
| IChannelAttributes, | ||||||
| IFluidDataStoreRuntime, | ||||||
|
|
@@ -85,7 +89,7 @@ export class SharedCell<T = any> | |||||
| */ | ||||||
| private messageIdObserved: number = -1; | ||||||
|
|
||||||
| private readonly pendingMessageIds: number[] = []; | ||||||
| private readonly pendingMessageIds = new DoublyLinkedList<number>(); | ||||||
|
|
||||||
| private attribution: AttributionKey | undefined; | ||||||
|
|
||||||
|
|
@@ -264,9 +268,7 @@ export class SharedCell<T = any> | |||||
| 0x00c /* "messageId is incorrect from from the local client's ACK" */, | ||||||
| ); | ||||||
| assert( | ||||||
| // eslint-disable-next-line @typescript-eslint/prefer-optional-chain -- TODO: ADO#58518 Code owners should verify if this code change is safe and make it if so or update this comment otherwise | ||||||
| this.pendingMessageIds !== undefined && | ||||||
| this.pendingMessageIds[0] === cellOpMetadata.pendingMessageId, | ||||||
| this.pendingMessageIds.first?.data === cellOpMetadata.pendingMessageId, | ||||||
| 0x471 /* Unexpected pending message received */, | ||||||
| ); | ||||||
| this.pendingMessageIds.shift(); | ||||||
|
|
@@ -306,9 +308,10 @@ export class SharedCell<T = any> | |||||
| previousValue?: Serializable<T>, | ||||||
| ): ICellLocalOpMetadata { | ||||||
| const pendingMessageId = ++this.messageId; | ||||||
| this.pendingMessageIds.push(pendingMessageId); | ||||||
| const { first: pendingNode } = this.pendingMessageIds.push(pendingMessageId); | ||||||
|
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:
Suggested change
|
||||||
| const localMetadata: ICellLocalOpMetadata = { | ||||||
| pendingMessageId, | ||||||
| pendingNode, | ||||||
|
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: PR #12273 wired This is narrower than it might look: One Is there an existing stash → rehydrate → ACK regression elsewhere in the DDS test corpus that exercises |
||||||
| previousValue, | ||||||
| }; | ||||||
| return localMetadata; | ||||||
|
|
@@ -352,8 +355,8 @@ export class SharedCell<T = any> | |||||
| this.setCore(cellOpMetadata.previousValue as Serializable<T>); | ||||||
| } | ||||||
|
|
||||||
| const lastPendingMessageId = this.pendingMessageIds.pop(); | ||||||
| if (lastPendingMessageId !== cellOpMetadata.pendingMessageId) { | ||||||
| const lastPendingNode = this.pendingMessageIds.pop(); | ||||||
| if (lastPendingNode?.data !== cellOpMetadata.pendingMessageId) { | ||||||
| throw new Error("Rollback op does not match last pending"); | ||||||
| } | ||||||
| } else { | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| * Licensed under the MIT License. | ||
| */ | ||
|
|
||
| import type { ListNode } from "@fluidframework/core-utils/internal"; | ||
| import type { Serializable } from "@fluidframework/datastore-definitions/internal"; | ||
| import type { AttributionKey } from "@fluidframework/runtime-definitions/internal"; | ||
| import type { | ||
|
|
@@ -125,6 +126,13 @@ export interface ICellLocalOpMetadata<T = any> { | |
| */ | ||
| pendingMessageId: number; | ||
|
|
||
| /** | ||
| * The node in the pending message list corresponding to this operation (op). | ||
| * Holding a direct reference to the node enables O(1) removal from arbitrary | ||
| * positions in the pending list, which is required for future squash support. | ||
|
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: Containment check: Suggested fix: declare a private |
||
| */ | ||
| pendingNode: ListNode<number>; | ||
|
|
||
| /** | ||
| * The value of the {@link ISharedCell} prior to this operation (op). | ||
| */ | ||
|
|
||
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: The
messageId !== messageIdObservedwatermark predicate is a sound proxy for "any pending writes?" today because the lifecycle is strictly push-on-submit / shift-on-ACK / pop-on-rollback —messageIdadvances exactly whenpendingMessageIdsgrows, and the head moves in lockstep with ACKs.This PR's stated motivation (per the description) is to enable future O(1)
list.remove(node)for arbitrary-position drop in the squash follow-up. As soon as squash/arbitrary-removal lands and a mid-queue entry is dropped,messageId(the last-submitted id) keeps advancing whilemessageIdObserved(the last-ACKed id) lags, leaving the predicatetrueeven when no actual pending op remains — which would suppress remote-op application indefinitely.The cross-cutting invariant — that this guard must move to
!this.pendingMessageIds.emptywhen arbitrary-position removal lands — is easy to miss from inside a future squash PR that's focused onreSubmitSquashedplumbing. Two options:!this.pendingMessageIds.emptyin this PR. Behavioral no-op today, and you're already touching every other site that reads the queue.// TODO:at this predicate referencing the squash/arbitrary-removal follow-up, so the next maintainer can't silently miss the dependency.Either is fine; the goal is just to make the coupling visible at the predicate site.