Tree: EditManager summary#12513
Conversation
|
|
||
| private localSessionId?: SessionId; | ||
|
|
||
| readonly computationName: string = "EditManager"; |
There was a problem hiding this comment.
Missing access modifier here
| super(); | ||
| } | ||
|
|
||
| public setSessionId(id: SessionId): void { |
There was a problem hiding this comment.
You could call this initSessionId to make it more implicit that it can only be set once, but it's totally fine as-is too.
| /** | ||
| * The storage key for the blob in the summary containing EditManager data | ||
| */ | ||
| const blobKey = "EditManagerBlob"; |
There was a problem hiding this comment.
There's nothing wrong with this as they are, but you don't have to worry about these keys colliding with other indexes since they get scoped to each index. So if you wanted to, you could just call them "Blob" and "String", for example.
| } | ||
| } | ||
|
|
||
| export function encodeSummary<TChange>( |
There was a problem hiding this comment.
Can we call this stringifySummary? Fluid summarizers use encode in a way that doesn't include stringifying. I don't know if it's a good naming convention but maybe we can avoid confusion by matching it.
| trackState?: boolean, | ||
| telemetryContext?: ITelemetryContext, | ||
| ): ISummaryTreeWithStats { | ||
| const builder = new SummaryTreeBuilder(); |
There was a problem hiding this comment.
This method can use the createSingleBlobSummary helper
| telemetryContext?: ITelemetryContext, | ||
| ): Promise<ISummaryTreeWithStats> { | ||
| const editDataBlobHandle = await this.editDataBlob.get(); | ||
| const builder = new SummaryTreeBuilder(); |
There was a problem hiding this comment.
This method can use the createSingleBlobSummary helper
| services: IChannelStorageService, | ||
| parse: SummaryElementParser, | ||
| ): Promise<void> { | ||
| const [hasString, hasBlob] = await Promise.all([ |
There was a problem hiding this comment.
I don't think it will really make a noticeable difference in practice, but I think if you did:
if (await services.contains(blobKey)) {
const handleBuffer = await services.readBlob(blobKey);
...
} else {
assert(await services.contains(stringKey), "EditManager data is required in summary");
schemaBuffer = await services.readBlob(stringKey);
}The flow is a little bit more minimal. Or maybe even:
...
} else {
schemaBuffer = await services.readBlob(stringKey).catch(() => undefined) ?? fail("EditManager data is required in summary"));
}|
|
||
| await provider.ensureSynchronized(); | ||
|
|
||
| // Stop the processing of incoming changes on tree2 so that it does not learn about the deletion of Z |
noencke
left a comment
There was a problem hiding this comment.
Summarization code looks good!
| validateTree(tree1, ["A", "B", "C"]); | ||
| validateTree(tree2, ["A", "B", "C"]); | ||
| validateTree(tree3, ["A", "B", "C"]); | ||
| // tree4 should only get the correct end state if was able to get the adequate | ||
| // EditManager state from the summary. Specifically, in order to correctly rebase the insert | ||
| // of B, tree4 needs to have a local copy of the edit that deleted Z, so it can | ||
| // rebase the insertion of B over that edit. | ||
| // Without that, it will interpret the insertion of B based on the current state, yielding | ||
| // the order ACB. | ||
| validateTree(tree4, ["A", "B", "C"]); |
There was a problem hiding this comment.
| validateTree(tree1, ["A", "B", "C"]); | |
| validateTree(tree2, ["A", "B", "C"]); | |
| validateTree(tree3, ["A", "B", "C"]); | |
| // tree4 should only get the correct end state if was able to get the adequate | |
| // EditManager state from the summary. Specifically, in order to correctly rebase the insert | |
| // of B, tree4 needs to have a local copy of the edit that deleted Z, so it can | |
| // rebase the insertion of B over that edit. | |
| // Without that, it will interpret the insertion of B based on the current state, yielding | |
| // the order ACB. | |
| validateTree(tree4, ["A", "B", "C"]); | |
| const expectedValues = ["A", "B", "C"]; | |
| validateTree(tree1, expectedValues); | |
| validateTree(tree2, expectedValues); | |
| validateTree(tree3, expectedValues); | |
| // tree4 should only get the correct end state if was able to get the adequate | |
| // EditManager state from the summary. Specifically, in order to correctly rebase the insert | |
| // of B, tree4 needs to have a local copy of the edit that deleted Z, so it can | |
| // rebase the insertion of B over that edit. | |
| // Without that, it will interpret the insertion of B based on the current state, yielding | |
| // the order ACB. | |
| validateTree(tree4, expectedValues); |
Changes
This PR adds
EditManagerdata to the set of data being summarized.Motivation
This is necessary in order to ensure that clients which load a document while it is being edited are able to interpret the edits correctly.
Implementation Notes
This PR does not add support for generating summaries from clients with local edits.
EditManagercould be updated to support this (see comments ineditManager.ts).The size of this additional summary data is currently unbounded because the
EditManagerdata is unbounded (grows for each edit). This is being addressed separately, and the fix will result in the additional summary data becoming bounded as well.