@fluidframework/container-runtime 2.1.1 → 2.2.1
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 +30 -0
- package/README.md +2 -2
- package/api-report/container-runtime.legacy.alpha.api.md +4 -3
- package/container-runtime.test-files.tar +0 -0
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +9 -0
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/channelCollection.d.ts +0 -14
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +2 -12
- package/dist/channelCollection.js.map +1 -1
- package/dist/containerRuntime.d.ts +34 -6
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +181 -90
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +9 -18
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +40 -78
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts +0 -6
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +23 -66
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcConfigs.d.ts.map +1 -1
- package/dist/gc/gcConfigs.js +11 -34
- package/dist/gc/gcConfigs.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +9 -52
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js +3 -23
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcHelpers.d.ts.map +1 -1
- package/dist/gc/gcHelpers.js +2 -6
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/gc/gcSummaryStateTracker.d.ts +1 -1
- package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/dist/gc/gcSummaryStateTracker.js +4 -8
- package/dist/gc/gcSummaryStateTracker.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts +1 -9
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +3 -25
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/gc/index.d.ts +2 -2
- package/dist/gc/index.d.ts.map +1 -1
- package/dist/gc/index.js +2 -7
- package/dist/gc/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/messageTypes.d.ts +6 -5
- package/dist/messageTypes.d.ts.map +1 -1
- package/dist/messageTypes.js.map +1 -1
- package/dist/metadata.d.ts +9 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +6 -1
- package/dist/metadata.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +1 -1
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts +8 -0
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +34 -2
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +1 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +20 -0
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +38 -19
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +67 -43
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +33 -22
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +148 -105
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.js +5 -1
- package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts +3 -4
- package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js +16 -15
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +9 -0
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/channelCollection.d.ts +0 -14
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +2 -11
- package/lib/channelCollection.js.map +1 -1
- package/lib/containerRuntime.d.ts +34 -6
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +181 -90
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +9 -18
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +27 -65
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts +0 -6
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +25 -68
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcConfigs.d.ts.map +1 -1
- package/lib/gc/gcConfigs.js +12 -35
- package/lib/gc/gcConfigs.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +9 -52
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js +2 -22
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcHelpers.d.ts.map +1 -1
- package/lib/gc/gcHelpers.js +2 -6
- package/lib/gc/gcHelpers.js.map +1 -1
- package/lib/gc/gcSummaryStateTracker.d.ts +1 -1
- package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
- package/lib/gc/gcSummaryStateTracker.js +4 -8
- package/lib/gc/gcSummaryStateTracker.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts +1 -9
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +3 -24
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/gc/index.d.ts +2 -2
- package/lib/gc/index.d.ts.map +1 -1
- package/lib/gc/index.js +2 -2
- package/lib/gc/index.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/messageTypes.d.ts +6 -5
- package/lib/messageTypes.d.ts.map +1 -1
- package/lib/messageTypes.js.map +1 -1
- package/lib/metadata.d.ts +9 -1
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js +4 -0
- package/lib/metadata.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +1 -1
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts +8 -0
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +34 -2
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +1 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +20 -0
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +38 -19
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +67 -43
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +33 -22
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +149 -106
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.js +5 -1
- package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts +3 -4
- package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js +16 -15
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/package.json +21 -21
- package/src/batchTracker.ts +4 -2
- package/src/blobManager/blobManager.ts +9 -0
- package/src/channelCollection.ts +2 -11
- package/src/containerRuntime.ts +216 -121
- package/src/dataStoreContext.ts +29 -93
- package/src/gc/garbageCollection.ts +26 -79
- package/src/gc/gcConfigs.ts +12 -45
- package/src/gc/gcDefinitions.ts +10 -55
- package/src/gc/gcHelpers.ts +10 -8
- package/src/gc/gcSummaryStateTracker.ts +6 -9
- package/src/gc/gcTelemetry.ts +3 -38
- package/src/gc/index.ts +2 -6
- package/src/index.ts +0 -1
- package/src/messageTypes.ts +12 -11
- package/src/metadata.ts +16 -2
- package/src/opLifecycle/index.ts +1 -0
- package/src/opLifecycle/opGroupingManager.ts +42 -3
- package/src/opLifecycle/outbox.ts +30 -0
- package/src/opLifecycle/remoteMessageProcessor.ts +110 -56
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +209 -168
- package/src/summary/README.md +31 -28
- package/src/summary/summarizerNode/summarizerNode.ts +6 -1
- package/src/summary/summarizerNode/summarizerNodeWithGc.ts +20 -43
- package/src/summary/summaryFormats.md +25 -22
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remoteMessageProcessor.js","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport {\n\tMessageType,\n\tISequencedDocumentMessage,\n} from \"@fluidframework/driver-definitions/internal\";\n\nimport {\n\tContainerMessageType,\n\ttype InboundContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessageOrSystemMessage,\n\ttype InboundSequencedRecentlyAddedContainerRuntimeMessage,\n} from \"../messageTypes.js\";\nimport { asBatchMetadata } from \"../metadata.js\";\n\nimport { OpDecompressor } from \"./opDecompressor.js\";\nimport { OpGroupingManager, isGroupedBatch } from \"./opGroupingManager.js\";\nimport { OpSplitter, isChunkedMessage } from \"./opSplitter.js\";\n\n/**\n * Stateful class for processing incoming remote messages as the virtualization measures are unwrapped,\n * potentially across numerous inbound ops.\n *\n * @internal\n */\nexport class RemoteMessageProcessor {\n\t/**\n\t * Client Sequence Number of the first message in the current batch being processed.\n\t * If undefined, we are expecting the next message to start a new batch.\n\t *\n\t * @remarks For chunked batches, this is the CSN of the \"representative\" chunk (the final chunk)\n\t */\n\tprivate batchStartCsn: number | undefined;\n\n\tconstructor(\n\t\tprivate readonly opSplitter: OpSplitter,\n\t\tprivate readonly opDecompressor: OpDecompressor,\n\t\tprivate readonly opGroupingManager: OpGroupingManager,\n\t) {}\n\n\tpublic get partialMessages(): ReadonlyMap<string, string[]> {\n\t\treturn this.opSplitter.chunks;\n\t}\n\n\tpublic clearPartialMessagesFor(clientId: string) {\n\t\tthis.opSplitter.clearPartialChunks(clientId);\n\t}\n\n\t/**\n\t * Ungroups and Unchunks the runtime ops encapsulated by the single remoteMessage received over the wire\n\t * @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized\n\t * (grouped, compressed, and/or chunked).\n\t * Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure\n\t * depends on this object instance.\n\t * Note remoteMessageCopy.contents (and other object props) MUST not be modified,\n\t * but may be overwritten (as is the case with contents).\n\t *\n\t * Incoming messages will always have compression, chunking, and grouped batching happen in a defined order and that order cannot be changed.\n\t * When processing these messages, the order is:\n\t * 1. If chunked, process the chunk and only continue if this is a final chunk\n\t * 2. If compressed, decompress the message and store for further unrolling of the decompressed content\n\t * 3. If grouped, ungroup the message\n\t * For more details, see https://github.com/microsoft/FluidFramework/blob/main/packages/runtime/container-runtime/src/opLifecycle/README.md#inbound\n\t *\n\t * @returns the unchunked, decompressed, ungrouped, unpacked SequencedContainerRuntimeMessages encapsulated in the remote message.\n\t * For ops that weren't virtualized (e.g. System ops that the ContainerRuntime will ultimately ignore),\n\t * a singleton array [remoteMessageCopy] is returned\n\t */\n\tpublic process(remoteMessageCopy: ISequencedDocumentMessage):\n\t\t| {\n\t\t\t\tmessages: InboundSequencedContainerRuntimeMessageOrSystemMessage[];\n\t\t\t\tbatchStartCsn: number;\n\t\t }\n\t\t| undefined {\n\t\tlet message = remoteMessageCopy;\n\n\t\tif (isChunkedMessage(message)) {\n\t\t\tconst chunkProcessingResult = this.opSplitter.processChunk(message);\n\t\t\t// Only continue further if current chunk is the final chunk\n\t\t\tif (!chunkProcessingResult.isFinalChunk) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// This message will always be compressed\n\t\t\tmessage = chunkProcessingResult.message;\n\t\t}\n\n\t\tif (this.opDecompressor.isCompressedMessage(message)) {\n\t\t\tthis.opDecompressor.decompressAndStore(message);\n\t\t}\n\n\t\tif (this.opDecompressor.currentlyUnrolling) {\n\t\t\tmessage = this.opDecompressor.unroll(message);\n\t\t\t// Need to unpack after unrolling if not a groupedBatch\n\t\t\tif (!isGroupedBatch(message)) {\n\t\t\t\tunpack(message);\n\t\t\t}\n\t\t}\n\n\t\tif (isGroupedBatch(message)) {\n\t\t\t// We should be awaiting a new batch (batchStartCsn undefined)\n\t\t\tassert(\n\t\t\t\tthis.batchStartCsn === undefined,\n\t\t\t\t0x9d3 /* Grouped batch interrupting another batch */,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tmessages: this.opGroupingManager.ungroupOp(message).map(unpack),\n\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t};\n\t\t}\n\n\t\tconst batchStartCsn = this.getAndUpdateBatchStartCsn(message);\n\n\t\t// Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked\n\t\tunpackRuntimeMessage(message);\n\t\treturn {\n\t\t\tmessages: [message as InboundSequencedContainerRuntimeMessageOrSystemMessage],\n\t\t\tbatchStartCsn,\n\t\t};\n\t}\n\n\t/**\n\t * Based on pre-existing batch tracking info and the current message's batch metadata,\n\t * this will return the starting CSN for this message's batch, and will also update\n\t * the batch tracking info (this.batchStartCsn) based on whether we're still mid-batch.\n\t */\n\tprivate getAndUpdateBatchStartCsn(message: ISequencedDocumentMessage): number {\n\t\tconst batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;\n\t\tif (this.batchStartCsn === undefined) {\n\t\t\t// We are waiting for a new batch\n\t\t\tassert(batchMetadataFlag !== false, 0x9d5 /* Unexpected batch end marker */);\n\n\t\t\t// Start of a new multi-message batch\n\t\t\tif (batchMetadataFlag === true) {\n\t\t\t\tthis.batchStartCsn = message.clientSequenceNumber;\n\t\t\t\treturn this.batchStartCsn;\n\t\t\t}\n\n\t\t\t// Single-message batch (Since metadata flag is undefined)\n\t\t\t// IMPORTANT: Leave this.batchStartCsn undefined, we're ready for the next batch now.\n\t\t\treturn message.clientSequenceNumber;\n\t\t}\n\n\t\t// We are in the middle or end of an existing multi-message batch. Return the current batchStartCsn\n\t\tconst batchStartCsn = this.batchStartCsn;\n\n\t\tassert(batchMetadataFlag !== true, 0x9d6 /* Unexpected batch start marker */);\n\t\tif (batchMetadataFlag === false) {\n\t\t\t// Batch end? Then get ready for the next batch to start\n\t\t\tthis.batchStartCsn = undefined;\n\t\t}\n\n\t\treturn batchStartCsn;\n\t}\n}\n\n/** Takes an incoming message and if the contents is a string, JSON.parse's it in place */\nexport function ensureContentsDeserialized(\n\tmutableMessage: ISequencedDocumentMessage,\n\tmodernRuntimeMessage: boolean,\n\tlogLegacyCase: (codePath: string) => void,\n): void {\n\t// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!\n\t// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.\n\t// Old ops may contain empty string (I assume noops).\n\tlet parsedJsonContents: boolean;\n\tif (typeof mutableMessage.contents === \"string\" && mutableMessage.contents !== \"\") {\n\t\tmutableMessage.contents = JSON.parse(mutableMessage.contents);\n\t\tparsedJsonContents = true;\n\t} else {\n\t\tparsedJsonContents = false;\n\t}\n\n\t// We expect Modern Runtime Messages to have JSON serialized contents,\n\t// and all other messages not to (system messages and legacy runtime messages without outer \"op\" type envelope)\n\t// Let's observe if we are wrong about this to learn about these cases.\n\tif (modernRuntimeMessage !== parsedJsonContents) {\n\t\tlogLegacyCase(\"ensureContentsDeserialized_unexpectedContentsType\");\n\t}\n}\n\n/**\n * For a given message, it moves the nested InboundContainerRuntimeMessage props one level up.\n *\n * The return type illustrates the assumption that the message param\n * becomes a InboundSequencedContainerRuntimeMessage by the time the function returns\n * (but there is no runtime validation of the 'type' or 'compatDetails' values).\n */\nfunction unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRuntimeMessage {\n\t// We assume the contents is an InboundContainerRuntimeMessage (the message is \"packed\")\n\tconst contents = message.contents as InboundContainerRuntimeMessage;\n\n\t// We're going to unpack message in-place (promoting those properties of contents up to message itself)\n\tconst messageUnpacked = message as InboundSequencedContainerRuntimeMessage;\n\n\tmessageUnpacked.type = contents.type;\n\tmessageUnpacked.contents = contents.contents;\n\tif (\"compatDetails\" in contents) {\n\t\t(messageUnpacked as InboundSequencedRecentlyAddedContainerRuntimeMessage).compatDetails =\n\t\t\tcontents.compatDetails;\n\t}\n\treturn messageUnpacked;\n}\n\n/**\n * Unpacks runtime messages.\n *\n * @remarks This API makes no promises regarding backward-compatibility. This is internal API.\n * @param message - message (as it observed in storage / service)\n * @returns whether the given message was unpacked\n *\n * @internal\n */\nexport function unpackRuntimeMessage(\n\tmessage: ISequencedDocumentMessage,\n\tlogLegacyCase: (codePath: string) => void = () => {},\n): boolean {\n\tif (message.type !== MessageType.Operation) {\n\t\t// Legacy format, but it's already \"unpacked\",\n\t\t// i.e. message.type is actually ContainerMessageType.\n\t\t// Or it's non-runtime message.\n\t\t// Nothing to do in such case.\n\t\treturn false;\n\t}\n\n\t// legacy op format?\n\t// TODO: Unsure if this is a real format we should be concerned with. There doesn't appear to be anything prepared to handle the address member.\n\tif (\n\t\t(message.contents as { address?: unknown }).address !== undefined &&\n\t\t(message.contents as { type?: unknown }).type === undefined\n\t) {\n\t\tmessage.type = ContainerMessageType.FluidDataStoreOp;\n\t\tlogLegacyCase(\"unpackRuntimeMessage_contentsWithAddress\");\n\t} else {\n\t\t// new format\n\t\tunpack(message);\n\t}\n\n\treturn true;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"remoteMessageProcessor.js","sourceRoot":"","sources":["../../src/opLifecycle/remoteMessageProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { assert } from \"@fluidframework/core-utils/internal\";\nimport {\n\tMessageType,\n\tISequencedDocumentMessage,\n} from \"@fluidframework/driver-definitions/internal\";\n\nimport {\n\tContainerMessageType,\n\ttype InboundContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessage,\n\ttype InboundSequencedRecentlyAddedContainerRuntimeMessage,\n} from \"../messageTypes.js\";\nimport { asBatchMetadata } from \"../metadata.js\";\n\nimport { OpDecompressor } from \"./opDecompressor.js\";\nimport { OpGroupingManager, isGroupedBatch } from \"./opGroupingManager.js\";\nimport { OpSplitter, isChunkedMessage } from \"./opSplitter.js\";\n\n/** Messages being received as a batch, with details needed to process the batch */\nexport interface InboundBatch {\n\t/** Messages in this batch */\n\treadonly messages: InboundSequencedContainerRuntimeMessage[];\n\t/** Batch ID, if present */\n\treadonly batchId: string | undefined;\n\t/** clientId that sent this batch. Used to compute Batch ID if needed */\n\treadonly clientId: string;\n\t/**\n\t * Client Sequence Number of the first message in the batch.\n\t * Used to compute Batch ID if needed\n\t *\n\t * @remarks For chunked batches, this is the CSN of the \"representative\" chunk (the final chunk).\n\t * For grouped batches, clientSequenceNumber on messages is overwritten, so we track this original value here.\n\t */\n\treadonly batchStartCsn: number;\n\t/** For an empty batch (with no messages), we need to remember the empty grouped batch's sequence number */\n\treadonly emptyBatchSequenceNumber?: number;\n}\n\nfunction assertHasClientId(\n\tmessage: ISequencedDocumentMessage,\n): asserts message is ISequencedDocumentMessage & { clientId: string } {\n\tassert(\n\t\tmessage.clientId !== null,\n\t\t0xa02 /* Server-generated message should not reach RemoteMessageProcessor */,\n\t);\n}\n\n/**\n * Stateful class for processing incoming remote messages as the virtualization measures are unwrapped,\n * potentially across numerous inbound ops.\n *\n * @internal\n */\nexport class RemoteMessageProcessor {\n\t/**\n\t * The current batch being received, with details needed to process it.\n\t *\n\t * @remarks If undefined, we are expecting the next message to start a new batch.\n\t */\n\tprivate batchInProgress: InboundBatch | undefined;\n\n\tconstructor(\n\t\tprivate readonly opSplitter: OpSplitter,\n\t\tprivate readonly opDecompressor: OpDecompressor,\n\t\tprivate readonly opGroupingManager: OpGroupingManager,\n\t) {}\n\n\tpublic get partialMessages(): ReadonlyMap<string, string[]> {\n\t\treturn this.opSplitter.chunks;\n\t}\n\n\tpublic clearPartialMessagesFor(clientId: string) {\n\t\tthis.opSplitter.clearPartialChunks(clientId);\n\t}\n\n\t/**\n\t * Ungroups and Unchunks the runtime ops of a batch received over the wire\n\t * @param remoteMessageCopy - A shallow copy of a message from another client, possibly virtualized\n\t * (grouped, compressed, and/or chunked).\n\t * Being a shallow copy, it's considered mutable, meaning no other Container or other parallel procedure\n\t * depends on this object instance.\n\t * Note remoteMessageCopy.contents (and other object props) MUST not be modified,\n\t * but may be overwritten (as is the case with contents).\n\t *\n\t * Incoming messages will always have compression, chunking, and grouped batching happen in a defined order and that order cannot be changed.\n\t * When processing these messages, the order is:\n\t * 1. If chunked, process the chunk and only continue if this is a final chunk\n\t * 2. If compressed, decompress the message and store for further unrolling of the decompressed content\n\t * 3. If grouped, ungroup the message\n\t * For more details, see https://github.com/microsoft/FluidFramework/blob/main/packages/runtime/container-runtime/src/opLifecycle/README.md#inbound\n\t *\n\t * @returns all the unchunked, decompressed, ungrouped, unpacked InboundSequencedContainerRuntimeMessage from a single batch\n\t * or undefined if the batch is not yet complete.\n\t */\n\tpublic process(\n\t\tremoteMessageCopy: ISequencedDocumentMessage,\n\t\tlogLegacyCase: (codePath: string) => void,\n\t): InboundBatch | undefined {\n\t\tlet message = remoteMessageCopy;\n\n\t\tassertHasClientId(message);\n\t\tconst clientId = message.clientId;\n\n\t\tif (isChunkedMessage(message)) {\n\t\t\tconst chunkProcessingResult = this.opSplitter.processChunk(message);\n\t\t\t// Only continue further if current chunk is the final chunk\n\t\t\tif (!chunkProcessingResult.isFinalChunk) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\t// This message will always be compressed\n\t\t\tmessage = chunkProcessingResult.message;\n\t\t}\n\n\t\tif (this.opDecompressor.isCompressedMessage(message)) {\n\t\t\tthis.opDecompressor.decompressAndStore(message);\n\t\t}\n\n\t\tif (this.opDecompressor.currentlyUnrolling) {\n\t\t\tmessage = this.opDecompressor.unroll(message);\n\t\t\t// Need to unpack after unrolling if not a groupedBatch\n\t\t\tif (!isGroupedBatch(message)) {\n\t\t\t\tunpack(message);\n\t\t\t}\n\t\t}\n\n\t\tif (isGroupedBatch(message)) {\n\t\t\t// We should be awaiting a new batch (batchInProgress undefined)\n\t\t\tassert(\n\t\t\t\tthis.batchInProgress === undefined,\n\t\t\t\t0x9d3 /* Grouped batch interrupting another batch */,\n\t\t\t);\n\t\t\tconst batchId = asBatchMetadata(message.metadata)?.batchId;\n\t\t\tconst groupedMessages = this.opGroupingManager.ungroupOp(message).map(unpack);\n\t\t\treturn {\n\t\t\t\tmessages: groupedMessages, // Will be [] for an empty batch\n\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\tclientId,\n\t\t\t\tbatchId,\n\t\t\t\t// If the batch is empty, we need to return the sequence number aside\n\t\t\t\temptyBatchSequenceNumber:\n\t\t\t\t\tgroupedMessages.length === 0 ? message.sequenceNumber : undefined,\n\t\t\t};\n\t\t}\n\n\t\t// Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked\n\t\tunpackRuntimeMessage(message, logLegacyCase);\n\n\t\tconst { batchEnded } = this.addMessageToBatch(\n\t\t\tmessage as InboundSequencedContainerRuntimeMessage & { clientId: string },\n\t\t);\n\n\t\tif (!batchEnded) {\n\t\t\t// batch not yet complete\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst completedBatch = this.batchInProgress;\n\t\tthis.batchInProgress = undefined;\n\t\treturn completedBatch;\n\t}\n\n\t/**\n\t * Add the given message to the current batch, and indicate whether the batch is now complete.\n\t *\n\t * @returns batchEnded: true if the batch is now complete, batchEnded: false if more messages are expected\n\t */\n\tprivate addMessageToBatch(\n\t\tmessage: InboundSequencedContainerRuntimeMessage & { clientId: string },\n\t): { batchEnded: boolean } {\n\t\tconst batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;\n\t\tif (this.batchInProgress === undefined) {\n\t\t\t// We are waiting for a new batch\n\t\t\tassert(batchMetadataFlag !== false, 0x9d5 /* Unexpected batch end marker */);\n\n\t\t\t// Start of a new multi-message batch\n\t\t\tif (batchMetadataFlag === true) {\n\t\t\t\tthis.batchInProgress = {\n\t\t\t\t\tmessages: [message],\n\t\t\t\t\tbatchId: asBatchMetadata(message.metadata)?.batchId,\n\t\t\t\t\tclientId: message.clientId,\n\t\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\t};\n\n\t\t\t\treturn { batchEnded: false };\n\t\t\t}\n\n\t\t\t// Single-message batch (Since metadata flag is undefined)\n\t\t\tthis.batchInProgress = {\n\t\t\t\tmessages: [message],\n\t\t\t\tbatchStartCsn: message.clientSequenceNumber,\n\t\t\t\tclientId: message.clientId,\n\t\t\t\tbatchId: asBatchMetadata(message.metadata)?.batchId,\n\t\t\t};\n\t\t\treturn { batchEnded: true };\n\t\t}\n\t\tassert(batchMetadataFlag !== true, 0x9d6 /* Unexpected batch start marker */);\n\n\t\tthis.batchInProgress.messages.push(message);\n\n\t\treturn { batchEnded: batchMetadataFlag === false };\n\t}\n}\n\n/**\n * Takes an incoming message and if the contents is a string, JSON.parse's it in place\n * @param mutableMessage - op message received\n * @param hasModernRuntimeMessageEnvelope - false if the message does not contain the modern op envelop where message.type is MessageType.Operation\n * @param logLegacyCase - callback to log when legacy op is encountered\n */\nexport function ensureContentsDeserialized(\n\tmutableMessage: ISequencedDocumentMessage,\n\thasModernRuntimeMessageEnvelope: boolean,\n\tlogLegacyCase: (codePath: string) => void,\n): void {\n\t// Currently the loader layer is parsing the contents of the message as JSON if it is a string,\n\t// so we never expect to see this case.\n\t// We intend to remove that logic from the Loader, at which point we will have it here.\n\t// Only hasModernRuntimeMessageEnvelope true will be expected to have JSON contents.\n\tlet didParseJsonContents: boolean;\n\tif (typeof mutableMessage.contents === \"string\" && mutableMessage.contents !== \"\") {\n\t\tmutableMessage.contents = JSON.parse(mutableMessage.contents);\n\t\tdidParseJsonContents = true;\n\t} else {\n\t\tdidParseJsonContents = false;\n\t}\n\n\t// The DeltaManager parses the contents of the message as JSON if it is a string,\n\t// so we should never end up parsing it here.\n\t// Let's observe if we are wrong about this to learn about these cases.\n\tif (didParseJsonContents) {\n\t\tlogLegacyCase(\"ensureContentsDeserialized_foundJsonContents\");\n\t}\n}\n\n/**\n * For a given message, it moves the nested InboundContainerRuntimeMessage props one level up.\n *\n * The return type illustrates the assumption that the message param\n * becomes a InboundSequencedContainerRuntimeMessage by the time the function returns\n * (but there is no runtime validation of the 'type' or 'compatDetails' values).\n */\nfunction unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRuntimeMessage {\n\t// We assume the contents is an InboundContainerRuntimeMessage (the message is \"packed\")\n\tconst contents = message.contents as InboundContainerRuntimeMessage;\n\n\t// We're going to unpack message in-place (promoting those properties of contents up to message itself)\n\tconst messageUnpacked = message as InboundSequencedContainerRuntimeMessage;\n\n\tmessageUnpacked.type = contents.type;\n\tmessageUnpacked.contents = contents.contents;\n\tif (\"compatDetails\" in contents) {\n\t\t(messageUnpacked as InboundSequencedRecentlyAddedContainerRuntimeMessage).compatDetails =\n\t\t\tcontents.compatDetails;\n\t}\n\treturn messageUnpacked;\n}\n\n/**\n * Unpacks runtime messages.\n *\n * @remarks This API makes no promises regarding backward-compatibility. This is internal API.\n * @param message - message (as it observed in storage / service)\n * @returns whether the given message was unpacked\n *\n * @internal\n */\nexport function unpackRuntimeMessage(\n\tmessage: ISequencedDocumentMessage,\n\tlogLegacyCase: (codePath: string) => void = () => {},\n): boolean {\n\tif (message.type !== MessageType.Operation) {\n\t\t// Legacy format, but it's already \"unpacked\",\n\t\t// i.e. message.type is actually ContainerMessageType.\n\t\t// Or it's non-runtime message.\n\t\t// Nothing to do in such case.\n\t\treturn false;\n\t}\n\n\t// legacy op format?\n\t// TODO: Unsure if this is a real format we should be concerned with. There doesn't appear to be anything prepared to handle the address member.\n\tif (\n\t\t(message.contents as { address?: unknown }).address !== undefined &&\n\t\t(message.contents as { type?: unknown }).type === undefined\n\t) {\n\t\tmessage.type = ContainerMessageType.FluidDataStoreOp;\n\t\tlogLegacyCase(\"unpackRuntimeMessage_contentsWithAddress\");\n\t} else {\n\t\t// new format\n\t\tunpack(message);\n\t}\n\n\treturn true;\n}\n"]}
|
package/dist/packageVersion.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
|
|
6
6
|
*/
|
|
7
7
|
export declare const pkgName = "@fluidframework/container-runtime";
|
|
8
|
-
export declare const pkgVersion = "2.
|
|
8
|
+
export declare const pkgVersion = "2.2.1";
|
|
9
9
|
//# sourceMappingURL=packageVersion.d.ts.map
|
package/dist/packageVersion.js
CHANGED
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.pkgVersion = exports.pkgName = void 0;
|
|
10
10
|
exports.pkgName = "@fluidframework/container-runtime";
|
|
11
|
-
exports.pkgVersion = "2.
|
|
11
|
+
exports.pkgVersion = "2.2.1";
|
|
12
12
|
//# sourceMappingURL=packageVersion.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,mCAAmC,CAAC;AAC9C,QAAA,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-runtime\";\nexport const pkgVersion = \"2.
|
|
1
|
+
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,mCAAmC,CAAC;AAC9C,QAAA,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-runtime\";\nexport const pkgVersion = \"2.2.1\";\n"]}
|
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
import { ICriticalContainerError } from "@fluidframework/container-definitions";
|
|
6
5
|
import { IDisposable } from "@fluidframework/core-interfaces";
|
|
7
6
|
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
|
|
8
|
-
import { InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
|
|
9
|
-
import { BatchId, BatchMessage } from "./opLifecycle/index.js";
|
|
7
|
+
import { type InboundSequencedContainerRuntimeMessage } from "./messageTypes.js";
|
|
8
|
+
import { BatchId, BatchMessage, InboundBatch } from "./opLifecycle/index.js";
|
|
10
9
|
/**
|
|
11
10
|
* This represents a message that has been submitted and is added to the pending queue when `submit` is called on the
|
|
12
11
|
* ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.
|
|
@@ -20,8 +19,8 @@ export interface IPendingMessage {
|
|
|
20
19
|
localOpMetadata: unknown;
|
|
21
20
|
opMetadata: Record<string, unknown> | undefined;
|
|
22
21
|
sequenceNumber?: number;
|
|
23
|
-
/** Info
|
|
24
|
-
|
|
22
|
+
/** Info about the batch this pending message belongs to, for validation and for computing the batchId on reconnect */
|
|
23
|
+
batchInfo: {
|
|
25
24
|
/** The Batch's original clientId, from when it was first flushed to be submitted */
|
|
26
25
|
clientId: string;
|
|
27
26
|
/**
|
|
@@ -29,6 +28,8 @@ export interface IPendingMessage {
|
|
|
29
28
|
* @remarks A negative value means it was not yet submitted when queued here (e.g. disconnected right before flush fired)
|
|
30
29
|
*/
|
|
31
30
|
batchStartCsn: number;
|
|
31
|
+
/** length of the batch (how many runtime messages here) */
|
|
32
|
+
length: number;
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
export interface IPendingLocalState {
|
|
@@ -42,7 +43,6 @@ export type PendingMessageResubmitData = Pick<IPendingMessage, "content" | "loca
|
|
|
42
43
|
export interface IRuntimeStateHandler {
|
|
43
44
|
connected(): boolean;
|
|
44
45
|
clientId(): string | undefined;
|
|
45
|
-
close(error?: ICriticalContainerError): void;
|
|
46
46
|
applyStashedOp(content: string): Promise<unknown>;
|
|
47
47
|
reSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId): void;
|
|
48
48
|
isActiveConnection: () => boolean;
|
|
@@ -71,8 +71,6 @@ export declare class PendingStateManager implements IDisposable {
|
|
|
71
71
|
/** Used to stand in for batchStartCsn for messages that weren't submitted (so no CSN) */
|
|
72
72
|
private negativeCounter;
|
|
73
73
|
private readonly disposeOnce;
|
|
74
|
-
private isProcessingBatch;
|
|
75
|
-
private pendingBatchBeginMessage;
|
|
76
74
|
/** Used to ensure we don't replay ops on the same connection twice */
|
|
77
75
|
private clientIdFromLastReplay;
|
|
78
76
|
/**
|
|
@@ -109,25 +107,38 @@ export declare class PendingStateManager implements IDisposable {
|
|
|
109
107
|
*/
|
|
110
108
|
applyStashedOpsAt(seqNum?: number): Promise<void>;
|
|
111
109
|
/**
|
|
112
|
-
* Processes
|
|
113
|
-
*
|
|
114
|
-
* @param
|
|
115
|
-
* @param
|
|
116
|
-
*
|
|
110
|
+
* Processes an inbound batch of messages - May be local or remote.
|
|
111
|
+
*
|
|
112
|
+
* @param batch - The inbound batch of messages to process. Could be local or remote.
|
|
113
|
+
* @param local - true if we submitted this batch and expect corresponding pending messages
|
|
114
|
+
* @returns The inbound batch's messages with localOpMetadata "zipped" in.
|
|
115
|
+
*
|
|
116
|
+
* @remarks Closes the container if:
|
|
117
|
+
* - The batchStartCsn doesn't match for local batches
|
|
118
|
+
*/
|
|
119
|
+
processInboundBatch(batch: InboundBatch, local: boolean): {
|
|
120
|
+
message: InboundSequencedContainerRuntimeMessage;
|
|
121
|
+
localOpMetadata?: unknown;
|
|
122
|
+
}[];
|
|
123
|
+
/**
|
|
124
|
+
* Processes the incoming batch from the server that was submitted by this client.
|
|
125
|
+
* It verifies that messages are received in the right order and that the batch information is correct.
|
|
126
|
+
* @param batch - The inbound batch (originating from this client) to correlate with the pending local state
|
|
127
|
+
* @returns The inbound batch's messages with localOpMetadata "zipped" in.
|
|
117
128
|
*/
|
|
118
|
-
|
|
129
|
+
private processPendingLocalBatch;
|
|
119
130
|
/**
|
|
120
|
-
*
|
|
121
|
-
* @param
|
|
122
|
-
* @param
|
|
123
|
-
* @
|
|
131
|
+
* Processes the pending local copy of message that's been ack'd by the server.
|
|
132
|
+
* @param sequenceNumber - The sequenceNumber from the server corresponding to the next pending message.
|
|
133
|
+
* @param message - [optional] The entire incoming message, for comparing contents with the pending message for extra validation.
|
|
134
|
+
* @throws DataProcessingError if the pending message content doesn't match the incoming message content.
|
|
135
|
+
* @returns - The localOpMetadata of the next pending message, to be sent to whoever submitted the original message.
|
|
124
136
|
*/
|
|
125
|
-
private
|
|
137
|
+
private processNextPendingMessage;
|
|
126
138
|
/**
|
|
127
|
-
*
|
|
128
|
-
* @param message - The message that is being processed.
|
|
139
|
+
* Check if the incoming batch matches the batch info for the next pending message.
|
|
129
140
|
*/
|
|
130
|
-
private
|
|
141
|
+
private onLocalBatchBegin;
|
|
131
142
|
/**
|
|
132
143
|
* Called when the Container's connection state changes. If the Container gets connected, it replays all the pending
|
|
133
144
|
* states in its queue. This includes triggering resubmission of unacked ops.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"pendingStateManager.d.ts","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,23 +14,50 @@ const double_ended_queue_1 = __importDefault(require("double-ended-queue"));
|
|
|
14
14
|
const uuid_1 = require("uuid");
|
|
15
15
|
const metadata_js_1 = require("./metadata.js");
|
|
16
16
|
const index_js_1 = require("./opLifecycle/index.js");
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
message) {
|
|
17
|
+
function isEmptyBatchPendingMessage(message) {
|
|
18
|
+
const content = JSON.parse(message.content);
|
|
19
|
+
return content.type === "groupedBatch" && content.contents?.length === 0;
|
|
20
|
+
}
|
|
21
|
+
function buildPendingMessageContent(message) {
|
|
22
22
|
// IMPORTANT: Order matters here, this must match the order of the properties used
|
|
23
23
|
// when submitting the message.
|
|
24
24
|
const { type, contents, compatDetails } = message;
|
|
25
25
|
// Any properties that are not defined, won't be emitted by stringify.
|
|
26
26
|
return JSON.stringify({ type, contents, compatDetails });
|
|
27
27
|
}
|
|
28
|
+
function typesOfKeys(obj) {
|
|
29
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
30
|
+
acc[key] = typeof obj[key];
|
|
31
|
+
return acc;
|
|
32
|
+
}, {});
|
|
33
|
+
}
|
|
34
|
+
function scrubAndStringify(message) {
|
|
35
|
+
// Scrub the whole object in case there are unexpected keys
|
|
36
|
+
const scrubbed = typesOfKeys(message);
|
|
37
|
+
// For these known/expected keys, we can either drill in (for contents)
|
|
38
|
+
// or just use the value as-is (since it's not personal info)
|
|
39
|
+
scrubbed.contents = message.contents && typesOfKeys(message.contents);
|
|
40
|
+
scrubbed.compatDetails = message.compatDetails;
|
|
41
|
+
scrubbed.type = message.type;
|
|
42
|
+
return JSON.stringify(scrubbed);
|
|
43
|
+
}
|
|
28
44
|
function withoutLocalOpMetadata(message) {
|
|
29
45
|
return {
|
|
30
46
|
...message,
|
|
31
47
|
localOpMetadata: undefined,
|
|
32
48
|
};
|
|
33
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the effective batch ID for a pending message.
|
|
52
|
+
* If the batch ID is already present in the message's op metadata, return it.
|
|
53
|
+
* Otherwise, generate a new batch ID using the client ID and batch start CSN.
|
|
54
|
+
* @param pendingMessage - The pending message
|
|
55
|
+
* @returns The effective batch ID
|
|
56
|
+
*/
|
|
57
|
+
function getEffectiveBatchId(pendingMessage) {
|
|
58
|
+
return ((0, metadata_js_1.asBatchMetadata)(pendingMessage.opMetadata)?.batchId ??
|
|
59
|
+
(0, index_js_1.generateBatchId)(pendingMessage.batchInfo.clientId, pendingMessage.batchInfo.batchStartCsn));
|
|
60
|
+
}
|
|
34
61
|
/**
|
|
35
62
|
* PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been
|
|
36
63
|
* acknowledged by the server. It also maintains the batch information for both automatically and manually flushed
|
|
@@ -102,8 +129,6 @@ class PendingStateManager {
|
|
|
102
129
|
this.initialMessages.clear();
|
|
103
130
|
this.pendingMessages.clear();
|
|
104
131
|
});
|
|
105
|
-
// Indicates whether we are processing a batch.
|
|
106
|
-
this.isProcessingBatch = false;
|
|
107
132
|
this.dispose = () => this.disposeOnce.value;
|
|
108
133
|
if (stashedLocalState?.pendingStates) {
|
|
109
134
|
this.initialMessages.push(...stashedLocalState.pendingStates);
|
|
@@ -136,8 +161,8 @@ class PendingStateManager {
|
|
|
136
161
|
content,
|
|
137
162
|
localOpMetadata,
|
|
138
163
|
opMetadata,
|
|
139
|
-
// Note: We only
|
|
140
|
-
|
|
164
|
+
// Note: We only will read this off the first message, but put it on all for simplicity
|
|
165
|
+
batchInfo: { clientId, batchStartCsn, length: batch.length },
|
|
141
166
|
};
|
|
142
167
|
this.pendingMessages.push(pendingMessage);
|
|
143
168
|
}
|
|
@@ -161,7 +186,15 @@ class PendingStateManager {
|
|
|
161
186
|
}
|
|
162
187
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
163
188
|
const nextMessage = this.initialMessages.shift();
|
|
189
|
+
// Nothing to apply if the message is an empty batch.
|
|
190
|
+
// We still need to track it for resubmission.
|
|
164
191
|
try {
|
|
192
|
+
if (isEmptyBatchPendingMessage(nextMessage)) {
|
|
193
|
+
nextMessage.localOpMetadata = { emptyBatch: true }; // equivalent to applyStashedOp for empty batch
|
|
194
|
+
patchbatchInfo(nextMessage); // Back compat
|
|
195
|
+
this.pendingMessages.push(nextMessage);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
165
198
|
// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it
|
|
166
199
|
const localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);
|
|
167
200
|
if (!this.stateHandler.isAttached()) {
|
|
@@ -172,7 +205,7 @@ class PendingStateManager {
|
|
|
172
205
|
else {
|
|
173
206
|
nextMessage.localOpMetadata = localOpMetadata;
|
|
174
207
|
// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect
|
|
175
|
-
|
|
208
|
+
patchbatchInfo(nextMessage); // Back compat
|
|
176
209
|
this.pendingMessages.push(nextMessage);
|
|
177
210
|
}
|
|
178
211
|
}
|
|
@@ -182,105 +215,113 @@ class PendingStateManager {
|
|
|
182
215
|
}
|
|
183
216
|
}
|
|
184
217
|
/**
|
|
185
|
-
* Processes
|
|
186
|
-
*
|
|
187
|
-
* @param
|
|
188
|
-
* @param
|
|
189
|
-
*
|
|
218
|
+
* Processes an inbound batch of messages - May be local or remote.
|
|
219
|
+
*
|
|
220
|
+
* @param batch - The inbound batch of messages to process. Could be local or remote.
|
|
221
|
+
* @param local - true if we submitted this batch and expect corresponding pending messages
|
|
222
|
+
* @returns The inbound batch's messages with localOpMetadata "zipped" in.
|
|
223
|
+
*
|
|
224
|
+
* @remarks Closes the container if:
|
|
225
|
+
* - The batchStartCsn doesn't match for local batches
|
|
190
226
|
*/
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
(0, internal_1.assert)(pendingMessage !== undefined, 0x169 /* "No pending message found for this remote message" */);
|
|
195
|
-
// This may be the start of a batch.
|
|
196
|
-
this.maybeProcessBatchBegin(message, batchStartCsn, pendingMessage);
|
|
197
|
-
pendingMessage.sequenceNumber = message.sequenceNumber;
|
|
198
|
-
this.savedOps.push(withoutLocalOpMetadata(pendingMessage));
|
|
199
|
-
this.pendingMessages.shift();
|
|
200
|
-
const messageContent = buildPendingMessageContent(message);
|
|
201
|
-
// Stringified content should match
|
|
202
|
-
if (pendingMessage.content !== messageContent) {
|
|
203
|
-
this.stateHandler.close(internal_2.DataProcessingError.create("pending local message content mismatch", "unexpectedAckReceived", message, {
|
|
204
|
-
expectedMessageType: JSON.parse(pendingMessage.content).type,
|
|
205
|
-
}));
|
|
206
|
-
return;
|
|
227
|
+
processInboundBatch(batch, local) {
|
|
228
|
+
if (local) {
|
|
229
|
+
return this.processPendingLocalBatch(batch);
|
|
207
230
|
}
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
return pendingMessage.localOpMetadata;
|
|
231
|
+
// No localOpMetadata for remote messages
|
|
232
|
+
return batch.messages.map((message) => ({ message }));
|
|
211
233
|
}
|
|
212
234
|
/**
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* @param
|
|
216
|
-
* @
|
|
235
|
+
* Processes the incoming batch from the server that was submitted by this client.
|
|
236
|
+
* It verifies that messages are received in the right order and that the batch information is correct.
|
|
237
|
+
* @param batch - The inbound batch (originating from this client) to correlate with the pending local state
|
|
238
|
+
* @returns The inbound batch's messages with localOpMetadata "zipped" in.
|
|
217
239
|
*/
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
240
|
+
processPendingLocalBatch(batch) {
|
|
241
|
+
this.onLocalBatchBegin(batch);
|
|
242
|
+
// Empty batch
|
|
243
|
+
if (batch.messages.length === 0) {
|
|
244
|
+
(0, internal_1.assert)(batch.emptyBatchSequenceNumber !== undefined, 0x9fb /* Expected sequence number for empty batch */);
|
|
245
|
+
const localOpMetadata = this.processNextPendingMessage(batch.emptyBatchSequenceNumber);
|
|
246
|
+
(0, internal_1.assert)((0, metadata_js_1.asEmptyBatchLocalOpMetadata)(localOpMetadata)?.emptyBatch === true, 0xa20 /* Expected empty batch marker */);
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
return batch.messages.map((message) => ({
|
|
250
|
+
message,
|
|
251
|
+
localOpMetadata: this.processNextPendingMessage(message.sequenceNumber, message),
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Processes the pending local copy of message that's been ack'd by the server.
|
|
256
|
+
* @param sequenceNumber - The sequenceNumber from the server corresponding to the next pending message.
|
|
257
|
+
* @param message - [optional] The entire incoming message, for comparing contents with the pending message for extra validation.
|
|
258
|
+
* @throws DataProcessingError if the pending message content doesn't match the incoming message content.
|
|
259
|
+
* @returns - The localOpMetadata of the next pending message, to be sent to whoever submitted the original message.
|
|
260
|
+
*/
|
|
261
|
+
processNextPendingMessage(sequenceNumber, message) {
|
|
262
|
+
const pendingMessage = this.pendingMessages.peekFront();
|
|
263
|
+
(0, internal_1.assert)(pendingMessage !== undefined, 0x169 /* "No pending message found for this remote message" */);
|
|
264
|
+
pendingMessage.sequenceNumber = sequenceNumber;
|
|
265
|
+
this.savedOps.push(withoutLocalOpMetadata(pendingMessage));
|
|
266
|
+
this.pendingMessages.shift();
|
|
267
|
+
// message is undefined in the Empty Batch case,
|
|
268
|
+
// because we don't have an incoming message to compare and pendingMessage is just a placeholder anyway.
|
|
269
|
+
if (message !== undefined) {
|
|
270
|
+
const messageContent = buildPendingMessageContent(message);
|
|
271
|
+
// Stringified content should match
|
|
272
|
+
if (pendingMessage.content !== messageContent) {
|
|
273
|
+
const pendingContentObj = JSON.parse(pendingMessage.content);
|
|
274
|
+
const incomingContentObj = JSON.parse(messageContent);
|
|
275
|
+
const contentsMatch = pendingContentObj.contents === incomingContentObj.contents ||
|
|
276
|
+
(pendingContentObj.contents !== undefined &&
|
|
277
|
+
incomingContentObj.contents !== undefined &&
|
|
278
|
+
JSON.stringify(pendingContentObj.contents) ===
|
|
279
|
+
JSON.stringify(incomingContentObj.contents));
|
|
280
|
+
this.logger.sendErrorEvent({
|
|
281
|
+
eventName: "unexpectedAckReceived",
|
|
224
282
|
details: {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
messageBatchMetadata: message.metadata?.batch,
|
|
229
|
-
pendingMessageBatchMetadata: pendingMessage.opMetadata?.batch,
|
|
283
|
+
pendingContentScrubbed: scrubAndStringify(pendingContentObj),
|
|
284
|
+
incomingContentScrubbed: scrubAndStringify(incomingContentObj),
|
|
285
|
+
contentsMatch,
|
|
230
286
|
},
|
|
231
|
-
messageDetails: (0, internal_2.extractSafePropertiesFromMessage)(message),
|
|
232
287
|
});
|
|
288
|
+
throw internal_2.DataProcessingError.create("pending local message content mismatch", "unexpectedAckReceived", message);
|
|
233
289
|
}
|
|
234
290
|
}
|
|
235
|
-
|
|
236
|
-
if (message.metadata?.batch) {
|
|
237
|
-
// We should not already be processing a batch and there should be no pending batch begin message.
|
|
238
|
-
(0, internal_1.assert)(!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined, 0x16b /* "The pending batch state indicates we are already processing a batch" */);
|
|
239
|
-
// Set the pending batch state indicating we have started processing a batch.
|
|
240
|
-
this.pendingBatchBeginMessage = message;
|
|
241
|
-
this.isProcessingBatch = true;
|
|
242
|
-
}
|
|
291
|
+
return pendingMessage.localOpMetadata;
|
|
243
292
|
}
|
|
244
293
|
/**
|
|
245
|
-
*
|
|
246
|
-
* @param message - The message that is being processed.
|
|
294
|
+
* Check if the incoming batch matches the batch info for the next pending message.
|
|
247
295
|
*/
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
pendingMessagesCount: this.pendingMessagesCount,
|
|
278
|
-
}));
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
// Clear the pending batch state now that we have processed the entire batch.
|
|
282
|
-
this.pendingBatchBeginMessage = undefined;
|
|
283
|
-
this.isProcessingBatch = false;
|
|
296
|
+
onLocalBatchBegin(batch) {
|
|
297
|
+
// Get the next message from the pending queue. Verify a message exists.
|
|
298
|
+
const pendingMessage = this.pendingMessages.peekFront();
|
|
299
|
+
(0, internal_1.assert)(pendingMessage !== undefined, 0xa21 /* No pending message found as we start processing this remote batch */);
|
|
300
|
+
// Note: This could be undefined if this batch became empty on resubmit.
|
|
301
|
+
// In this case the next pending message is an empty batch marker.
|
|
302
|
+
// Empty batches became empty on Resubmit, and submit them and track them in case
|
|
303
|
+
// a different fork of this container also submitted the same batch (and it may not be empty for that fork).
|
|
304
|
+
const firstMessage = batch.messages.length > 0 ? batch.messages[0] : undefined;
|
|
305
|
+
const expectedPendingBatchLength = batch.messages.length === 0 ? 1 : batch.messages.length;
|
|
306
|
+
// We expect the incoming batch to be of the same length, starting at the same clientSequenceNumber,
|
|
307
|
+
// as the batch we originally submitted.
|
|
308
|
+
// We have another later check to compare the message contents, which we'd expect to fail if this check does,
|
|
309
|
+
// so we don't throw here, merely log. In a later release this check may replace that one.
|
|
310
|
+
if (pendingMessage.batchInfo.batchStartCsn !== batch.batchStartCsn ||
|
|
311
|
+
(pendingMessage.batchInfo.length >= 0 && // -1 length is back compat and isn't suitable for this check
|
|
312
|
+
pendingMessage.batchInfo.length !== expectedPendingBatchLength)) {
|
|
313
|
+
this.logger?.sendErrorEvent({
|
|
314
|
+
eventName: "BatchInfoMismatch",
|
|
315
|
+
details: {
|
|
316
|
+
pendingBatchCsn: pendingMessage.batchInfo.batchStartCsn,
|
|
317
|
+
batchStartCsn: batch.batchStartCsn,
|
|
318
|
+
pendingBatchLength: pendingMessage.batchInfo.length,
|
|
319
|
+
batchLength: batch.messages.length,
|
|
320
|
+
pendingMessageBatchMetadata: (0, metadata_js_1.asBatchMetadata)(pendingMessage.opMetadata)?.batch,
|
|
321
|
+
messageBatchMetadata: (0, metadata_js_1.asBatchMetadata)(firstMessage?.metadata)?.batch,
|
|
322
|
+
},
|
|
323
|
+
messageDetails: firstMessage && (0, internal_2.extractSafePropertiesFromMessage)(firstMessage),
|
|
324
|
+
});
|
|
284
325
|
}
|
|
285
326
|
}
|
|
286
327
|
/**
|
|
@@ -306,10 +347,12 @@ class PendingStateManager {
|
|
|
306
347
|
const batchMetadataFlag = (0, metadata_js_1.asBatchMetadata)(pendingMessage.opMetadata)?.batch;
|
|
307
348
|
(0, internal_1.assert)(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);
|
|
308
349
|
// The next message starts a batch (possibly single-message), and we'll need its batchId.
|
|
309
|
-
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
350
|
+
const batchId = getEffectiveBatchId(pendingMessage);
|
|
351
|
+
// Resubmit no messages, with the batchId. Will result in another empty batch marker.
|
|
352
|
+
if ((0, metadata_js_1.asEmptyBatchLocalOpMetadata)(pendingMessage.localOpMetadata)?.emptyBatch === true) {
|
|
353
|
+
this.stateHandler.reSubmitBatch([], batchId);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
313
356
|
/**
|
|
314
357
|
* We must preserve the distinct batches on resubmit.
|
|
315
358
|
* Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will
|
|
@@ -362,12 +405,12 @@ class PendingStateManager {
|
|
|
362
405
|
}
|
|
363
406
|
}
|
|
364
407
|
exports.PendingStateManager = PendingStateManager;
|
|
365
|
-
/** For back-compat if trying to apply stashed ops that pre-date
|
|
366
|
-
function
|
|
367
|
-
const
|
|
368
|
-
if (
|
|
408
|
+
/** For back-compat if trying to apply stashed ops that pre-date batchInfo */
|
|
409
|
+
function patchbatchInfo(message) {
|
|
410
|
+
const batchInfo = message.batchInfo;
|
|
411
|
+
if (batchInfo === undefined) {
|
|
369
412
|
// Using uuid guarantees uniqueness, retaining existing behavior
|
|
370
|
-
message.
|
|
413
|
+
message.batchInfo = { clientId: (0, uuid_1.v4)(), batchStartCsn: -1, length: -1 };
|
|
371
414
|
}
|
|
372
415
|
}
|
|
373
416
|
//# sourceMappingURL=pendingStateManager.js.map
|