@fluidframework/container-runtime 2.0.0-internal.7.2.1 → 2.0.0-internal.7.3.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 +1 -2
- package/dist/batchTracker.d.ts +1 -0
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +1 -1
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +5 -11
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +26 -32
- package/dist/containerRuntime.js.map +1 -1
- package/dist/gc/gcConfigs.d.ts.map +1 -1
- package/dist/gc/gcConfigs.js +4 -1
- package/dist/gc/gcConfigs.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +7 -2
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js +8 -3
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/gc/gcTelemetry.d.ts +1 -1
- package/dist/gc/gcTelemetry.d.ts.map +1 -1
- package/dist/gc/gcTelemetry.js +20 -8
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/gc/index.d.ts +1 -1
- package/dist/gc/index.d.ts.map +1 -1
- package/dist/gc/index.js +3 -1
- package/dist/gc/index.js.map +1 -1
- package/dist/messageTypes.d.ts +3 -6
- package/dist/messageTypes.d.ts.map +1 -1
- package/dist/messageTypes.js.map +1 -1
- package/dist/metadata.d.ts +6 -0
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts +10 -2
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +33 -4
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +2 -1
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +23 -1
- package/dist/opLifecycle/outbox.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 +0 -1
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +1 -11
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/scheduleManager.d.ts +1 -0
- package/dist/scheduleManager.d.ts.map +1 -1
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/batchTracker.d.ts +1 -0
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +1 -1
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +5 -11
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +26 -32
- package/lib/containerRuntime.js.map +1 -1
- package/lib/gc/gcConfigs.d.ts.map +1 -1
- package/lib/gc/gcConfigs.js +5 -2
- package/lib/gc/gcConfigs.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +7 -2
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js +7 -2
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/gc/gcTelemetry.d.ts +1 -1
- package/lib/gc/gcTelemetry.d.ts.map +1 -1
- package/lib/gc/gcTelemetry.js +21 -9
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/gc/index.d.ts +1 -1
- package/lib/gc/index.d.ts.map +1 -1
- package/lib/gc/index.js +1 -1
- package/lib/gc/index.js.map +1 -1
- package/lib/messageTypes.d.ts +3 -6
- package/lib/messageTypes.d.ts.map +1 -1
- package/lib/messageTypes.js.map +1 -1
- package/lib/metadata.d.ts +6 -0
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts +10 -2
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +33 -4
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +2 -1
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +23 -1
- package/lib/opLifecycle/outbox.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 +0 -1
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +1 -11
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/scheduleManager.d.ts +1 -0
- package/lib/scheduleManager.d.ts.map +1 -1
- package/package.json +25 -25
- package/src/connectionTelemetry.ts +1 -1
- package/src/containerRuntime.ts +41 -39
- package/src/gc/gcConfigs.ts +8 -2
- package/src/gc/gcDefinitions.ts +10 -2
- package/src/gc/gcTelemetry.ts +28 -17
- package/src/gc/index.ts +2 -0
- package/src/messageTypes.ts +2 -7
- package/src/metadata.ts +7 -0
- package/src/opLifecycle/opGroupingManager.ts +47 -3
- package/src/opLifecycle/outbox.ts +38 -2
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +2 -13
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pendingStateManager.js","sourceRoot":"","sources":["../src/pendingStateManager.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 Deque from \"double-ended-queue\";\n\nimport { IDisposable } from \"@fluidframework/core-interfaces\";\nimport { assert, Lazy } from \"@fluidframework/core-utils\";\nimport { ICriticalContainerError } from \"@fluidframework/container-definitions\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/protocol-definitions\";\nimport { DataProcessingError, ITelemetryLoggerExt } from \"@fluidframework/telemetry-utils\";\n\nimport { ContainerMessageType, InboundSequencedContainerRuntimeMessage } from \"./messageTypes\";\nimport { pkgVersion } from \"./packageVersion\";\nimport { IBatchMetadata } from \"./metadata\";\n\n/**\n * This represents a message that has been submitted and is added to the pending queue when `submit` is called on the\n * ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.\n */\nexport interface IPendingMessage {\n\ttype: \"message\";\n\tclientSequenceNumber: number;\n\treferenceSequenceNumber: number;\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n}\n\nexport interface IPendingLocalState {\n\t/**\n\t * list of pending states, including ops and batch information\n\t */\n\tpendingStates: IPendingMessage[];\n}\n\nexport interface IPendingBatchMessage {\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n}\n\nexport interface IRuntimeStateHandler {\n\tconnected(): boolean;\n\tclientId(): string | undefined;\n\tclose(error?: ICriticalContainerError): void;\n\tapplyStashedOp(content: string): Promise<unknown>;\n\treSubmit(message: IPendingBatchMessage): void;\n\treSubmitBatch(batch: IPendingBatchMessage[]): void;\n\tisActiveConnection: () => boolean;\n}\n\n/** Union of keys of T */\ntype KeysOfUnion<T extends object> = T extends T ? keyof T : never;\n/** *Partial* type all possible combinations of properties and values of union T.\n * This loosens typing allowing access to all possible properties without\n * narrowing.\n */\ntype AnyComboFromUnion<T extends object> = { [P in KeysOfUnion<T>]?: T[P] };\n\nfunction buildPendingMessageContent(\n\t// AnyComboFromUnion is needed need to gain access to compatDetails that\n\t// is only defined for some cases.\n\tmessage: AnyComboFromUnion<InboundSequencedContainerRuntimeMessage>,\n): string {\n\t// IMPORTANT: Order matters here, this must match the order of the properties used\n\t// when submitting the message.\n\tconst { type, contents, compatDetails } = message;\n\t// Any properties that are not defined, won't be emitted by stringify.\n\treturn JSON.stringify({ type, contents, compatDetails });\n}\n\n/**\n * PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been\n * acknowledged by the server. It also maintains the batch information for both automatically and manually flushed\n * batches along with the messages.\n * When the Container reconnects, it replays the pending states, which includes manual flushing\n * of messages and triggering resubmission of unacked ops.\n *\n * It verifies that all the ops are acked, are received in the right order and batch information is correct.\n */\nexport class PendingStateManager implements IDisposable {\n\tprivate readonly pendingMessages = new Deque<IPendingMessage>();\n\tprivate readonly initialMessages = new Deque<IPendingMessage>();\n\n\t/**\n\t * Sequenced local ops that are saved when stashing since pending ops may depend on them\n\t */\n\tprivate savedOps: IPendingMessage[] = [];\n\n\tprivate readonly disposeOnce = new Lazy<void>(() => {\n\t\tthis.initialMessages.clear();\n\t\tthis.pendingMessages.clear();\n\t});\n\n\t// Indicates whether we are processing a batch.\n\tprivate isProcessingBatch: boolean = false;\n\n\t// This stores the first message in the batch that we are processing. This is used to verify that we get\n\t// the correct batch metadata.\n\tprivate pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;\n\n\tprivate clientId: string | undefined;\n\n\t/**\n\t * The pending messages count. Includes `pendingMessages` and `initialMessages` to keep in sync with\n\t * 'hasPendingMessages'.\n\t */\n\tpublic get pendingMessagesCount(): number {\n\t\treturn this.pendingMessages.length + this.initialMessages.length;\n\t}\n\n\t/**\n\t * Called to check if there are any pending messages in the pending message queue.\n\t * @returns A boolean indicating whether there are messages or not.\n\t */\n\tpublic hasPendingMessages(): boolean {\n\t\treturn this.pendingMessagesCount !== 0;\n\t}\n\n\tpublic getLocalState(): IPendingLocalState | undefined {\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x2e9 /* \"Must call getLocalState() after applying initial states\" */,\n\t\t);\n\t\tif (!this.pendingMessages.isEmpty()) {\n\t\t\treturn {\n\t\t\t\tpendingStates: [...this.savedOps, ...this.pendingMessages.toArray()].map(\n\t\t\t\t\t(message) => {\n\t\t\t\t\t\tlet content = message.content;\n\t\t\t\t\t\tconst parsedContent = JSON.parse(content);\n\t\t\t\t\t\t// IdAllocations need their localOpMetadata stashed in the contents\n\t\t\t\t\t\t// of the op to correctly resume the session when processing stashed ops\n\t\t\t\t\t\tif (parsedContent.type === ContainerMessageType.IdAllocation) {\n\t\t\t\t\t\t\tparsedContent.contents.stashedState = message.localOpMetadata;\n\t\t\t\t\t\t\tcontent = JSON.stringify(parsedContent);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// delete localOpMetadata since it may not be serializable\n\t\t\t\t\t\t// and will be regenerated by applyStashedOp()\n\t\t\t\t\t\treturn { ...message, content, localOpMetadata: undefined };\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\t}\n\n\tconstructor(\n\t\tprivate readonly stateHandler: IRuntimeStateHandler,\n\t\tinitialLocalState: IPendingLocalState | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt | undefined,\n\t) {\n\t\tif (initialLocalState?.pendingStates) {\n\t\t\tthis.initialMessages.push(...initialLocalState.pendingStates);\n\t\t}\n\t}\n\n\tpublic get disposed() {\n\t\treturn this.disposeOnce.evaluated;\n\t}\n\tpublic readonly dispose = () => this.disposeOnce.value;\n\n\t/**\n\t * Called when a message is submitted locally. Adds the message and the associated details to the pending state\n\t * queue.\n\t * @param type - The container message type.\n\t * @param content - The message content.\n\t * @param localOpMetadata - The local metadata associated with the message.\n\t */\n\tpublic onSubmitMessage(\n\t\tcontent: string,\n\t\treferenceSequenceNumber: number,\n\t\tlocalOpMetadata: unknown,\n\t\topMetadata: Record<string, unknown> | undefined,\n\t) {\n\t\tconst pendingMessage: IPendingMessage = {\n\t\t\ttype: \"message\",\n\t\t\tclientSequenceNumber: -1, // dummy value (not to be used anywhere)\n\t\t\treferenceSequenceNumber,\n\t\t\tcontent,\n\t\t\tlocalOpMetadata,\n\t\t\topMetadata,\n\t\t};\n\n\t\tthis.pendingMessages.push(pendingMessage);\n\t}\n\n\t/**\n\t * Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted\n\t * @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.\n\t */\n\tpublic async applyStashedOpsAt(seqNum?: number) {\n\t\t// apply stashed ops at sequence number\n\t\twhile (!this.initialMessages.isEmpty()) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tconst nextMessage = this.initialMessages.peekFront()!;\n\t\t\tif (seqNum !== undefined) {\n\t\t\t\tif (nextMessage.referenceSequenceNumber > seqNum) {\n\t\t\t\t\tbreak; // nothing left to do at this sequence number\n\t\t\t\t}\n\t\t\t\tif (nextMessage.referenceSequenceNumber < seqNum) {\n\t\t\t\t\tthrow new Error(\"loaded from snapshot too recent to apply stashed ops\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it\n\t\t\t\tconst localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);\n\t\t\t\tnextMessage.localOpMetadata = localOpMetadata;\n\t\t\t} catch (error) {\n\t\t\t\tthrow DataProcessingError.wrapIfUnrecognized(error, \"applyStashedOp\", nextMessage);\n\t\t\t}\n\n\t\t\t// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tthis.pendingMessages.push(this.initialMessages.shift()!);\n\t\t}\n\t}\n\n\t/**\n\t * Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that\n\t * the batch information was preserved for batch messages.\n\t * @param message - The message that got ack'd and needs to be processed.\n\t */\n\tpublic processPendingLocalMessage(message: InboundSequencedContainerRuntimeMessage): unknown {\n\t\t// Pre-processing part - This may be the start of a batch.\n\t\tthis.maybeProcessBatchBegin(message);\n\n\t\t// Get the next message from the pending queue. Verify a message exists.\n\t\tconst pendingMessage = this.pendingMessages.peekFront();\n\t\tassert(\n\t\t\tpendingMessage !== undefined,\n\t\t\t0x169 /* \"No pending message found for this remote message\" */,\n\t\t);\n\t\tthis.savedOps.push(pendingMessage);\n\n\t\tthis.pendingMessages.shift();\n\n\t\tconst messageContent = buildPendingMessageContent(message);\n\n\t\t// Stringified content should match\n\t\tif (pendingMessage.content !== messageContent) {\n\t\t\tthis.stateHandler.close(\n\t\t\t\tDataProcessingError.create(\n\t\t\t\t\t\"pending local message content mismatch\",\n\t\t\t\t\t\"unexpectedAckReceived\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\texpectedMessageType: JSON.parse(pendingMessage.content).type,\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Post-processing part - If we are processing a batch then this could be the last message in the batch.\n\t\tthis.maybeProcessBatchEnd(message);\n\n\t\treturn pendingMessage.localOpMetadata;\n\t}\n\n\t/**\n\t * This message could be the first message in batch. If so, set batch state marking the beginning of a batch.\n\t * @param message - The message that is being processed.\n\t */\n\tprivate maybeProcessBatchBegin(message: ISequencedDocumentMessage) {\n\t\t// This message is the first in a batch if the \"batch\" property on the metadata is set to true\n\t\tif ((message.metadata as IBatchMetadata | undefined)?.batch) {\n\t\t\t// We should not already be processing a batch and there should be no pending batch begin message.\n\t\t\tassert(\n\t\t\t\t!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,\n\t\t\t\t0x16b /* \"The pending batch state indicates we are already processing a batch\" */,\n\t\t\t);\n\n\t\t\t// Set the pending batch state indicating we have started processing a batch.\n\t\t\tthis.pendingBatchBeginMessage = message;\n\t\t\tthis.isProcessingBatch = true;\n\t\t}\n\t}\n\n\t/**\n\t * This message could be the last message in batch. If so, clear batch state since the batch is complete.\n\t * @param message - The message that is being processed.\n\t */\n\tprivate maybeProcessBatchEnd(message: ISequencedDocumentMessage) {\n\t\tif (!this.isProcessingBatch) {\n\t\t\treturn;\n\t\t}\n\n\t\t// There should be a pending batch begin message.\n\t\tassert(\n\t\t\tthis.pendingBatchBeginMessage !== undefined,\n\t\t\t0x16d /* \"There is no pending batch begin message\" */,\n\t\t);\n\n\t\tconst batchEndMetadata = (message.metadata as IBatchMetadata | undefined)?.batch;\n\t\tif (this.pendingMessages.isEmpty() || batchEndMetadata === false) {\n\t\t\t// Get the batch begin metadata from the first message in the batch.\n\t\t\tconst batchBeginMetadata = (\n\t\t\t\tthis.pendingBatchBeginMessage.metadata as IBatchMetadata | undefined\n\t\t\t)?.batch;\n\n\t\t\t// There could be just a single message in the batch. If so, it should not have any batch metadata. If there\n\t\t\t// are multiple messages in the batch, verify that we got the correct batch begin and end metadata.\n\t\t\tif (this.pendingBatchBeginMessage === message) {\n\t\t\t\tassert(\n\t\t\t\t\tbatchBeginMetadata === undefined,\n\t\t\t\t\t0x16e /* \"Batch with single message should not have batch metadata\" */,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tif (batchBeginMetadata !== true || batchEndMetadata !== false) {\n\t\t\t\t\tthis.stateHandler.close(\n\t\t\t\t\t\tDataProcessingError.create(\n\t\t\t\t\t\t\t\"Pending batch inconsistency\", // Formerly known as asserts 0x16f and 0x170\n\t\t\t\t\t\t\t\"processPendingLocalMessage\",\n\t\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\t\t\t\t\tbatchClientId:\n\t\t\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n\t\t\t\t\t\t\t\t\tthis.pendingBatchBeginMessage.clientId === null\n\t\t\t\t\t\t\t\t\t\t? \"null\"\n\t\t\t\t\t\t\t\t\t\t: this.pendingBatchBeginMessage.clientId,\n\t\t\t\t\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t\t\t\t\t\thasBatchStart: batchBeginMetadata === true,\n\t\t\t\t\t\t\t\thasBatchEnd: batchEndMetadata === false,\n\t\t\t\t\t\t\t\tmessageType: message.type,\n\t\t\t\t\t\t\t\tpendingMessagesCount: this.pendingMessagesCount,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Clear the pending batch state now that we have processed the entire batch.\n\t\t\tthis.pendingBatchBeginMessage = undefined;\n\t\t\tthis.isProcessingBatch = false;\n\t\t}\n\t}\n\n\t/**\n\t * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending\n\t * states in its queue. This includes triggering resubmission of unacked ops.\n\t */\n\tpublic replayPendingStates() {\n\t\tassert(\n\t\t\tthis.stateHandler.connected(),\n\t\t\t0x172 /* \"The connection state is not consistent with the runtime\" */,\n\t\t);\n\n\t\t// This assert suggests we are about to send same ops twice, which will result in data loss.\n\t\tassert(\n\t\t\tthis.clientId !== this.stateHandler.clientId(),\n\t\t\t0x173 /* \"replayPendingStates called twice for same clientId!\" */,\n\t\t);\n\t\tthis.clientId = this.stateHandler.clientId();\n\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x174 /* \"initial states should be empty before replaying pending\" */,\n\t\t);\n\n\t\tconst initialPendingMessagesCount = this.pendingMessages.length;\n\t\tlet remainingPendingMessagesCount = this.pendingMessages.length;\n\n\t\t// Process exactly `pendingMessagesCount` items in the queue as it represents the number of messages that were\n\t\t// pending when we connected. This is important because the `reSubmitFn` might add more items in the queue\n\t\t// which must not be replayed.\n\t\twhile (remainingPendingMessagesCount > 0) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tlet pendingMessage = this.pendingMessages.shift()!;\n\t\t\tremainingPendingMessagesCount--;\n\t\t\tassert(\n\t\t\t\tpendingMessage.opMetadata?.batch !== false,\n\t\t\t\t0x41b /* We cannot process batches in chunks */,\n\t\t\t);\n\n\t\t\t/**\n\t\t\t * We want to ensure grouped messages get processed in a batch.\n\t\t\t * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will\n\t\t\t * either receive the whole batch ack or nothing at all.\n\t\t\t */\n\t\t\tif (pendingMessage.opMetadata?.batch) {\n\t\t\t\tassert(\n\t\t\t\t\tremainingPendingMessagesCount > 0,\n\t\t\t\t\t0x554 /* Last pending message cannot be a batch begin */,\n\t\t\t\t);\n\n\t\t\t\tconst batch: IPendingBatchMessage[] = [];\n\n\t\t\t\t// check is >= because batch end may be last pending message\n\t\t\t\twhile (remainingPendingMessagesCount >= 0) {\n\t\t\t\t\tbatch.push({\n\t\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (pendingMessage.opMetadata?.batch === false) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tassert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);\n\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\t\tpendingMessage = this.pendingMessages.shift()!;\n\t\t\t\t\tremainingPendingMessagesCount--;\n\t\t\t\t\tassert(\n\t\t\t\t\t\tpendingMessage.opMetadata?.batch !== true,\n\t\t\t\t\t\t0x556 /* Batch start needs a corresponding batch end */,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tthis.stateHandler.reSubmitBatch(batch);\n\t\t\t} else {\n\t\t\t\tthis.stateHandler.reSubmit({\n\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// pending ops should no longer depend on previous sequenced local ops after resubmit\n\t\tthis.savedOps = [];\n\n\t\t// We replayPendingStates on read connections too - we expect these to get nack'd though, and to then reconnect\n\t\t// on a write connection and replay again. This filters out the replay that happens on the read connection so\n\t\t// we only see the replays on write connections (that have a chance to go through).\n\t\tif (this.stateHandler.isActiveConnection()) {\n\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\teventName: \"PendingStatesReplayed\",\n\t\t\t\tcount: initialPendingMessagesCount,\n\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t});\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pendingStateManager.js","sourceRoot":"","sources":["../src/pendingStateManager.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 Deque from \"double-ended-queue\";\n\nimport { IDisposable } from \"@fluidframework/core-interfaces\";\nimport { assert, Lazy } from \"@fluidframework/core-utils\";\nimport { ICriticalContainerError } from \"@fluidframework/container-definitions\";\nimport { ISequencedDocumentMessage } from \"@fluidframework/protocol-definitions\";\nimport { DataProcessingError, ITelemetryLoggerExt } from \"@fluidframework/telemetry-utils\";\n\nimport { InboundSequencedContainerRuntimeMessage } from \"./messageTypes\";\nimport { pkgVersion } from \"./packageVersion\";\nimport { IBatchMetadata } from \"./metadata\";\n\n/**\n * This represents a message that has been submitted and is added to the pending queue when `submit` is called on the\n * ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.\n */\nexport interface IPendingMessage {\n\ttype: \"message\";\n\treferenceSequenceNumber: number;\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n}\n\nexport interface IPendingLocalState {\n\t/**\n\t * list of pending states, including ops and batch information\n\t */\n\tpendingStates: IPendingMessage[];\n}\n\nexport interface IPendingBatchMessage {\n\tcontent: string;\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n}\n\nexport interface IRuntimeStateHandler {\n\tconnected(): boolean;\n\tclientId(): string | undefined;\n\tclose(error?: ICriticalContainerError): void;\n\tapplyStashedOp(content: string): Promise<unknown>;\n\treSubmit(message: IPendingBatchMessage): void;\n\treSubmitBatch(batch: IPendingBatchMessage[]): void;\n\tisActiveConnection: () => boolean;\n}\n\n/** Union of keys of T */\ntype KeysOfUnion<T extends object> = T extends T ? keyof T : never;\n/** *Partial* type all possible combinations of properties and values of union T.\n * This loosens typing allowing access to all possible properties without\n * narrowing.\n */\ntype AnyComboFromUnion<T extends object> = { [P in KeysOfUnion<T>]?: T[P] };\n\nfunction buildPendingMessageContent(\n\t// AnyComboFromUnion is needed need to gain access to compatDetails that\n\t// is only defined for some cases.\n\tmessage: AnyComboFromUnion<InboundSequencedContainerRuntimeMessage>,\n): string {\n\t// IMPORTANT: Order matters here, this must match the order of the properties used\n\t// when submitting the message.\n\tconst { type, contents, compatDetails } = message;\n\t// Any properties that are not defined, won't be emitted by stringify.\n\treturn JSON.stringify({ type, contents, compatDetails });\n}\n\n/**\n * PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been\n * acknowledged by the server. It also maintains the batch information for both automatically and manually flushed\n * batches along with the messages.\n * When the Container reconnects, it replays the pending states, which includes manual flushing\n * of messages and triggering resubmission of unacked ops.\n *\n * It verifies that all the ops are acked, are received in the right order and batch information is correct.\n */\nexport class PendingStateManager implements IDisposable {\n\tprivate readonly pendingMessages = new Deque<IPendingMessage>();\n\tprivate readonly initialMessages = new Deque<IPendingMessage>();\n\n\t/**\n\t * Sequenced local ops that are saved when stashing since pending ops may depend on them\n\t */\n\tprivate savedOps: IPendingMessage[] = [];\n\n\tprivate readonly disposeOnce = new Lazy<void>(() => {\n\t\tthis.initialMessages.clear();\n\t\tthis.pendingMessages.clear();\n\t});\n\n\t// Indicates whether we are processing a batch.\n\tprivate isProcessingBatch: boolean = false;\n\n\t// This stores the first message in the batch that we are processing. This is used to verify that we get\n\t// the correct batch metadata.\n\tprivate pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;\n\n\tprivate clientId: string | undefined;\n\n\t/**\n\t * The pending messages count. Includes `pendingMessages` and `initialMessages` to keep in sync with\n\t * 'hasPendingMessages'.\n\t */\n\tpublic get pendingMessagesCount(): number {\n\t\treturn this.pendingMessages.length + this.initialMessages.length;\n\t}\n\n\t/**\n\t * Called to check if there are any pending messages in the pending message queue.\n\t * @returns A boolean indicating whether there are messages or not.\n\t */\n\tpublic hasPendingMessages(): boolean {\n\t\treturn this.pendingMessagesCount !== 0;\n\t}\n\n\tpublic getLocalState(): IPendingLocalState | undefined {\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x2e9 /* \"Must call getLocalState() after applying initial states\" */,\n\t\t);\n\t\tif (!this.pendingMessages.isEmpty()) {\n\t\t\treturn {\n\t\t\t\tpendingStates: [...this.savedOps, ...this.pendingMessages.toArray()].map(\n\t\t\t\t\t(message) => {\n\t\t\t\t\t\t// delete localOpMetadata since it may not be serializable\n\t\t\t\t\t\t// and will be regenerated by applyStashedOp()\n\t\t\t\t\t\treturn { ...message, localOpMetadata: undefined };\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\t}\n\n\tconstructor(\n\t\tprivate readonly stateHandler: IRuntimeStateHandler,\n\t\tinitialLocalState: IPendingLocalState | undefined,\n\t\tprivate readonly logger: ITelemetryLoggerExt | undefined,\n\t) {\n\t\tif (initialLocalState?.pendingStates) {\n\t\t\tthis.initialMessages.push(...initialLocalState.pendingStates);\n\t\t}\n\t}\n\n\tpublic get disposed() {\n\t\treturn this.disposeOnce.evaluated;\n\t}\n\tpublic readonly dispose = () => this.disposeOnce.value;\n\n\t/**\n\t * Called when a message is submitted locally. Adds the message and the associated details to the pending state\n\t * queue.\n\t * @param type - The container message type.\n\t * @param content - The message content.\n\t * @param localOpMetadata - The local metadata associated with the message.\n\t */\n\tpublic onSubmitMessage(\n\t\tcontent: string,\n\t\treferenceSequenceNumber: number,\n\t\tlocalOpMetadata: unknown,\n\t\topMetadata: Record<string, unknown> | undefined,\n\t) {\n\t\tconst pendingMessage: IPendingMessage = {\n\t\t\ttype: \"message\",\n\t\t\treferenceSequenceNumber,\n\t\t\tcontent,\n\t\t\tlocalOpMetadata,\n\t\t\topMetadata,\n\t\t};\n\n\t\tthis.pendingMessages.push(pendingMessage);\n\t}\n\n\t/**\n\t * Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted\n\t * @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.\n\t */\n\tpublic async applyStashedOpsAt(seqNum?: number) {\n\t\t// apply stashed ops at sequence number\n\t\twhile (!this.initialMessages.isEmpty()) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tconst nextMessage = this.initialMessages.peekFront()!;\n\t\t\tif (seqNum !== undefined) {\n\t\t\t\tif (nextMessage.referenceSequenceNumber > seqNum) {\n\t\t\t\t\tbreak; // nothing left to do at this sequence number\n\t\t\t\t}\n\t\t\t\tif (nextMessage.referenceSequenceNumber < seqNum) {\n\t\t\t\t\tthrow new Error(\"loaded from snapshot too recent to apply stashed ops\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it\n\t\t\t\tconst localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);\n\t\t\t\tnextMessage.localOpMetadata = localOpMetadata;\n\t\t\t} catch (error) {\n\t\t\t\tthrow DataProcessingError.wrapIfUnrecognized(error, \"applyStashedOp\", nextMessage);\n\t\t\t}\n\n\t\t\t// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tthis.pendingMessages.push(this.initialMessages.shift()!);\n\t\t}\n\t}\n\n\t/**\n\t * Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that\n\t * the batch information was preserved for batch messages.\n\t * @param message - The message that got ack'd and needs to be processed.\n\t */\n\tpublic processPendingLocalMessage(message: InboundSequencedContainerRuntimeMessage): unknown {\n\t\t// Pre-processing part - This may be the start of a batch.\n\t\tthis.maybeProcessBatchBegin(message);\n\n\t\t// Get the next message from the pending queue. Verify a message exists.\n\t\tconst pendingMessage = this.pendingMessages.peekFront();\n\t\tassert(\n\t\t\tpendingMessage !== undefined,\n\t\t\t0x169 /* \"No pending message found for this remote message\" */,\n\t\t);\n\t\tthis.savedOps.push(pendingMessage);\n\n\t\tthis.pendingMessages.shift();\n\n\t\tconst messageContent = buildPendingMessageContent(message);\n\n\t\t// Stringified content should match\n\t\tif (pendingMessage.content !== messageContent) {\n\t\t\tthis.stateHandler.close(\n\t\t\t\tDataProcessingError.create(\n\t\t\t\t\t\"pending local message content mismatch\",\n\t\t\t\t\t\"unexpectedAckReceived\",\n\t\t\t\t\tmessage,\n\t\t\t\t\t{\n\t\t\t\t\t\texpectedMessageType: JSON.parse(pendingMessage.content).type,\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Post-processing part - If we are processing a batch then this could be the last message in the batch.\n\t\tthis.maybeProcessBatchEnd(message);\n\n\t\treturn pendingMessage.localOpMetadata;\n\t}\n\n\t/**\n\t * This message could be the first message in batch. If so, set batch state marking the beginning of a batch.\n\t * @param message - The message that is being processed.\n\t */\n\tprivate maybeProcessBatchBegin(message: ISequencedDocumentMessage) {\n\t\t// This message is the first in a batch if the \"batch\" property on the metadata is set to true\n\t\tif ((message.metadata as IBatchMetadata | undefined)?.batch) {\n\t\t\t// We should not already be processing a batch and there should be no pending batch begin message.\n\t\t\tassert(\n\t\t\t\t!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,\n\t\t\t\t0x16b /* \"The pending batch state indicates we are already processing a batch\" */,\n\t\t\t);\n\n\t\t\t// Set the pending batch state indicating we have started processing a batch.\n\t\t\tthis.pendingBatchBeginMessage = message;\n\t\t\tthis.isProcessingBatch = true;\n\t\t}\n\t}\n\n\t/**\n\t * This message could be the last message in batch. If so, clear batch state since the batch is complete.\n\t * @param message - The message that is being processed.\n\t */\n\tprivate maybeProcessBatchEnd(message: ISequencedDocumentMessage) {\n\t\tif (!this.isProcessingBatch) {\n\t\t\treturn;\n\t\t}\n\n\t\t// There should be a pending batch begin message.\n\t\tassert(\n\t\t\tthis.pendingBatchBeginMessage !== undefined,\n\t\t\t0x16d /* \"There is no pending batch begin message\" */,\n\t\t);\n\n\t\tconst batchEndMetadata = (message.metadata as IBatchMetadata | undefined)?.batch;\n\t\tif (this.pendingMessages.isEmpty() || batchEndMetadata === false) {\n\t\t\t// Get the batch begin metadata from the first message in the batch.\n\t\t\tconst batchBeginMetadata = (\n\t\t\t\tthis.pendingBatchBeginMessage.metadata as IBatchMetadata | undefined\n\t\t\t)?.batch;\n\n\t\t\t// There could be just a single message in the batch. If so, it should not have any batch metadata. If there\n\t\t\t// are multiple messages in the batch, verify that we got the correct batch begin and end metadata.\n\t\t\tif (this.pendingBatchBeginMessage === message) {\n\t\t\t\tassert(\n\t\t\t\t\tbatchBeginMetadata === undefined,\n\t\t\t\t\t0x16e /* \"Batch with single message should not have batch metadata\" */,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tif (batchBeginMetadata !== true || batchEndMetadata !== false) {\n\t\t\t\t\tthis.stateHandler.close(\n\t\t\t\t\t\tDataProcessingError.create(\n\t\t\t\t\t\t\t\"Pending batch inconsistency\", // Formerly known as asserts 0x16f and 0x170\n\t\t\t\t\t\t\t\"processPendingLocalMessage\",\n\t\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\truntimeVersion: pkgVersion,\n\t\t\t\t\t\t\t\tbatchClientId:\n\t\t\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing\n\t\t\t\t\t\t\t\t\tthis.pendingBatchBeginMessage.clientId === null\n\t\t\t\t\t\t\t\t\t\t? \"null\"\n\t\t\t\t\t\t\t\t\t\t: this.pendingBatchBeginMessage.clientId,\n\t\t\t\t\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t\t\t\t\t\thasBatchStart: batchBeginMetadata === true,\n\t\t\t\t\t\t\t\thasBatchEnd: batchEndMetadata === false,\n\t\t\t\t\t\t\t\tmessageType: message.type,\n\t\t\t\t\t\t\t\tpendingMessagesCount: this.pendingMessagesCount,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Clear the pending batch state now that we have processed the entire batch.\n\t\t\tthis.pendingBatchBeginMessage = undefined;\n\t\t\tthis.isProcessingBatch = false;\n\t\t}\n\t}\n\n\t/**\n\t * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending\n\t * states in its queue. This includes triggering resubmission of unacked ops.\n\t */\n\tpublic replayPendingStates() {\n\t\tassert(\n\t\t\tthis.stateHandler.connected(),\n\t\t\t0x172 /* \"The connection state is not consistent with the runtime\" */,\n\t\t);\n\n\t\t// This assert suggests we are about to send same ops twice, which will result in data loss.\n\t\tassert(\n\t\t\tthis.clientId !== this.stateHandler.clientId(),\n\t\t\t0x173 /* \"replayPendingStates called twice for same clientId!\" */,\n\t\t);\n\t\tthis.clientId = this.stateHandler.clientId();\n\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x174 /* \"initial states should be empty before replaying pending\" */,\n\t\t);\n\n\t\tconst initialPendingMessagesCount = this.pendingMessages.length;\n\t\tlet remainingPendingMessagesCount = this.pendingMessages.length;\n\n\t\t// Process exactly `pendingMessagesCount` items in the queue as it represents the number of messages that were\n\t\t// pending when we connected. This is important because the `reSubmitFn` might add more items in the queue\n\t\t// which must not be replayed.\n\t\twhile (remainingPendingMessagesCount > 0) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tlet pendingMessage = this.pendingMessages.shift()!;\n\t\t\tremainingPendingMessagesCount--;\n\t\t\tassert(\n\t\t\t\tpendingMessage.opMetadata?.batch !== false,\n\t\t\t\t0x41b /* We cannot process batches in chunks */,\n\t\t\t);\n\n\t\t\t/**\n\t\t\t * We want to ensure grouped messages get processed in a batch.\n\t\t\t * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will\n\t\t\t * either receive the whole batch ack or nothing at all.\n\t\t\t */\n\t\t\tif (pendingMessage.opMetadata?.batch) {\n\t\t\t\tassert(\n\t\t\t\t\tremainingPendingMessagesCount > 0,\n\t\t\t\t\t0x554 /* Last pending message cannot be a batch begin */,\n\t\t\t\t);\n\n\t\t\t\tconst batch: IPendingBatchMessage[] = [];\n\n\t\t\t\t// check is >= because batch end may be last pending message\n\t\t\t\twhile (remainingPendingMessagesCount >= 0) {\n\t\t\t\t\tbatch.push({\n\t\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (pendingMessage.opMetadata?.batch === false) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tassert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);\n\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\t\tpendingMessage = this.pendingMessages.shift()!;\n\t\t\t\t\tremainingPendingMessagesCount--;\n\t\t\t\t\tassert(\n\t\t\t\t\t\tpendingMessage.opMetadata?.batch !== true,\n\t\t\t\t\t\t0x556 /* Batch start needs a corresponding batch end */,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tthis.stateHandler.reSubmitBatch(batch);\n\t\t\t} else {\n\t\t\t\tthis.stateHandler.reSubmit({\n\t\t\t\t\tcontent: pendingMessage.content,\n\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// pending ops should no longer depend on previous sequenced local ops after resubmit\n\t\tthis.savedOps = [];\n\n\t\t// We replayPendingStates on read connections too - we expect these to get nack'd though, and to then reconnect\n\t\t// on a write connection and replay again. This filters out the replay that happens on the read connection so\n\t\t// we only see the replays on write connections (that have a chance to go through).\n\t\tif (this.stateHandler.isActiveConnection()) {\n\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\teventName: \"PendingStatesReplayed\",\n\t\t\t\tcount: initialPendingMessagesCount,\n\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t});\n\t\t}\n\t}\n}\n"]}
|
package/lib/scheduleManager.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduleManager.d.ts","sourceRoot":"","sources":["../src/scheduleManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AACnG,OAAO,EAKN,mBAAmB,EACnB,MAAM,iCAAiC,CAAC;AAczC;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAM1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,GAAG,SAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM;IARxB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,QAAQ,CAAS;gBAGP,YAAY,EAAE,aAAa,CAAC,yBAAyB,EAAE,gBAAgB,CAAC,EACxE,OAAO,EAAE,YAAY,EAC7B,WAAW,EAAE,MAAM,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,mBAAmB;IAStC,kBAAkB,CAAC,OAAO,EAAE,yBAAyB;IAkBrD,iBAAiB,CAAC,KAAK,EAAE,GAAG,GAAG,SAAS,EAAE,OAAO,EAAE,yBAAyB;CAwBnF"}
|
|
1
|
+
{"version":3,"file":"scheduleManager.d.ts","sourceRoot":"","sources":["../src/scheduleManager.ts"],"names":[],"mappings":";AAAA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AACnG,OAAO,EAKN,mBAAmB,EACnB,MAAM,iCAAiC,CAAC;AAczC;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAM1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,GAAG,SAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM;IARxB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,QAAQ,CAAS;gBAGP,YAAY,EAAE,aAAa,CAAC,yBAAyB,EAAE,gBAAgB,CAAC,EACxE,OAAO,EAAE,YAAY,EAC7B,WAAW,EAAE,MAAM,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,mBAAmB;IAStC,kBAAkB,CAAC,OAAO,EAAE,yBAAyB;IAkBrD,iBAAiB,CAAC,KAAK,EAAE,GAAG,GAAG,SAAS,EAAE,OAAO,EAAE,yBAAyB;CAwBnF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/container-runtime",
|
|
3
|
-
"version": "2.0.0-internal.7.
|
|
3
|
+
"version": "2.0.0-internal.7.3.0",
|
|
4
4
|
"description": "Fluid container runtime",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -35,18 +35,18 @@
|
|
|
35
35
|
"temp-directory": "nyc/.nyc_output"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@fluid-internal/client-utils": ">=2.0.0-internal.7.
|
|
39
|
-
"@fluidframework/container-definitions": ">=2.0.0-internal.7.
|
|
40
|
-
"@fluidframework/container-runtime-definitions": ">=2.0.0-internal.7.
|
|
41
|
-
"@fluidframework/core-interfaces": ">=2.0.0-internal.7.
|
|
42
|
-
"@fluidframework/core-utils": ">=2.0.0-internal.7.
|
|
43
|
-
"@fluidframework/datastore": ">=2.0.0-internal.7.
|
|
44
|
-
"@fluidframework/driver-definitions": ">=2.0.0-internal.7.
|
|
45
|
-
"@fluidframework/driver-utils": ">=2.0.0-internal.7.
|
|
38
|
+
"@fluid-internal/client-utils": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
39
|
+
"@fluidframework/container-definitions": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
40
|
+
"@fluidframework/container-runtime-definitions": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
41
|
+
"@fluidframework/core-interfaces": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
42
|
+
"@fluidframework/core-utils": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
43
|
+
"@fluidframework/datastore": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
44
|
+
"@fluidframework/driver-definitions": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
45
|
+
"@fluidframework/driver-utils": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
46
46
|
"@fluidframework/protocol-definitions": "^3.0.0",
|
|
47
|
-
"@fluidframework/runtime-definitions": ">=2.0.0-internal.7.
|
|
48
|
-
"@fluidframework/runtime-utils": ">=2.0.0-internal.7.
|
|
49
|
-
"@fluidframework/telemetry-utils": ">=2.0.0-internal.7.
|
|
47
|
+
"@fluidframework/runtime-definitions": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
48
|
+
"@fluidframework/runtime-utils": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
49
|
+
"@fluidframework/telemetry-utils": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
50
50
|
"double-ended-queue": "^2.1.0-0",
|
|
51
51
|
"events": "^3.1.0",
|
|
52
52
|
"lz4js": "^0.2.0",
|
|
@@ -54,16 +54,16 @@
|
|
|
54
54
|
"uuid": "^9.0.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@fluid-
|
|
57
|
+
"@fluid-private/stochastic-test-utils": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
58
58
|
"@fluid-tools/benchmark": "^0.48.0",
|
|
59
|
-
"@fluid-tools/build-cli": "^0.
|
|
59
|
+
"@fluid-tools/build-cli": "^0.28.0",
|
|
60
60
|
"@fluidframework/build-common": "^2.0.3",
|
|
61
|
-
"@fluidframework/build-tools": "^0.
|
|
62
|
-
"@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.7.
|
|
63
|
-
"@fluidframework/eslint-config-fluid": "^3.
|
|
64
|
-
"@fluidframework/mocha-test-setup": ">=2.0.0-internal.7.
|
|
65
|
-
"@fluidframework/test-runtime-utils": ">=2.0.0-internal.7.
|
|
66
|
-
"@microsoft/api-extractor": "^7.
|
|
61
|
+
"@fluidframework/build-tools": "^0.28.0",
|
|
62
|
+
"@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.0.0-internal.7.2.0",
|
|
63
|
+
"@fluidframework/eslint-config-fluid": "^3.1.0",
|
|
64
|
+
"@fluidframework/mocha-test-setup": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
65
|
+
"@fluidframework/test-runtime-utils": ">=2.0.0-internal.7.3.0 <2.0.0-internal.7.4.0",
|
|
66
|
+
"@microsoft/api-extractor": "^7.38.3",
|
|
67
67
|
"@types/double-ended-queue": "^2.1.0",
|
|
68
68
|
"@types/events": "^3.0.0",
|
|
69
69
|
"@types/mocha": "^9.1.1",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"typeValidation": {
|
|
85
85
|
"broken": {
|
|
86
86
|
"ClassDeclaration_ContainerRuntime": {
|
|
87
|
-
"
|
|
87
|
+
"backCompat": false
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
},
|
|
@@ -97,18 +97,18 @@
|
|
|
97
97
|
"build:genver": "gen-version",
|
|
98
98
|
"build:test": "tsc --project ./src/test/tsconfig.json",
|
|
99
99
|
"ci:build:docs": "api-extractor run",
|
|
100
|
-
"clean": "rimraf --glob dist lib \"
|
|
100
|
+
"clean": "rimraf --glob dist lib \"**/*.tsbuildinfo\" \"**/*.build.log\" _api-extractor-temp nyc",
|
|
101
101
|
"eslint": "eslint --format stylish src",
|
|
102
102
|
"eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
|
|
103
103
|
"format": "npm run prettier:fix",
|
|
104
104
|
"lint": "npm run prettier && npm run eslint",
|
|
105
|
-
"lint:fix": "npm run prettier:fix &&npm run eslint:fix",
|
|
105
|
+
"lint:fix": "npm run prettier:fix && npm run eslint:fix",
|
|
106
106
|
"prettier": "prettier --check . --ignore-path ../../../.prettierignore",
|
|
107
107
|
"prettier:fix": "prettier --write . --ignore-path ../../../.prettierignore",
|
|
108
108
|
"test": "npm run test:mocha",
|
|
109
|
-
"test:benchmark:report": "mocha --timeout 999999 --perfMode --parentProcess --fgrep @Benchmark --reporter @fluid-tools/benchmark/dist/MochaReporter.js ./dist/**/*.perf.spec.js",
|
|
109
|
+
"test:benchmark:report": "mocha --timeout 999999 --perfMode --parentProcess --fgrep @Benchmark --reporter @fluid-tools/benchmark/dist/MochaReporter.js \"./dist/**/*.perf.spec.js\"",
|
|
110
110
|
"test:coverage": "c8 npm test",
|
|
111
|
-
"test:mocha": "mocha --ignore
|
|
111
|
+
"test:mocha": "mocha --ignore \"dist/test/types/*\" --recursive dist/test -r node_modules/@fluidframework/mocha-test-setup",
|
|
112
112
|
"test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
|
|
113
113
|
"tsc": "tsc",
|
|
114
114
|
"tsc:watch": "tsc --watch",
|
|
@@ -110,7 +110,7 @@ class OpPerfTelemetry {
|
|
|
110
110
|
|
|
111
111
|
this.deltaLatencyLogger = createSampledLogger(logger, deltaLatencyEventSampler);
|
|
112
112
|
|
|
113
|
-
// The SampledLogger here is used get access to the isSamplingDisabled property
|
|
113
|
+
// The SampledLogger here is used get access to the isSamplingDisabled property derived from
|
|
114
114
|
// telemetry config properties. The actual sampling logic for op messages happens outside this SampledLogger
|
|
115
115
|
// due to complexity of the different asynchronus scenarios of the op message lifecycle.
|
|
116
116
|
this.opLatencyLogger = createSampledLogger(logger);
|
package/src/containerRuntime.ts
CHANGED
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
IIdCompressor,
|
|
92
92
|
IIdCompressorCore,
|
|
93
93
|
IdCreationRange,
|
|
94
|
-
|
|
94
|
+
SerializedIdCompressorWithOngoingSession,
|
|
95
95
|
} from "@fluidframework/runtime-definitions";
|
|
96
96
|
import {
|
|
97
97
|
addBlobToSummary,
|
|
@@ -185,13 +185,12 @@ import {
|
|
|
185
185
|
getLongStack,
|
|
186
186
|
} from "./opLifecycle";
|
|
187
187
|
import { DeltaManagerSummarizerProxy } from "./deltaManagerSummarizerProxy";
|
|
188
|
-
import { IBatchMetadata } from "./metadata";
|
|
188
|
+
import { IBatchMetadata, IIdAllocationMetadata } from "./metadata";
|
|
189
189
|
import {
|
|
190
190
|
ContainerMessageType,
|
|
191
191
|
type InboundSequencedContainerRuntimeMessage,
|
|
192
192
|
type InboundSequencedContainerRuntimeMessageOrSystemMessage,
|
|
193
193
|
type ContainerRuntimeIdAllocationMessage,
|
|
194
|
-
type LocalContainerRuntimeIdAllocationMessage,
|
|
195
194
|
type LocalContainerRuntimeMessage,
|
|
196
195
|
type OutboundContainerRuntimeMessage,
|
|
197
196
|
type UnknownContainerRuntimeMessage,
|
|
@@ -213,15 +212,6 @@ function compatBehaviorAllowsMessageType(
|
|
|
213
212
|
return compatBehavior === "Ignore";
|
|
214
213
|
}
|
|
215
214
|
|
|
216
|
-
function prepareLocalContainerRuntimeIdAllocationMessageForTransit(
|
|
217
|
-
message: LocalContainerRuntimeIdAllocationMessage | ContainerRuntimeIdAllocationMessage,
|
|
218
|
-
): asserts message is ContainerRuntimeIdAllocationMessage {
|
|
219
|
-
// Remove the stashedState from the op if it's a stashed op
|
|
220
|
-
if ("stashedState" in message.contents) {
|
|
221
|
-
delete message.contents.stashedState;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
215
|
/**
|
|
226
216
|
* @public
|
|
227
217
|
*/
|
|
@@ -557,6 +547,10 @@ export interface IPendingRuntimeState {
|
|
|
557
547
|
* Pending blobs from BlobManager
|
|
558
548
|
*/
|
|
559
549
|
pendingAttachmentBlobs?: IPendingBlobs;
|
|
550
|
+
/**
|
|
551
|
+
* Pending idCompressor state
|
|
552
|
+
*/
|
|
553
|
+
pendingIdCompressorState?: SerializedIdCompressorWithOngoingSession;
|
|
560
554
|
}
|
|
561
555
|
|
|
562
556
|
const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
|
|
@@ -916,10 +910,16 @@ export class ContainerRuntime
|
|
|
916
910
|
let idCompressor: (IIdCompressor & IIdCompressorCore) | undefined;
|
|
917
911
|
if (idCompressorEnabled) {
|
|
918
912
|
const { IdCompressor, createSessionId } = await import("./id-compressor");
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
913
|
+
|
|
914
|
+
const pendingLocalState = context.pendingLocalState as IPendingRuntimeState;
|
|
915
|
+
|
|
916
|
+
if (pendingLocalState?.pendingIdCompressorState !== undefined) {
|
|
917
|
+
idCompressor = IdCompressor.deserialize(pendingLocalState.pendingIdCompressorState);
|
|
918
|
+
} else if (serializedIdCompressor !== undefined) {
|
|
919
|
+
idCompressor = IdCompressor.deserialize(serializedIdCompressor, createSessionId());
|
|
920
|
+
} else {
|
|
921
|
+
idCompressor = IdCompressor.create(logger);
|
|
922
|
+
}
|
|
923
923
|
}
|
|
924
924
|
|
|
925
925
|
const runtime = new containerRuntimeCtor(
|
|
@@ -1356,7 +1356,17 @@ export class ContainerRuntime
|
|
|
1356
1356
|
"Fluid.ContainerRuntime.CompressionChunkingDisabled",
|
|
1357
1357
|
);
|
|
1358
1358
|
|
|
1359
|
-
const opGroupingManager = new OpGroupingManager(
|
|
1359
|
+
const opGroupingManager = new OpGroupingManager(
|
|
1360
|
+
{
|
|
1361
|
+
groupedBatchingEnabled: this.groupedBatchingEnabled,
|
|
1362
|
+
opCountThreshold:
|
|
1363
|
+
this.mc.config.getNumber("Fluid.ContainerRuntime.GroupedBatchingOpCount") ?? 2,
|
|
1364
|
+
reentrantBatchGroupingEnabled:
|
|
1365
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.GroupedBatchingReentrancy") ??
|
|
1366
|
+
true,
|
|
1367
|
+
},
|
|
1368
|
+
this.mc.logger,
|
|
1369
|
+
);
|
|
1360
1370
|
|
|
1361
1371
|
const opSplitter = new OpSplitter(
|
|
1362
1372
|
chunks,
|
|
@@ -1566,7 +1576,6 @@ export class ContainerRuntime
|
|
|
1566
1576
|
compressionOptions,
|
|
1567
1577
|
maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
|
|
1568
1578
|
disablePartialFlush: disablePartialFlush === true,
|
|
1569
|
-
enableGroupedBatching: this.groupedBatchingEnabled,
|
|
1570
1579
|
},
|
|
1571
1580
|
logger: this.mc.logger,
|
|
1572
1581
|
groupingManager: opGroupingManager,
|
|
@@ -2041,20 +2050,6 @@ export class ContainerRuntime
|
|
|
2041
2050
|
this.updateDocumentDirtyState(newState);
|
|
2042
2051
|
}
|
|
2043
2052
|
|
|
2044
|
-
/**
|
|
2045
|
-
* Updates the runtime's IdCompressor with the stashed state present in the given op. This is a bit of a
|
|
2046
|
-
* hack and is unnecessarily expensive. As it stands, every locally stashed op (all ops that get stored in
|
|
2047
|
-
* the PendingStateManager) will store their serialized representation locally until ack'd. Upon receiving
|
|
2048
|
-
* this stashed state, the IdCompressor blindly deserializes to the stashed state and assumes the session.
|
|
2049
|
-
* Technically only the last stashed state is needed to do this correctly, but we would have to write some
|
|
2050
|
-
* more hacky code to modify the batch before it gets sent out.
|
|
2051
|
-
* @param content - An IdAllocationOp with "stashedState", which is a representation of un-ack'd local state.
|
|
2052
|
-
*/
|
|
2053
|
-
private async applyStashedIdAllocationOp(op: IdCreationRangeWithStashedState) {
|
|
2054
|
-
const { IdCompressor } = await import("./id-compressor");
|
|
2055
|
-
this.idCompressor = IdCompressor.deserialize(op.stashedState);
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
2053
|
/**
|
|
2059
2054
|
* Parse an op's type and actual content from given serialized content
|
|
2060
2055
|
* ! Note: this format needs to be in-line with what is set in the "ContainerRuntime.submit(...)" method
|
|
@@ -2080,7 +2075,7 @@ export class ContainerRuntime
|
|
|
2080
2075
|
this.idCompressor !== undefined,
|
|
2081
2076
|
0x67b /* IdCompressor should be defined if enabled */,
|
|
2082
2077
|
);
|
|
2083
|
-
return
|
|
2078
|
+
return;
|
|
2084
2079
|
case ContainerMessageType.Alias:
|
|
2085
2080
|
case ContainerMessageType.BlobAttach:
|
|
2086
2081
|
return;
|
|
@@ -2332,7 +2327,14 @@ export class ContainerRuntime
|
|
|
2332
2327
|
this.idCompressor !== undefined,
|
|
2333
2328
|
0x67c /* IdCompressor should be defined if enabled */,
|
|
2334
2329
|
);
|
|
2335
|
-
|
|
2330
|
+
|
|
2331
|
+
// Don't re-finalize the range if we're processing a "savedOp" in
|
|
2332
|
+
// stashed ops flow. The compressor is stashed with these ops already processed.
|
|
2333
|
+
if (
|
|
2334
|
+
(messageWithContext.message.metadata as IIdAllocationMetadata)?.savedOp !== true
|
|
2335
|
+
) {
|
|
2336
|
+
this.idCompressor.finalizeCreationRange(messageWithContext.message.contents);
|
|
2337
|
+
}
|
|
2336
2338
|
break;
|
|
2337
2339
|
case ContainerMessageType.ChunkedOp:
|
|
2338
2340
|
case ContainerMessageType.Rejoin:
|
|
@@ -3507,13 +3509,13 @@ export class ContainerRuntime
|
|
|
3507
3509
|
contents: JSON.stringify(idAllocationMessage),
|
|
3508
3510
|
referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
3509
3511
|
metadata: undefined,
|
|
3510
|
-
localOpMetadata:
|
|
3512
|
+
localOpMetadata: undefined,
|
|
3511
3513
|
type: ContainerMessageType.IdAllocation,
|
|
3512
3514
|
};
|
|
3513
3515
|
}
|
|
3514
3516
|
|
|
3515
3517
|
if (idAllocationBatchMessage !== undefined) {
|
|
3516
|
-
this.outbox.
|
|
3518
|
+
this.outbox.submitIdAllocation(idAllocationBatchMessage);
|
|
3517
3519
|
}
|
|
3518
3520
|
}
|
|
3519
3521
|
}
|
|
@@ -3738,10 +3740,7 @@ export class ContainerRuntime
|
|
|
3738
3740
|
break;
|
|
3739
3741
|
case ContainerMessageType.Attach:
|
|
3740
3742
|
case ContainerMessageType.Alias:
|
|
3741
|
-
this.submit(message, localOpMetadata);
|
|
3742
|
-
break;
|
|
3743
3743
|
case ContainerMessageType.IdAllocation: {
|
|
3744
|
-
prepareLocalContainerRuntimeIdAllocationMessageForTransit(message);
|
|
3745
3744
|
this.submit(message, localOpMetadata);
|
|
3746
3745
|
break;
|
|
3747
3746
|
}
|
|
@@ -3981,9 +3980,12 @@ export class ContainerRuntime
|
|
|
3981
3980
|
return; // no pending state to save
|
|
3982
3981
|
}
|
|
3983
3982
|
|
|
3983
|
+
const pendingIdCompressorState = this.idCompressor?.serialize(true);
|
|
3984
|
+
|
|
3984
3985
|
const pendingState: IPendingRuntimeState = {
|
|
3985
3986
|
pending,
|
|
3986
3987
|
pendingAttachmentBlobs,
|
|
3988
|
+
pendingIdCompressorState,
|
|
3987
3989
|
};
|
|
3988
3990
|
event.end({
|
|
3989
3991
|
attachmentBlobsSize: Object.keys(pendingAttachmentBlobs ?? {}).length,
|
package/src/gc/gcConfigs.ts
CHANGED
|
@@ -24,8 +24,9 @@ import {
|
|
|
24
24
|
runSessionExpiryKey,
|
|
25
25
|
runSweepKey,
|
|
26
26
|
stableGCVersion,
|
|
27
|
-
|
|
27
|
+
throwOnTombstoneLoadOverrideKey,
|
|
28
28
|
throwOnTombstoneUsageKey,
|
|
29
|
+
gcThrowOnTombstoneLoadOptionName,
|
|
29
30
|
} from "./gcDefinitions";
|
|
30
31
|
import { getGCVersion, shouldAllowGcSweep, shouldAllowGcTombstoneEnforcement } from "./gcHelpers";
|
|
31
32
|
|
|
@@ -168,8 +169,13 @@ export function generateGCConfigs(
|
|
|
168
169
|
createParams.metadata?.gcFeatureMatrix?.tombstoneGeneration /* persisted */,
|
|
169
170
|
createParams.gcOptions[gcTombstoneGenerationOptionName] /* current */,
|
|
170
171
|
);
|
|
172
|
+
|
|
173
|
+
const throwOnTombstoneLoadConfig =
|
|
174
|
+
mc.config.getBoolean(throwOnTombstoneLoadOverrideKey) ??
|
|
175
|
+
createParams.gcOptions[gcThrowOnTombstoneLoadOptionName] ??
|
|
176
|
+
false;
|
|
171
177
|
const throwOnTombstoneLoad =
|
|
172
|
-
|
|
178
|
+
throwOnTombstoneLoadConfig &&
|
|
173
179
|
tombstoneEnforcementAllowed &&
|
|
174
180
|
!createParams.isSummarizerClient;
|
|
175
181
|
const throwOnTombstoneUsage =
|
package/src/gc/gcDefinitions.ts
CHANGED
|
@@ -38,6 +38,13 @@ export const nextGCVersion: GCVersion = 4;
|
|
|
38
38
|
* Otherwise, only enforce GC Tombstone if the passed in value matches the persisted value
|
|
39
39
|
*/
|
|
40
40
|
export const gcTombstoneGenerationOptionName = "gcTombstoneGeneration";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* This undocumented GC Option (on ContainerRuntime Options) allows an app to enable throwing an error when tombstone
|
|
44
|
+
* object is loaded (requested).
|
|
45
|
+
*/
|
|
46
|
+
export const gcThrowOnTombstoneLoadOptionName = "gcThrowOnTombstoneLoad";
|
|
47
|
+
|
|
41
48
|
/**
|
|
42
49
|
* This GC Option (on ContainerRuntime Options) allows an app to disable GC Sweep on old documents by incrementing this value.
|
|
43
50
|
*
|
|
@@ -59,8 +66,9 @@ export const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
|
59
66
|
export const disableSweepLogKey = "Fluid.GarbageCollection.DisableSweepLog";
|
|
60
67
|
/** Config key to disable the tombstone feature, i.e., tombstone information is not read / written into summary. */
|
|
61
68
|
export const disableTombstoneKey = "Fluid.GarbageCollection.DisableTombstone";
|
|
62
|
-
/** Config key to
|
|
63
|
-
export const
|
|
69
|
+
/** Config key to override throwing an error when tombstone object is loaded (requested). */
|
|
70
|
+
export const throwOnTombstoneLoadOverrideKey =
|
|
71
|
+
"Fluid.GarbageCollection.ThrowOnTombstoneLoadOverride";
|
|
64
72
|
/** Config key to enable throwing an error when tombstone object is used (e.g. outgoing or incoming ops). */
|
|
65
73
|
export const throwOnTombstoneUsageKey = "Fluid.GarbageCollection.ThrowOnTombstoneUsage";
|
|
66
74
|
/** Config key to enable GC version upgrade. */
|
package/src/gc/gcTelemetry.ts
CHANGED
|
@@ -20,8 +20,9 @@ import {
|
|
|
20
20
|
IGarbageCollectorConfigs,
|
|
21
21
|
disableTombstoneKey,
|
|
22
22
|
throwOnTombstoneUsageKey,
|
|
23
|
-
|
|
23
|
+
throwOnTombstoneLoadOverrideKey,
|
|
24
24
|
runSweepKey,
|
|
25
|
+
GCFeatureMatrix,
|
|
25
26
|
} from "./gcDefinitions";
|
|
26
27
|
import { UnreferencedStateTracker } from "./gcUnreferencedStateTracker";
|
|
27
28
|
|
|
@@ -46,6 +47,10 @@ interface IUnreferencedEventProps extends ICreateContainerMetadata, ICommonProps
|
|
|
46
47
|
type: GCNodeType;
|
|
47
48
|
unrefTime: number;
|
|
48
49
|
age: number;
|
|
50
|
+
// Expanding GC feature matrix. Without doing this, the configs cannot be logged in telemetry directly.
|
|
51
|
+
gcConfigs: Omit<IGarbageCollectorConfigs, "persistedGcFeatureMatrix"> & {
|
|
52
|
+
[K in keyof GCFeatureMatrix]: GCFeatureMatrix[K];
|
|
53
|
+
};
|
|
49
54
|
timeout?: number;
|
|
50
55
|
fromId?: {
|
|
51
56
|
value: string;
|
|
@@ -79,14 +84,7 @@ export class GCTelemetryTracker {
|
|
|
79
84
|
|
|
80
85
|
constructor(
|
|
81
86
|
private readonly mc: MonitoringContext,
|
|
82
|
-
private readonly configs:
|
|
83
|
-
IGarbageCollectorConfigs,
|
|
84
|
-
| "inactiveTimeoutMs"
|
|
85
|
-
| "sweepTimeoutMs"
|
|
86
|
-
| "tombstoneEnforcementAllowed"
|
|
87
|
-
| "throwOnTombstoneLoad"
|
|
88
|
-
| "throwOnTombstoneUsage"
|
|
89
|
-
>,
|
|
87
|
+
private readonly configs: IGarbageCollectorConfigs,
|
|
90
88
|
private readonly isSummarizerClient: boolean,
|
|
91
89
|
private readonly createContainerMetadata: ICreateContainerMetadata,
|
|
92
90
|
private readonly getNodeType: (nodeId: string) => GCNodeType,
|
|
@@ -153,6 +151,7 @@ export class GCTelemetryTracker {
|
|
|
153
151
|
fromId: untaggedFromId,
|
|
154
152
|
...propsToLog
|
|
155
153
|
} = nodeUsageProps;
|
|
154
|
+
const { persistedGcFeatureMatrix, ...configs } = this.configs;
|
|
156
155
|
const unrefEventProps: Omit<IUnreferencedEventProps, "state" | "usageType"> = {
|
|
157
156
|
type: nodeType,
|
|
158
157
|
unrefTime: nodeStateTracker?.unreferencedTimestampMs ?? -1,
|
|
@@ -168,6 +167,7 @@ export class GCTelemetryTracker {
|
|
|
168
167
|
...tagCodeArtifacts({ id: untaggedId, fromId: untaggedFromId }),
|
|
169
168
|
...propsToLog,
|
|
170
169
|
...this.createContainerMetadata,
|
|
170
|
+
gcConfigs: { ...configs, ...persistedGcFeatureMatrix },
|
|
171
171
|
};
|
|
172
172
|
|
|
173
173
|
// If the node that is used is tombstoned, log a tombstone telemetry.
|
|
@@ -217,7 +217,7 @@ export class GCTelemetryTracker {
|
|
|
217
217
|
// Events generated:
|
|
218
218
|
// InactiveObject_Loaded, SweepReadyObject_Loaded
|
|
219
219
|
if (nodeUsageProps.usageType === "Loaded") {
|
|
220
|
-
const { id, fromId, headers, ...detailedProps } = unrefEventProps;
|
|
220
|
+
const { id, fromId, headers, gcConfigs, ...detailedProps } = unrefEventProps;
|
|
221
221
|
const event = {
|
|
222
222
|
eventName: `${state}Object_${nodeUsageProps.usageType}`,
|
|
223
223
|
...tagCodeArtifacts({ pkg: nodeUsageProps.packagePath?.join("/") }),
|
|
@@ -226,6 +226,7 @@ export class GCTelemetryTracker {
|
|
|
226
226
|
fromId,
|
|
227
227
|
headers: { ...headers },
|
|
228
228
|
details: detailedProps,
|
|
229
|
+
gcConfigs,
|
|
229
230
|
};
|
|
230
231
|
|
|
231
232
|
// Do not log the inactive object x events as error events as they are not the best signal for
|
|
@@ -252,7 +253,7 @@ export class GCTelemetryTracker {
|
|
|
252
253
|
// GC_Tombstone_DataStore_Requested, GC_Tombstone_DataStore_Changed, GC_Tombstone_DataStore_Revived
|
|
253
254
|
// GC_Tombstone_SubDataStore_Requested, GC_Tombstone_SubDataStore_Changed, GC_Tombstone_SubDataStore_Revived
|
|
254
255
|
// GC_Tombstone_Blob_Requested, GC_Tombstone_Blob_Changed, GC_Tombstone_Blob_Revived
|
|
255
|
-
const { id, fromId, headers, ...detailedProps } = unrefEventProps;
|
|
256
|
+
const { id, fromId, headers, gcConfigs, ...detailedProps } = unrefEventProps;
|
|
256
257
|
const eventUsageName = usageType === "Loaded" ? "Requested" : usageType;
|
|
257
258
|
const event = {
|
|
258
259
|
eventName: `GC_Tombstone_${nodeType}_${eventUsageName}`,
|
|
@@ -262,7 +263,15 @@ export class GCTelemetryTracker {
|
|
|
262
263
|
fromId,
|
|
263
264
|
headers: { ...headers },
|
|
264
265
|
details: detailedProps,
|
|
265
|
-
|
|
266
|
+
gcConfigs,
|
|
267
|
+
tombstoneFlags: {
|
|
268
|
+
DisableTombstone: this.mc.config.getBoolean(disableTombstoneKey),
|
|
269
|
+
ThrowOnTombstoneUsage: this.mc.config.getBoolean(throwOnTombstoneUsageKey),
|
|
270
|
+
ThrowOnTombstoneLoad: this.mc.config.getBoolean(throwOnTombstoneLoadOverrideKey),
|
|
271
|
+
},
|
|
272
|
+
sweepFlags: {
|
|
273
|
+
EnableSweepFlag: this.mc.config.getBoolean(runSweepKey),
|
|
274
|
+
},
|
|
266
275
|
};
|
|
267
276
|
|
|
268
277
|
if (
|
|
@@ -343,7 +352,9 @@ export class GCTelemetryTracker {
|
|
|
343
352
|
// InactiveObject_Loaded, InactiveObject_Changed, InactiveObject_Revived
|
|
344
353
|
// SweepReadyObject_Loaded, SweepReadyObject_Changed, SweepReadyObject_Revived
|
|
345
354
|
for (const eventProps of this.pendingEventsQueue) {
|
|
346
|
-
const { usageType, state, id, fromId, ...propsToLog } = eventProps;
|
|
355
|
+
// const { usageType, state, id, fromId, ...propsToLog } = eventProps;
|
|
356
|
+
const { usageType, state, id, fromId, headers, gcConfigs, ...detailedProps } =
|
|
357
|
+
eventProps;
|
|
347
358
|
/**
|
|
348
359
|
* Revived event is logged only if the node is active. If the node is not active, the reference to it was
|
|
349
360
|
* from another unreferenced node and this scenario is not interesting to log.
|
|
@@ -361,11 +372,11 @@ export class GCTelemetryTracker {
|
|
|
361
372
|
: undefined;
|
|
362
373
|
const event = {
|
|
363
374
|
eventName: `${state}Object_${usageType}`,
|
|
364
|
-
details: JSON.stringify({
|
|
365
|
-
...propsToLog,
|
|
366
|
-
}),
|
|
367
375
|
id,
|
|
368
376
|
fromId,
|
|
377
|
+
headers: { ...headers },
|
|
378
|
+
details: detailedProps,
|
|
379
|
+
gcConfigs,
|
|
369
380
|
...tagCodeArtifacts({
|
|
370
381
|
pkg: pkg?.join("/"),
|
|
371
382
|
fromPkg: fromPkg?.join("/"),
|
|
@@ -452,7 +463,7 @@ export function sendGCUnexpectedUsageEvent(
|
|
|
452
463
|
event.tombstoneFlags = JSON.stringify({
|
|
453
464
|
DisableTombstone: mc.config.getBoolean(disableTombstoneKey),
|
|
454
465
|
ThrowOnTombstoneUsage: mc.config.getBoolean(throwOnTombstoneUsageKey),
|
|
455
|
-
ThrowOnTombstoneLoad: mc.config.getBoolean(
|
|
466
|
+
ThrowOnTombstoneLoad: mc.config.getBoolean(throwOnTombstoneLoadOverrideKey),
|
|
456
467
|
});
|
|
457
468
|
event.sweepFlags = JSON.stringify({
|
|
458
469
|
EnableSweepFlag: mc.config.getBoolean(runSweepKey),
|
package/src/gc/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ export {
|
|
|
12
12
|
GCNodeType,
|
|
13
13
|
gcTestModeKey,
|
|
14
14
|
gcTombstoneGenerationOptionName,
|
|
15
|
+
gcThrowOnTombstoneLoadOptionName,
|
|
15
16
|
gcSweepGenerationOptionName,
|
|
16
17
|
GCFeatureMatrix,
|
|
17
18
|
GCVersion,
|
|
@@ -32,6 +33,7 @@ export {
|
|
|
32
33
|
disableAttachmentBlobSweepKey,
|
|
33
34
|
disableDatastoreSweepKey,
|
|
34
35
|
UnreferencedState,
|
|
36
|
+
throwOnTombstoneLoadOverrideKey,
|
|
35
37
|
} from "./gcDefinitions";
|
|
36
38
|
export {
|
|
37
39
|
cloneGCData,
|