-
Notifications
You must be signed in to change notification settings - Fork 576
perf(ordered-collection): back SnapshotableQueue with DoublyLinkedList #27421
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 all commits
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,18 +3,32 @@ | |
| * Licensed under the MIT License. | ||
| */ | ||
|
|
||
| import { assert } from "@fluidframework/core-utils/internal"; | ||
| import { assert, DoublyLinkedList } from "@fluidframework/core-utils/internal"; | ||
|
|
||
| export class SnapshotableArray<T> extends Array { | ||
| protected data: T[] = []; | ||
| /** | ||
| * Base class for a snapshotable, ordered collection. | ||
| * | ||
| * Note: the historical "Array" suffix predates the current `DoublyLinkedList` backing — the | ||
| * collection is no longer backed by (and does not extend) a JS Array. The name is retained to | ||
| * avoid a broader rename across consumers; iteration order is still the contract that callers | ||
| * (notably the snapshot path via {@link asArray}) rely on. | ||
| */ | ||
| export class SnapshotableArray<T> { | ||
| protected data: DoublyLinkedList<T> = new DoublyLinkedList<T>(); | ||
|
|
||
| public asArray(): T[] { | ||
| return this.data; | ||
| const result: T[] = []; | ||
| for (const node of this.data) { | ||
| result.push(node.data); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| public async loadFrom(from: T[]): Promise<void> { | ||
| assert(this.data.length === 0, 0x06b /* "Loading snapshot into a non-empty collection" */); | ||
| this.data = from; | ||
| for (const value of from) { | ||
| this.data.push(value); | ||
| } | ||
|
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: There is strong transitive coverage: Suggest one ~10-line unit test in this PR: |
||
| } | ||
|
|
||
| public size(): number { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| /*! | ||
| * Copyright (c) Microsoft Corporation and contributors. All rights reserved. | ||
| * Licensed under the MIT License. | ||
| */ | ||
|
|
||
| import { strict as assert } from "node:assert"; | ||
|
|
||
| import type { IOrderedCollection } from "../interfaces.js"; | ||
| import { SnapshotableArray } from "../snapshotableArray.js"; | ||
|
|
||
| /** | ||
| * Minimal queue subclass mirroring the in-tree `SnapshotableQueue` so we can exercise | ||
| * removal against the protected `data` list without depending on the full | ||
| * ConsensusQueue runtime plumbing. | ||
| */ | ||
| class TestQueue<T> extends SnapshotableArray<T> implements IOrderedCollection<T> { | ||
| public add(value: T): void { | ||
| this.data.push(value); | ||
| } | ||
| public remove(): T { | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| return this.data.shift()!.data; | ||
| } | ||
| } | ||
|
|
||
| describe("SnapshotableArray", () => { | ||
| it("asArray() reflects iteration order after loadFrom + remove (snapshot contract)", async () => { | ||
| const queue = new TestQueue<string>(); | ||
| await queue.loadFrom(["a", "b", "c"]); | ||
| queue.remove(); | ||
| assert.deepEqual(queue.asArray(), ["b", "c"]); | ||
| assert.equal(queue.size(), 2); | ||
| }); | ||
| }); |
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 class name
SnapshotableArraywas chosen in #1395 (vladsud, 2020-03-05) specifically because it was anArraysubclass. This PR removes theextends Array— the PR description acknowledges it ("SnapshotableArrayno longer extendsArray— it is now a plain class") but the name stays. Both blind solution-space proposers independently expected the class to still be array-backed, so a future reader is likely to hit the same surprise.The symbol is internal to the package (not exported via api-report; only consumer is
SnapshotableQueueinconsensusQueue.ts), so renaming in-PR is cheap — e.g.SnapshotableCollectionorSnapshotableList. If the rename is deferred, a one-line doc comment noting the legacy name predates the linked-list backing would save the next reader from re-deriving "why is this called Array?".