@fluidframework/container-runtime 2.1.0-276985 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +71 -18
- package/api-extractor/api-extractor.current.json +5 -0
- package/api-extractor/api-extractor.legacy.json +1 -1
- package/api-extractor.json +1 -1
- package/api-report/container-runtime.legacy.public.api.md +9 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/blobManager/blobManager.d.ts +10 -0
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +19 -0
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/channelCollection.d.ts +1 -1
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +40 -8
- package/dist/channelCollection.js.map +1 -1
- package/dist/containerRuntime.d.ts +14 -5
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +151 -99
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +4 -0
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +9 -3
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +1 -1
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +14 -8
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +4 -2
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcHelpers.d.ts.map +1 -1
- package/dist/gc/gcHelpers.js +12 -0
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts +3 -2
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +6 -6
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/legacy.d.ts +1 -1
- package/dist/metadata.d.ts +7 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +6 -0
- package/dist/metadata.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +8 -1
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +37 -16
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/definitions.d.ts +1 -1
- package/dist/opLifecycle/definitions.d.ts.map +1 -1
- package/dist/opLifecycle/definitions.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +2 -2
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js +3 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opCompressor.js +12 -8
- package/dist/opLifecycle/opCompressor.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +14 -11
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
- package/dist/opLifecycle/opSplitter.js +11 -6
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +22 -6
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +43 -21
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +10 -8
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +39 -15
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +37 -13
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +95 -45
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/public.d.ts +1 -1
- package/dist/scheduleManager.js +4 -0
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeUtils.js +2 -0
- package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/dist/summary/summaryFormat.d.ts.map +1 -1
- package/dist/summary/summaryFormat.js +4 -1
- package/dist/summary/summaryFormat.js.map +1 -1
- package/internal.d.ts +1 -1
- package/legacy.d.ts +1 -1
- package/lib/blobManager/blobManager.d.ts +10 -0
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +19 -0
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/channelCollection.d.ts +1 -1
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +40 -8
- package/lib/channelCollection.js.map +1 -1
- package/lib/containerRuntime.d.ts +14 -5
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +152 -100
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +4 -0
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +10 -4
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +1 -1
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +14 -8
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +4 -2
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcHelpers.d.ts.map +1 -1
- package/lib/gc/gcHelpers.js +12 -0
- package/lib/gc/gcHelpers.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts +3 -2
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +6 -6
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/legacy.d.ts +1 -1
- package/lib/metadata.d.ts +7 -1
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js +4 -1
- package/lib/metadata.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +8 -1
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +35 -15
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/definitions.d.ts +1 -1
- package/lib/opLifecycle/definitions.d.ts.map +1 -1
- package/lib/opLifecycle/definitions.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +2 -2
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js +2 -2
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opCompressor.js +12 -8
- package/lib/opLifecycle/opCompressor.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +14 -11
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
- package/lib/opLifecycle/opSplitter.js +11 -6
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +22 -6
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +44 -22
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +10 -8
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +37 -14
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +37 -13
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +95 -45
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/public.d.ts +1 -1
- package/lib/scheduleManager.js +4 -0
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeUtils.js +2 -0
- package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
- package/lib/summary/summaryFormat.d.ts.map +1 -1
- package/lib/summary/summaryFormat.js +4 -1
- package/lib/summary/summaryFormat.js.map +1 -1
- package/package.json +46 -31
- package/src/blobManager/blobManager.ts +19 -0
- package/src/channelCollection.ts +48 -11
- package/src/containerRuntime.ts +203 -133
- package/src/dataStoreContext.ts +22 -4
- package/src/gc/garbageCollection.ts +15 -10
- package/src/gc/gcDefinitions.ts +7 -2
- package/src/gc/gcHelpers.ts +18 -6
- package/src/gc/gcTelemetry.ts +20 -8
- package/src/metadata.ts +11 -1
- package/src/opLifecycle/README.md +0 -8
- package/src/opLifecycle/batchManager.ts +49 -16
- package/src/opLifecycle/definitions.ts +1 -1
- package/src/opLifecycle/index.ts +13 -2
- package/src/opLifecycle/opCompressor.ts +12 -8
- package/src/opLifecycle/opGroupingManager.ts +14 -11
- package/src/opLifecycle/opSplitter.ts +10 -6
- package/src/opLifecycle/outbox.ts +64 -26
- package/src/opLifecycle/remoteMessageProcessor.ts +56 -17
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +173 -74
- package/src/scheduleManager.ts +6 -2
- package/src/summary/README.md +81 -0
- package/src/summary/summarizerNode/summarizerNodeUtils.ts +3 -1
- package/src/summary/summaryFormat.ts +3 -1
- package/src/summary/summaryFormats.md +69 -8
- package/tsconfig.json +0 -1
- package/src/summary/images/appTree.png +0 -0
- package/src/summary/images/protocolAndAppTree.png +0 -0
- package/src/summary/images/summaryTree.png +0 -0
|
@@ -14,15 +14,18 @@ import {
|
|
|
14
14
|
extractSafePropertiesFromMessage,
|
|
15
15
|
} from "@fluidframework/telemetry-utils/internal";
|
|
16
16
|
import Deque from "double-ended-queue";
|
|
17
|
+
import { v4 as uuid } from "uuid";
|
|
17
18
|
|
|
18
|
-
import { InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
|
|
19
|
-
import { IBatchMetadata } from "./metadata.js";
|
|
20
|
-
import
|
|
19
|
+
import { type InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
|
|
20
|
+
import { asBatchMetadata, IBatchMetadata } from "./metadata.js";
|
|
21
|
+
import { BatchId, BatchMessage, generateBatchId } from "./opLifecycle/index.js";
|
|
21
22
|
import { pkgVersion } from "./packageVersion.js";
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* This represents a message that has been submitted and is added to the pending queue when `submit` is called on the
|
|
25
26
|
* ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.
|
|
27
|
+
*
|
|
28
|
+
* @remarks This is the current serialization format for pending local state when a Container is serialized.
|
|
26
29
|
*/
|
|
27
30
|
export interface IPendingMessage {
|
|
28
31
|
type: "message";
|
|
@@ -31,9 +34,30 @@ export interface IPendingMessage {
|
|
|
31
34
|
localOpMetadata: unknown;
|
|
32
35
|
opMetadata: Record<string, unknown> | undefined;
|
|
33
36
|
sequenceNumber?: number;
|
|
34
|
-
|
|
37
|
+
/** Info needed to compute the batchId on reconnect */
|
|
38
|
+
batchIdContext: {
|
|
39
|
+
/** The Batch's original clientId, from when it was first flushed to be submitted */
|
|
40
|
+
clientId: string;
|
|
41
|
+
/**
|
|
42
|
+
* The Batch's original clientSequenceNumber, from when it was first flushed to be submitted
|
|
43
|
+
* @remarks A negative value means it was not yet submitted when queued here (e.g. disconnected right before flush fired)
|
|
44
|
+
*/
|
|
45
|
+
batchStartCsn: number;
|
|
46
|
+
};
|
|
35
47
|
}
|
|
36
48
|
|
|
49
|
+
type Patch<T, U> = U & Omit<T, keyof U>;
|
|
50
|
+
|
|
51
|
+
/** First version of the type (pre-dates batchIdContext) */
|
|
52
|
+
type IPendingMessageV0 = Patch<IPendingMessage, { batchIdContext?: undefined }>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Union of all supported schemas for when applying stashed ops
|
|
56
|
+
*
|
|
57
|
+
* @remarks When the format changes, this type should update to reflect all possible schemas.
|
|
58
|
+
*/
|
|
59
|
+
type IPendingMessageFromStash = IPendingMessageV0 | IPendingMessage;
|
|
60
|
+
|
|
37
61
|
export interface IPendingLocalState {
|
|
38
62
|
/**
|
|
39
63
|
* list of pending states, including ops and batch information
|
|
@@ -41,19 +65,18 @@ export interface IPendingLocalState {
|
|
|
41
65
|
pendingStates: IPendingMessage[];
|
|
42
66
|
}
|
|
43
67
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
68
|
+
/** Info needed to replay/resubmit a pending message */
|
|
69
|
+
export type PendingMessageResubmitData = Pick<
|
|
70
|
+
IPendingMessage,
|
|
71
|
+
"content" | "localOpMetadata" | "opMetadata"
|
|
72
|
+
>;
|
|
49
73
|
|
|
50
74
|
export interface IRuntimeStateHandler {
|
|
51
75
|
connected(): boolean;
|
|
52
76
|
clientId(): string | undefined;
|
|
53
77
|
close(error?: ICriticalContainerError): void;
|
|
54
78
|
applyStashedOp(content: string): Promise<unknown>;
|
|
55
|
-
|
|
56
|
-
reSubmitBatch(batch: IPendingBatchMessage[]): void;
|
|
79
|
+
reSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId): void;
|
|
57
80
|
isActiveConnection: () => boolean;
|
|
58
81
|
isAttached: () => boolean;
|
|
59
82
|
}
|
|
@@ -95,15 +118,19 @@ function withoutLocalOpMetadata(message: IPendingMessage): IPendingMessage {
|
|
|
95
118
|
* It verifies that all the ops are acked, are received in the right order and batch information is correct.
|
|
96
119
|
*/
|
|
97
120
|
export class PendingStateManager implements IDisposable {
|
|
121
|
+
/** Messages that will need to be resubmitted if not ack'd before the next reconnection */
|
|
98
122
|
private readonly pendingMessages = new Deque<IPendingMessage>();
|
|
99
|
-
|
|
100
|
-
private readonly initialMessages = new Deque<
|
|
123
|
+
/** Messages stashed from a previous container, now being rehydrated. Need to be resubmitted. */
|
|
124
|
+
private readonly initialMessages = new Deque<IPendingMessageFromStash>();
|
|
101
125
|
|
|
102
126
|
/**
|
|
103
127
|
* Sequenced local ops that are saved when stashing since pending ops may depend on them
|
|
104
128
|
*/
|
|
105
129
|
private savedOps: IPendingMessage[] = [];
|
|
106
130
|
|
|
131
|
+
/** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */
|
|
132
|
+
private negativeCounter: number = -1;
|
|
133
|
+
|
|
107
134
|
private readonly disposeOnce = new Lazy<void>(() => {
|
|
108
135
|
this.initialMessages.clear();
|
|
109
136
|
this.pendingMessages.clear();
|
|
@@ -116,7 +143,8 @@ export class PendingStateManager implements IDisposable {
|
|
|
116
143
|
// the correct batch metadata.
|
|
117
144
|
private pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;
|
|
118
145
|
|
|
119
|
-
|
|
146
|
+
/** Used to ensure we don't replay ops on the same connection twice */
|
|
147
|
+
private clientIdFromLastReplay: string | undefined;
|
|
120
148
|
|
|
121
149
|
/**
|
|
122
150
|
* The pending messages count. Includes `pendingMessages` and `initialMessages` to keep in sync with
|
|
@@ -176,11 +204,11 @@ export class PendingStateManager implements IDisposable {
|
|
|
176
204
|
|
|
177
205
|
constructor(
|
|
178
206
|
private readonly stateHandler: IRuntimeStateHandler,
|
|
179
|
-
|
|
180
|
-
private readonly logger: ITelemetryLoggerExt
|
|
207
|
+
stashedLocalState: IPendingLocalState | undefined,
|
|
208
|
+
private readonly logger: ITelemetryLoggerExt,
|
|
181
209
|
) {
|
|
182
|
-
if (
|
|
183
|
-
this.initialMessages.push(...
|
|
210
|
+
if (stashedLocalState?.pendingStates) {
|
|
211
|
+
this.initialMessages.push(...stashedLocalState.pendingStates);
|
|
184
212
|
}
|
|
185
213
|
}
|
|
186
214
|
|
|
@@ -197,6 +225,16 @@ export class PendingStateManager implements IDisposable {
|
|
|
197
225
|
* or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)
|
|
198
226
|
*/
|
|
199
227
|
public onFlushBatch(batch: BatchMessage[], clientSequenceNumber: number | undefined) {
|
|
228
|
+
// If we're connected this is the client of the current connection,
|
|
229
|
+
// otherwise it's the clientId that just disconnected
|
|
230
|
+
// It's only undefined if we've NEVER connected. This is a tight corner case and we can
|
|
231
|
+
// simply make up a unique ID in this case.
|
|
232
|
+
const clientId = this.stateHandler.clientId() ?? uuid();
|
|
233
|
+
|
|
234
|
+
// If the batch was not yet sent, we need to assign a unique batchStartCsn
|
|
235
|
+
// Use a negative number to distinguish these from real CSNs
|
|
236
|
+
const batchStartCsn = clientSequenceNumber ?? this.negativeCounter--;
|
|
237
|
+
|
|
200
238
|
for (const message of batch) {
|
|
201
239
|
const {
|
|
202
240
|
contents: content = "",
|
|
@@ -210,7 +248,8 @@ export class PendingStateManager implements IDisposable {
|
|
|
210
248
|
content,
|
|
211
249
|
localOpMetadata,
|
|
212
250
|
opMetadata,
|
|
213
|
-
|
|
251
|
+
// Note: We only need this on the first message.
|
|
252
|
+
batchIdContext: { clientId, batchStartCsn },
|
|
214
253
|
};
|
|
215
254
|
this.pendingMessages.push(pendingMessage);
|
|
216
255
|
}
|
|
@@ -245,6 +284,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
245
284
|
} else {
|
|
246
285
|
nextMessage.localOpMetadata = localOpMetadata;
|
|
247
286
|
// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect
|
|
287
|
+
patchBatchIdContext(nextMessage); // Back compat
|
|
248
288
|
this.pendingMessages.push(nextMessage);
|
|
249
289
|
}
|
|
250
290
|
} catch (error) {
|
|
@@ -253,6 +293,25 @@ export class PendingStateManager implements IDisposable {
|
|
|
253
293
|
}
|
|
254
294
|
}
|
|
255
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Processes the incoming batch from the server. It verifies that messages are received in the right order and
|
|
298
|
+
* that the batch information is correct.
|
|
299
|
+
* @param batch - The batch that is being processed.
|
|
300
|
+
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch
|
|
301
|
+
*/
|
|
302
|
+
public processPendingLocalBatch(
|
|
303
|
+
batch: InboundSequencedContainerRuntimeMessage[],
|
|
304
|
+
batchStartCsn: number,
|
|
305
|
+
): {
|
|
306
|
+
message: InboundSequencedContainerRuntimeMessage;
|
|
307
|
+
localOpMetadata: unknown;
|
|
308
|
+
}[] {
|
|
309
|
+
return batch.map((message) => ({
|
|
310
|
+
message,
|
|
311
|
+
localOpMetadata: this.processPendingLocalMessage(message, batchStartCsn),
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
|
|
256
315
|
/**
|
|
257
316
|
* Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that
|
|
258
317
|
* the batch information was preserved for batch messages.
|
|
@@ -260,37 +319,25 @@ export class PendingStateManager implements IDisposable {
|
|
|
260
319
|
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
|
|
261
320
|
* (not to be confused with message.clientSequenceNumber - the overwritten value in case of grouped batching)
|
|
262
321
|
*/
|
|
263
|
-
|
|
322
|
+
private processPendingLocalMessage(
|
|
264
323
|
message: InboundSequencedContainerRuntimeMessage,
|
|
265
324
|
batchStartCsn: number,
|
|
266
325
|
): unknown {
|
|
267
|
-
// Pre-processing part - This may be the start of a batch.
|
|
268
|
-
this.maybeProcessBatchBegin(message);
|
|
269
326
|
// Get the next message from the pending queue. Verify a message exists.
|
|
270
327
|
const pendingMessage = this.pendingMessages.peekFront();
|
|
271
328
|
assert(
|
|
272
329
|
pendingMessage !== undefined,
|
|
273
330
|
0x169 /* "No pending message found for this remote message" */,
|
|
274
331
|
);
|
|
332
|
+
|
|
333
|
+
// This may be the start of a batch.
|
|
334
|
+
this.maybeProcessBatchBegin(message, batchStartCsn, pendingMessage);
|
|
335
|
+
|
|
275
336
|
pendingMessage.sequenceNumber = message.sequenceNumber;
|
|
276
337
|
this.savedOps.push(withoutLocalOpMetadata(pendingMessage));
|
|
277
338
|
|
|
278
339
|
this.pendingMessages.shift();
|
|
279
340
|
|
|
280
|
-
if (pendingMessage.batchStartCsn !== batchStartCsn) {
|
|
281
|
-
this.logger?.sendErrorEvent({
|
|
282
|
-
eventName: "BatchClientSequenceNumberMismatch",
|
|
283
|
-
details: {
|
|
284
|
-
processingBatch: !!this.pendingBatchBeginMessage,
|
|
285
|
-
pendingBatchCsn: pendingMessage.batchStartCsn,
|
|
286
|
-
batchStartCsn,
|
|
287
|
-
messageBatchMetadata: (message.metadata as any)?.batch,
|
|
288
|
-
pendingMessageBatchMetadata: (pendingMessage.opMetadata as any)?.batch,
|
|
289
|
-
},
|
|
290
|
-
messageDetails: extractSafePropertiesFromMessage(message),
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
|
|
294
341
|
const messageContent = buildPendingMessageContent(message);
|
|
295
342
|
|
|
296
343
|
// Stringified content should match
|
|
@@ -317,8 +364,31 @@ export class PendingStateManager implements IDisposable {
|
|
|
317
364
|
/**
|
|
318
365
|
* This message could be the first message in batch. If so, set batch state marking the beginning of a batch.
|
|
319
366
|
* @param message - The message that is being processed.
|
|
367
|
+
* @param batchStartCsn - The clientSequenceNumber of the start of this message's batch (assigned during submit)
|
|
368
|
+
* @param pendingMessage - The corresponding pendingMessage.
|
|
320
369
|
*/
|
|
321
|
-
private maybeProcessBatchBegin(
|
|
370
|
+
private maybeProcessBatchBegin(
|
|
371
|
+
message: ISequencedDocumentMessage,
|
|
372
|
+
batchStartCsn: number,
|
|
373
|
+
pendingMessage: IPendingMessage,
|
|
374
|
+
) {
|
|
375
|
+
if (!this.isProcessingBatch) {
|
|
376
|
+
// Expecting the start of a batch (maybe single-message).
|
|
377
|
+
if (pendingMessage.batchIdContext.batchStartCsn !== batchStartCsn) {
|
|
378
|
+
this.logger?.sendErrorEvent({
|
|
379
|
+
eventName: "BatchClientSequenceNumberMismatch",
|
|
380
|
+
details: {
|
|
381
|
+
processingBatch: !!this.pendingBatchBeginMessage,
|
|
382
|
+
pendingBatchCsn: pendingMessage.batchIdContext.batchStartCsn,
|
|
383
|
+
batchStartCsn,
|
|
384
|
+
messageBatchMetadata: (message.metadata as any)?.batch,
|
|
385
|
+
pendingMessageBatchMetadata: (pendingMessage.opMetadata as any)?.batch,
|
|
386
|
+
},
|
|
387
|
+
messageDetails: extractSafePropertiesFromMessage(message),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
322
392
|
// This message is the first in a batch if the "batch" property on the metadata is set to true
|
|
323
393
|
if ((message.metadata as IBatchMetadata | undefined)?.batch) {
|
|
324
394
|
// We should not already be processing a batch and there should be no pending batch begin message.
|
|
@@ -406,10 +476,10 @@ export class PendingStateManager implements IDisposable {
|
|
|
406
476
|
|
|
407
477
|
// This assert suggests we are about to send same ops twice, which will result in data loss.
|
|
408
478
|
assert(
|
|
409
|
-
this.
|
|
479
|
+
this.clientIdFromLastReplay !== this.stateHandler.clientId(),
|
|
410
480
|
0x173 /* "replayPendingStates called twice for same clientId!" */,
|
|
411
481
|
);
|
|
412
|
-
this.
|
|
482
|
+
this.clientIdFromLastReplay = this.stateHandler.clientId();
|
|
413
483
|
|
|
414
484
|
assert(
|
|
415
485
|
this.initialMessages.isEmpty(),
|
|
@@ -426,54 +496,72 @@ export class PendingStateManager implements IDisposable {
|
|
|
426
496
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
427
497
|
let pendingMessage = this.pendingMessages.shift()!;
|
|
428
498
|
remainingPendingMessagesCount--;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
499
|
+
|
|
500
|
+
const batchMetadataFlag = asBatchMetadata(pendingMessage.opMetadata)?.batch;
|
|
501
|
+
assert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);
|
|
502
|
+
|
|
503
|
+
// The next message starts a batch (possibly single-message), and we'll need its batchId.
|
|
504
|
+
// We'll find batchId on this message if it was previously generated.
|
|
505
|
+
// Otherwise, generate it now - this is the first time resubmitting this batch.
|
|
506
|
+
const batchId =
|
|
507
|
+
asBatchMetadata(pendingMessage.opMetadata)?.batchId ??
|
|
508
|
+
generateBatchId(
|
|
509
|
+
pendingMessage.batchIdContext.clientId,
|
|
510
|
+
pendingMessage.batchIdContext.batchStartCsn,
|
|
511
|
+
);
|
|
433
512
|
|
|
434
513
|
/**
|
|
435
|
-
* We
|
|
514
|
+
* We must preserve the distinct batches on resubmit.
|
|
436
515
|
* Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will
|
|
437
|
-
* either receive the whole batch ack or nothing at all.
|
|
516
|
+
* either receive the whole batch ack or nothing at all. @see ScheduleManager for how this works.
|
|
438
517
|
*/
|
|
439
|
-
if (
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
518
|
+
if (batchMetadataFlag === undefined) {
|
|
519
|
+
// Single-message batch
|
|
520
|
+
|
|
521
|
+
this.stateHandler.reSubmitBatch(
|
|
522
|
+
[
|
|
523
|
+
{
|
|
524
|
+
content: pendingMessage.content,
|
|
525
|
+
localOpMetadata: pendingMessage.localOpMetadata,
|
|
526
|
+
opMetadata: pendingMessage.opMetadata,
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
batchId,
|
|
443
530
|
);
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
// else: batchMetadataFlag === true (It's a typical multi-message batch)
|
|
444
534
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
batch.push({
|
|
450
|
-
content: pendingMessage.content,
|
|
451
|
-
localOpMetadata: pendingMessage.localOpMetadata,
|
|
452
|
-
opMetadata: pendingMessage.opMetadata,
|
|
453
|
-
});
|
|
535
|
+
assert(
|
|
536
|
+
remainingPendingMessagesCount > 0,
|
|
537
|
+
0x554 /* Last pending message cannot be a batch begin */,
|
|
538
|
+
);
|
|
454
539
|
|
|
455
|
-
|
|
456
|
-
break;
|
|
457
|
-
}
|
|
458
|
-
assert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);
|
|
459
|
-
|
|
460
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
461
|
-
pendingMessage = this.pendingMessages.shift()!;
|
|
462
|
-
remainingPendingMessagesCount--;
|
|
463
|
-
assert(
|
|
464
|
-
pendingMessage.opMetadata?.batch !== true,
|
|
465
|
-
0x556 /* Batch start needs a corresponding batch end */,
|
|
466
|
-
);
|
|
467
|
-
}
|
|
540
|
+
const batch: PendingMessageResubmitData[] = [];
|
|
468
541
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
542
|
+
// check is >= because batch end may be last pending message
|
|
543
|
+
while (remainingPendingMessagesCount >= 0) {
|
|
544
|
+
batch.push({
|
|
472
545
|
content: pendingMessage.content,
|
|
473
546
|
localOpMetadata: pendingMessage.localOpMetadata,
|
|
474
547
|
opMetadata: pendingMessage.opMetadata,
|
|
475
548
|
});
|
|
549
|
+
|
|
550
|
+
if (pendingMessage.opMetadata?.batch === false) {
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
assert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);
|
|
554
|
+
|
|
555
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
556
|
+
pendingMessage = this.pendingMessages.shift()!;
|
|
557
|
+
remainingPendingMessagesCount--;
|
|
558
|
+
assert(
|
|
559
|
+
pendingMessage.opMetadata?.batch !== true,
|
|
560
|
+
0x556 /* Batch start needs a corresponding batch end */,
|
|
561
|
+
);
|
|
476
562
|
}
|
|
563
|
+
|
|
564
|
+
this.stateHandler.reSubmitBatch(batch, batchId);
|
|
477
565
|
}
|
|
478
566
|
|
|
479
567
|
// pending ops should no longer depend on previous sequenced local ops after resubmit
|
|
@@ -491,3 +579,14 @@ export class PendingStateManager implements IDisposable {
|
|
|
491
579
|
}
|
|
492
580
|
}
|
|
493
581
|
}
|
|
582
|
+
|
|
583
|
+
/** For back-compat if trying to apply stashed ops that pre-date batchIdContext */
|
|
584
|
+
function patchBatchIdContext(
|
|
585
|
+
message: IPendingMessageFromStash,
|
|
586
|
+
): asserts message is IPendingMessage {
|
|
587
|
+
const batchIdContext: IPendingMessageFromStash["batchIdContext"] = message.batchIdContext;
|
|
588
|
+
if (batchIdContext === undefined) {
|
|
589
|
+
// Using uuid guarantees uniqueness, retaining existing behavior
|
|
590
|
+
message.batchIdContext = { clientId: uuid(), batchStartCsn: -1 };
|
|
591
|
+
}
|
|
592
|
+
}
|
package/src/scheduleManager.ts
CHANGED
|
@@ -124,7 +124,9 @@ class ScheduleManagerCore {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// First message will have the batch flag set to true if doing a batched send
|
|
127
|
-
|
|
127
|
+
// Non null asserting because of the length check above
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
129
|
+
const firstMessageMetadata = messages[0]!.metadata as IRuntimeMessageMetadata;
|
|
128
130
|
if (!firstMessageMetadata?.batch) {
|
|
129
131
|
return;
|
|
130
132
|
}
|
|
@@ -136,7 +138,9 @@ class ScheduleManagerCore {
|
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
// Set the batch flag to false on the last message to indicate the end of the send batch
|
|
139
|
-
|
|
141
|
+
// Non null asserting here because of the length check at the start of the function
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
143
|
+
const lastMessage = messages[messages.length - 1]!;
|
|
140
144
|
// TODO: It's not clear if this shallow clone is required, as opposed to just setting "batch" to false.
|
|
141
145
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
142
146
|
lastMessage.metadata = { ...(lastMessage.metadata as any), batch: false };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
## Table of contents
|
|
2
|
+
|
|
3
|
+
- [Introduction](#introduction)
|
|
4
|
+
- [Summary vs snapshot](#summary-vs-snapshot)
|
|
5
|
+
- [Why do we need summaries?](#why-do-we-need-summaries)
|
|
6
|
+
- [Who generates summaries?](#who-generates-summaries)
|
|
7
|
+
- [When are summaries generated?](#when-are-summaries-generated)
|
|
8
|
+
- [How are summaries generated?](#how-are-summaries-generated)
|
|
9
|
+
- [Summary Lifecycle](#summary-lifecycle)
|
|
10
|
+
- [Single-commit vs two-commit summaries](#single-commit-vs-two-commit-summaries)
|
|
11
|
+
- [Incremental summaries](#incremental-summaries)
|
|
12
|
+
- [Resiliency](#resiliency)
|
|
13
|
+
- [What does a summary look like?](#what-does-a-summary-look-like)
|
|
14
|
+
|
|
15
|
+
## Introduction
|
|
16
|
+
|
|
17
|
+
This document provides a conceptual overview of summarization without going into a lot of technical or implementation details. It describes what summaries are, how / when they are generated and what do they look like. The goal is for this to be an entry point into summarization for users and developers alike.
|
|
18
|
+
|
|
19
|
+
### Summary vs snapshot
|
|
20
|
+
|
|
21
|
+
The terms summary and snapshot are sometimes used interchangeably. Both represent the state of a container at a point in time. They differ in some respects which are described in [this FAQ](https://fluidframework.com/docs/faq/#summarization).
|
|
22
|
+
|
|
23
|
+
## Why do we need summaries?
|
|
24
|
+
|
|
25
|
+
A 'summary' captures the state of a container at a point in time. Without it, a client would have to apply every operation in the op log, even if those operations no longer affected the current state (e.g. op 1 inserts ‘h’ and op 2 deletes ‘h’). For very large op logs, this would be very expensive for clients to both process and download from the service.
|
|
26
|
+
Instead, when a client opens a collaborative document, it can download a snapshot of the container, and simply process new operations from that point forward.
|
|
27
|
+
|
|
28
|
+
## Who generates summaries?
|
|
29
|
+
|
|
30
|
+
Summaries can be generated by any client connected in "write" mode. In the current implementation, summaries are generated by a separate non-interactive client. Using a separate client is an optimization - this client doesn't have to take local changes into account which can make the summary process more complicated. A summarizer client has the following characteristics:
|
|
31
|
+
|
|
32
|
+
- All the clients connected to the document participate in a process called "summary client election" to elect a "parent summarizer" client. Typically, it's the oldest "write" client connected to the document. The parent summarizer client spawns a "summarizer" client which is responsible for summarization.
|
|
33
|
+
- A summarizer client is like any other client connected to the document except that users cannot interact with this client, and it only works on the state it receives from other clients. It has a brand-new container with its own connection to services.
|
|
34
|
+
|
|
35
|
+
> Note that if the summarizer client closes, the "summary client election" process will choose a new one, if applicable. The default "summary client election" algorithm is to select the oldest "write" client as the parent summarizer client which in turn will create the summarizer client.
|
|
36
|
+
|
|
37
|
+
## When are summaries generated?
|
|
38
|
+
|
|
39
|
+
The summarizer client periodically generates summary based on heuristics calculated based on configurations such as the number of user or system ops received, the amount of time a client has been idle (hasn't received any ops), the maximum time since last summary, maximum number of ops since last summary, etc. The heuristic configurations are defined by an _ISummaryConfigurationHeuristics_ interface defined in [this file](../../src/containerRuntime.ts).
|
|
40
|
+
|
|
41
|
+
The summarizer client uses a default set of configurations defined by _DefaultSummaryConfiguration_ in [this file](../../src/containerRuntime.ts). These can be overridden by providing a new set of configurations as part of container runtime options during creation.
|
|
42
|
+
|
|
43
|
+
## How are summaries generated?
|
|
44
|
+
|
|
45
|
+
When summarization process is triggered, every object in the container's object tree that has data to be summarized is asked to generate its summary, starting at the container runtime which is at the root. There are various objects that participate in the summary process and generate its summary such as data stores, DDSes, garbage collector, blob manager, id compressor, etc. Note that the user data is in the DDSes.
|
|
46
|
+
|
|
47
|
+
### Summary Lifecycle
|
|
48
|
+
|
|
49
|
+
The lifecycle of a summary starts when a "parent summarizer" client is elected.
|
|
50
|
+
- The parent summarizer spawns a non-interactive summarizer client.
|
|
51
|
+
- The summarizer client periodically starts a summary as per heuristics. A summary happens at a particular sequence number called the "summary sequence number" or reference sequence number for the summary.
|
|
52
|
+
- The container runtime (or simply runtime) generates a summary tree (described in the ["What does a summary look like?"](#what-does-a-summary-look-like) section below).
|
|
53
|
+
- The runtime uploads the summary tree to the Fluid storage service which returns a handle (unique id) to the summary if the upload is successful. Otherwise, it returns a failure.
|
|
54
|
+
- The runtime submits a "summarize" op to the Fluid ordering service containing the uploaded summary handle and the summary sequence number.
|
|
55
|
+
- The ordering service stamps it with a sequence number (like any other op) and broadcasts the summarize op.
|
|
56
|
+
- Another service on the server responds to the summarize op.
|
|
57
|
+
- If the summary is accepted, it sends a "summary ack" with the summary sequence number and a summary handle. This handle may or may not be the same as the one in summary op depending on whether this is a single-commit or two-commit summary. More details on this below.
|
|
58
|
+
- If the summary is rejected, it sends a "summary nack" with the details of the summary op
|
|
59
|
+
- The runtime completes the summary process on receiving the summary ack / nack. The runtime has a timeout called "maxAckWaitTime" and if the summary op, ack or nack is not received within this time, it will fail this summary.
|
|
60
|
+
|
|
61
|
+
### Single-commit vs two-commit summaries
|
|
62
|
+
|
|
63
|
+
By default, Fluid uses "two-commit summaries" mode where the two commits refer to the storage committing the summary twice and returning two different handles for it - One when the summary is uploaded and second, on responding to the summary op via a summary ack. In this mode, when the server receives the summary op, it augments the corresponding summary with a "protocol" blob hence generating a new commit and new handle for this summary which it returns in the summary ack.
|
|
64
|
+
|
|
65
|
+
Fluid is switching to "single-commit summary" mode where the client adds the "protocol" blob when uploading the summary. Thus, the server doesn't need to augment the summary and the summary ack is no longer required. As soon as the summary is uploaded (first commit), the summary process is complete. The "summarize" op then is just a way to indicate that a summary happened, and it has details of the summary
|
|
66
|
+
|
|
67
|
+
### Incremental summaries
|
|
68
|
+
|
|
69
|
+
Summaries are incremental, i.e., if an object (or node) did not change since the last summary, it doesn't have to re-summarize its entire contents. Fluid supports the concept of a summary handle defined in [this file](../../../../../common/lib/protocol-definitions/src/summary.ts). A handle is a path to a subtree in a snapshot and it allows objects to reference a subtree in the previous snapshot, which is essentially an instruction to storage to find that subtree and populate into new summary.
|
|
70
|
+
|
|
71
|
+
Say that a data store or DDS did not change since the last summary, it doesn't have to go through the whole summary process described above. It can instead return an ISummaryHandle with path to its subtree in the previous snapshot. The same applies to other types of content like a single content blob within an object's summary tree.
|
|
72
|
+
|
|
73
|
+
### Resiliency
|
|
74
|
+
|
|
75
|
+
The summarization process is designed to be resilient - Basically, a document will eventually summarize and make progress even if there are intermittent failures or disruptions. Some examples of steps taken to achieve this:
|
|
76
|
+
- Last summary - Usually, if the "parent summarizer" client disconnects or shuts down, the "summarizer" client also shuts down and the summarizer election process begins. However, if there a certain number of un-summarized ops, the summarizer client will perform a "last summary" even if the parent shuts down. This is done to make progress in scenarios where new summarizer clients are closed quickly because the parent summarizer keeps disconnecting repeatedly.
|
|
77
|
+
- Retries - The summarizer has a retry mechanism which can identify certain types of intermittent failures either in the client or in the server. It will retry the summary attempt for these failures a certain number of times. This helps in cases where there are intermittent failures such as throttling errors from the server which goes away after waiting for a while.
|
|
78
|
+
|
|
79
|
+
## What does a summary look like?
|
|
80
|
+
|
|
81
|
+
The format of a summary is described in [summary formats](./summaryFormats.md).
|
|
@@ -73,7 +73,9 @@ export class EscapedPath {
|
|
|
73
73
|
public static createAndConcat(pathParts: string[]): EscapedPath {
|
|
74
74
|
let ret = EscapedPath.create(pathParts[0] ?? "");
|
|
75
75
|
for (let i = 1; i < pathParts.length; i++) {
|
|
76
|
-
|
|
76
|
+
// Non null asserting here since we are iterating over pathParts
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
78
|
+
ret = ret.concat(EscapedPath.create(pathParts[i]!));
|
|
77
79
|
}
|
|
78
80
|
return ret;
|
|
79
81
|
}
|
|
@@ -281,7 +281,9 @@ export async function getFluidDataStoreAttributes(
|
|
|
281
281
|
): Promise<ReadFluidDataStoreAttributes> {
|
|
282
282
|
const attributes = await readAndParse<ReadFluidDataStoreAttributes>(
|
|
283
283
|
storage,
|
|
284
|
-
|
|
284
|
+
// TODO why are we non null asserting here?
|
|
285
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
286
|
+
snapshot.blobs[dataStoreAttributesBlobName]!,
|
|
285
287
|
);
|
|
286
288
|
// Use the snapshotFormatVersion to determine how the pkg is encoded in the snapshot.
|
|
287
289
|
// For snapshotFormatVersion = "0.1" (1) or above, pkg is jsonified, otherwise it is just a string.
|
|
@@ -104,13 +104,26 @@ Each node in a snapshot tree is represented by the above interface and contains
|
|
|
104
104
|
This section shows what a typical summary or snapshot tree in a container looks like. Some key things to note:
|
|
105
105
|
|
|
106
106
|
- The diagrams in this section show some examples of existing blobs / trees that are added at each node and doesn't show an exhaustive list.
|
|
107
|
-
- The blue boxes represent tree nodes.
|
|
108
|
-
- The green boxes represent blobs.
|
|
109
|
-
- The purple boxes represent attachments.
|
|
110
|
-
- The
|
|
107
|
+
- The blue boxes represent summary tree nodes.
|
|
108
|
+
- The green boxes represent summary blobs.
|
|
109
|
+
- The purple boxes represent summary attachments.
|
|
110
|
+
- The pink boxes represent other nodes - either existing nodes that are not shown or new nodes that may be added in the future. A node can be a tree, blob or attachment.
|
|
111
111
|
|
|
112
112
|
A typical tree uploaded to or downloaded from storage looks like the following:
|
|
113
|
-
|
|
113
|
+
|
|
114
|
+
```mermaid
|
|
115
|
+
flowchart TD
|
|
116
|
+
classDef tree fill:#4672c4,color:#fff
|
|
117
|
+
classDef blob fill:#538135,color:#fff
|
|
118
|
+
classDef others fill:#d636bb,stroke:#4672c4,stroke-width:1px,color:#fff,stroke-dasharray: 5 5
|
|
119
|
+
A["/"]:::tree --> B[".protocol"]:::tree
|
|
120
|
+
B --> C[attributes]:::blob
|
|
121
|
+
B --> D["quorum members"]:::blob
|
|
122
|
+
B --> E["quorum proposals"]:::blob
|
|
123
|
+
B --> F["quorum values"]:::blob
|
|
124
|
+
B --> G["other nodes"]:::others
|
|
125
|
+
A --> H[".app (described below)"]:::tree
|
|
126
|
+
```
|
|
114
127
|
|
|
115
128
|
`Protocol tree` - This is the tree named `.protocol` and contains protocol level information for the container. These are used by the container to initialize.
|
|
116
129
|
|
|
@@ -129,8 +142,38 @@ The contents of the protocol tree are:
|
|
|
129
142
|
### App tree
|
|
130
143
|
|
|
131
144
|
This is what the ".app" tree looks like which is generated by the container runtime during summary upload. The same is passed to container runtime during snapshot download:
|
|
132
|
-

|
|
133
145
|
|
|
146
|
+
```mermaid
|
|
147
|
+
flowchart TD
|
|
148
|
+
classDef tree fill:#4672c4,color:#fff
|
|
149
|
+
classDef blob fill:#538135,color:#fff
|
|
150
|
+
classDef attachment fill:#904fc2,color:#fff
|
|
151
|
+
classDef others fill:#d636bb,stroke:#4672c4,stroke-width:1px,color:#fff,stroke-dasharray: 5 5
|
|
152
|
+
classDef hidden display:none;
|
|
153
|
+
A[".app"]:::tree --> B[.metadata]:::blob
|
|
154
|
+
A --> C[.aliases]:::blob
|
|
155
|
+
A --> D[.idCompressor]:::blob
|
|
156
|
+
A --> E[.channels]:::tree
|
|
157
|
+
E --> F["data store 1"]:::tree
|
|
158
|
+
E --> G["data store 2"]:::tree
|
|
159
|
+
E --> H["data store N"]:::tree
|
|
160
|
+
G --> I[.components]:::blob
|
|
161
|
+
G --> J[.channels]:::tree
|
|
162
|
+
J --> K[.channels]:::tree
|
|
163
|
+
J --> L[DDS2]:::tree
|
|
164
|
+
L --> M[.attributes]:::blob
|
|
165
|
+
L --> N["other nodes"]:::others
|
|
166
|
+
N --> END:::hidden
|
|
167
|
+
L --> O[.header]:::blob
|
|
168
|
+
J --> P[.channels]:::tree
|
|
169
|
+
G --> Q["other nodes"]:::others
|
|
170
|
+
A --> R[gc]:::tree
|
|
171
|
+
A --> S["other nodes"]:::others
|
|
172
|
+
A --> T[.blobs]:::tree
|
|
173
|
+
T --> U["attachment blob 1"]:::attachment
|
|
174
|
+
T --> V["attachment blob N"]:::attachment
|
|
175
|
+
|
|
176
|
+
```
|
|
134
177
|
- `Container`: The root represents the container or container runtime node. Its contents are described below:
|
|
135
178
|
|
|
136
179
|
- `.metadata blob` - The container level metadata such as creation time, create version, etc.
|
|
@@ -152,9 +195,27 @@ This is what the ".app" tree looks like which is generated by the container runt
|
|
|
152
195
|
- `.header blob` - Added by some DDSs and may contains its data. Note that all DDSs may not add this.
|
|
153
196
|
- A DDS may add other blobs and / or trees to represent its data. Basically, a DDS can write its data in any form
|
|
154
197
|
|
|
155
|
-
### Summary tree distinction
|
|
198
|
+
### Summary tree distinction - Incremental summaries
|
|
156
199
|
|
|
157
200
|
In the visualization above, a summary tree differs from a snapshot tree in the following way:
|
|
158
201
|
A summary tree supports incremental summaries via summary handles. Any node in the tree that has not changed since the previous successful summary can send a summary handle (`ISummaryHandle`) instead of sending its entire contents in a full summary. The following diagram shows this with an example where certain parts of the summary tree use a summary handle. It is a zoomed in version of the same app tree as above where nodes where summary handles are marked in red:
|
|
159
202
|
|
|
160
|
-
|
|
203
|
+
```mermaid
|
|
204
|
+
flowchart TD
|
|
205
|
+
classDef tree fill:#4672c4,color:#fff
|
|
206
|
+
classDef blob fill:#538135,color:#fff
|
|
207
|
+
classDef others fill:#d636bb,stroke:#4672c4,stroke-width:1px,color:#fff,stroke-dasharray: 5 5
|
|
208
|
+
classDef handle fill:#cc4343,color:#fff
|
|
209
|
+
A[".app"]:::tree --> B["other nodes"]:::others
|
|
210
|
+
A --> C[.channels]:::tree
|
|
211
|
+
C --> D["handle: '/data store 1'"]:::handle
|
|
212
|
+
C --> E["data store 2"]:::tree
|
|
213
|
+
E --> F[".channels"]:::tree
|
|
214
|
+
F --> G["handle: '/data store 2/DDS 1'"]:::handle
|
|
215
|
+
F --> H["DDS 2"]:::tree
|
|
216
|
+
H --> I["handle: '/data store 2/DDS 2/sub node'"]:::handle
|
|
217
|
+
F --> J["DDS N"]:::tree
|
|
218
|
+
E --> K["other nodes"]:::others
|
|
219
|
+
C --> L["data store N"]:::tree
|
|
220
|
+
A --> M["handle: '/gc'"]:::handle
|
|
221
|
+
```
|
package/tsconfig.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|