@fluidframework/container-runtime 2.101.0 → 2.101.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.
@@ -21,14 +21,6 @@ export declare class DuplicateBatchDetector {
21
21
  * We map from sequenceNumber to batchId to find which ones we can stop tracking as MSN advances
22
22
  */
23
23
  private readonly batchIdsBySeqNum;
24
- /**
25
- * Number of inbound batches processed since the last summary. Reset by getRecentBatchInfoForSummary.
26
- */
27
- private processedBatchCount;
28
- /**
29
- * Largest tracked-batch count observed since the last summary. Reset by getRecentBatchInfoForSummary.
30
- */
31
- private peakTrackedBatchCount;
32
24
  /**
33
25
  * Initialize from snapshot data if provided - otherwise initialize empty
34
26
  */
@@ -1 +1 @@
1
- {"version":3,"file":"duplicateBatchDetector.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/duplicateBatchDetector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAGtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;;;;GAOG;AACH,qBAAa,sBAAsB;IAClC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAE7D;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IAE9D;;OAEG;IACH,OAAO,CAAC,mBAAmB,CAAK;IAEhC;;OAEG;IACH,OAAO,CAAC,qBAAqB,CAAK;IAElC;;OAEG;gBACS,oBAAoB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,SAAS;IAUhE;;;;;OAKG;IACI,mBAAmB,CACzB,UAAU,EAAE,cAAc,GACxB;QAAE,SAAS,EAAE,IAAI,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,SAAS,EAAE,KAAK,CAAA;KAAE;IAwC1E;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;;;OAKG;IACI,4BAA4B,CAClC,gBAAgB,CAAC,EAAE,iBAAiB,GAClC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,SAAS;CAmBjC"}
1
+ {"version":3,"file":"duplicateBatchDetector.d.ts","sourceRoot":"","sources":["../../src/opLifecycle/duplicateBatchDetector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8CAA8C,CAAC;AAGtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;;;;GAOG;AACH,qBAAa,sBAAsB;IAClC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAE7D;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IAE9D;;OAEG;gBACS,oBAAoB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,SAAS;IAShE;;;;;OAKG;IACI,mBAAmB,CACzB,UAAU,EAAE,cAAc,GACxB;QAAE,SAAS,EAAE,IAAI,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,SAAS,EAAE,KAAK,CAAA;KAAE;IAoC1E;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;;;OAKG;IACI,4BAA4B,CAClC,gBAAgB,CAAC,EAAE,iBAAiB,GAClC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,SAAS;CAajC"}
@@ -25,20 +25,11 @@ export class DuplicateBatchDetector {
25
25
  * We map from sequenceNumber to batchId to find which ones we can stop tracking as MSN advances
26
26
  */
27
27
  this.batchIdsBySeqNum = new Map();
28
- /**
29
- * Number of inbound batches processed since the last summary. Reset by getRecentBatchInfoForSummary.
30
- */
31
- this.processedBatchCount = 0;
32
- /**
33
- * Largest tracked-batch count observed since the last summary. Reset by getRecentBatchInfoForSummary.
34
- */
35
- this.peakTrackedBatchCount = 0;
36
28
  if (batchIdsFromSnapshot) {
37
29
  for (const [seqNum, batchId] of batchIdsFromSnapshot) {
38
30
  this.batchIdsBySeqNum.set(seqNum, batchId);
39
31
  this.seqNumByBatchId.set(batchId, seqNum);
40
32
  }
41
- this.peakTrackedBatchCount = this.batchIdsBySeqNum.size;
42
33
  }
43
34
  }
44
35
  /**
@@ -49,7 +40,6 @@ export class DuplicateBatchDetector {
49
40
  */
50
41
  processInboundBatch(batchStart) {
51
42
  const { sequenceNumber, minimumSequenceNumber } = batchStart.keyMessage;
52
- this.processedBatchCount++;
53
43
  // Glance at this batch's MSN. Any batchIds we're tracking with a lower sequence number are now safe to forget.
54
44
  // Why? Because any other client holding the same batch locally would have seen the earlier batch and closed before submitting its duplicate.
55
45
  this.clearOldBatchIds(minimumSequenceNumber);
@@ -69,9 +59,6 @@ export class DuplicateBatchDetector {
69
59
  // Add new batch
70
60
  this.batchIdsBySeqNum.set(sequenceNumber, batchId);
71
61
  this.seqNumByBatchId.set(batchId, sequenceNumber);
72
- if (this.batchIdsBySeqNum.size > this.peakTrackedBatchCount) {
73
- this.peakTrackedBatchCount = this.batchIdsBySeqNum.size;
74
- }
75
62
  return { duplicate: false };
76
63
  }
77
64
  /**
@@ -96,19 +83,10 @@ export class DuplicateBatchDetector {
96
83
  * @returns A serializable object representing the state of the detector, or undefined if there is nothing to save.
97
84
  */
98
85
  getRecentBatchInfoForSummary(telemetryContext) {
99
- if (telemetryContext !== undefined) {
100
- const prefix = "fluid_DuplicateBatchDetector_";
101
- telemetryContext.set(prefix, "recentBatchCount", this.batchIdsBySeqNum.size);
102
- telemetryContext.set(prefix, "peakRecentBatchCount", this.peakTrackedBatchCount);
103
- telemetryContext.set(prefix, "processedBatchCount", this.processedBatchCount);
104
- }
105
- // Reset per-window perf counters so each summary covers only the activity since the
106
- // previous one. Peak resets to the current size (the floor for the next window).
107
- this.processedBatchCount = 0;
108
- this.peakTrackedBatchCount = this.batchIdsBySeqNum.size;
109
86
  if (this.batchIdsBySeqNum.size === 0) {
110
87
  return undefined;
111
88
  }
89
+ telemetryContext?.set("fluid_DuplicateBatchDetector_", "recentBatchCount", this.batchIdsBySeqNum.size);
112
90
  return [...this.batchIdsBySeqNum.entries()];
113
91
  }
114
92
  }
@@ -1 +1 @@
1
- {"version":3,"file":"duplicateBatchDetector.js","sourceRoot":"","sources":["../../src/opLifecycle/duplicateBatchDetector.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 type { ITelemetryContext } from \"@fluidframework/runtime-definitions/internal\";\n\nimport { getEffectiveBatchId } from \"./batchManager.js\";\nimport type { BatchStartInfo } from \"./remoteMessageProcessor.js\";\n\n/**\n * Detects duplicate batches that can arise from the \"parallel fork\" scenario:\n * Container 1 is serialized, and Containers 2 and 3 are rehydrated from that state.\n * They both catch up and (re)connect in parallel (at the same time), submitting the same local state,\n * sharing the same batchId and sequence number.\n *\n * For \"serial fork\" detection scenarios see PendingStateManager.\n */\nexport class DuplicateBatchDetector {\n\t/**\n\t * Map from batchId to sequenceNumber\n\t */\n\tprivate readonly seqNumByBatchId = new Map<string, number>();\n\n\t/**\n\t * We map from sequenceNumber to batchId to find which ones we can stop tracking as MSN advances\n\t */\n\tprivate readonly batchIdsBySeqNum = new Map<number, string>();\n\n\t/**\n\t * Number of inbound batches processed since the last summary. Reset by getRecentBatchInfoForSummary.\n\t */\n\tprivate processedBatchCount = 0;\n\n\t/**\n\t * Largest tracked-batch count observed since the last summary. Reset by getRecentBatchInfoForSummary.\n\t */\n\tprivate peakTrackedBatchCount = 0;\n\n\t/**\n\t * Initialize from snapshot data if provided - otherwise initialize empty\n\t */\n\tconstructor(batchIdsFromSnapshot: [number, string][] | undefined) {\n\t\tif (batchIdsFromSnapshot) {\n\t\t\tfor (const [seqNum, batchId] of batchIdsFromSnapshot) {\n\t\t\t\tthis.batchIdsBySeqNum.set(seqNum, batchId);\n\t\t\t\tthis.seqNumByBatchId.set(batchId, seqNum);\n\t\t\t}\n\t\t\tthis.peakTrackedBatchCount = this.batchIdsBySeqNum.size;\n\t\t}\n\t}\n\n\t/**\n\t * Records this batch's batchId, and checks if it's a duplicate of a batch we've already seen.\n\t * If it's a duplicate, also return the sequence number of the other batch for logging.\n\t *\n\t * @remarks We also use the minimumSequenceNumber to clear out old batchIds that are no longer at risk for duplicates.\n\t */\n\tpublic processInboundBatch(\n\t\tbatchStart: BatchStartInfo,\n\t): { duplicate: true; otherSequenceNumber: number } | { duplicate: false } {\n\t\tconst { sequenceNumber, minimumSequenceNumber } = batchStart.keyMessage;\n\t\tthis.processedBatchCount++;\n\n\t\t// Glance at this batch's MSN. Any batchIds we're tracking with a lower sequence number are now safe to forget.\n\t\t// Why? Because any other client holding the same batch locally would have seen the earlier batch and closed before submitting its duplicate.\n\t\tthis.clearOldBatchIds(minimumSequenceNumber);\n\n\t\t// getEffectiveBatchId is only needed in the SUPER rare/surprising case where\n\t\t// the original batch (not resubmitted, so no batchId) arrives in parallel with a resubmitted batch.\n\t\t// In the presence of typical network conditions, this would not be possible\n\t\t// (the original batch should roundtrip WAY before another container could rehydrate, connect, and resubmit)\n\t\tconst batchId = getEffectiveBatchId(batchStart);\n\n\t\t// O(1) duplicate check + get otherSequenceNumber in one lookup\n\t\tconst otherSequenceNumber = this.seqNumByBatchId.get(batchId);\n\t\tif (otherSequenceNumber !== undefined) {\n\t\t\tassert(\n\t\t\t\tthis.batchIdsBySeqNum.get(otherSequenceNumber) === batchId,\n\t\t\t\t0xce0 /* batchIdToSeqNum and seqNumToBatchId should be in sync for duplicate */,\n\t\t\t);\n\t\t\treturn { duplicate: true, otherSequenceNumber };\n\t\t}\n\n\t\t// Now we know it's not a duplicate, so add it to the tracked batchIds and return.\n\t\tassert(\n\t\t\t!this.batchIdsBySeqNum.has(sequenceNumber),\n\t\t\t0xce1 /* seqNumToBatchId and batchIdToSeqNum should be in sync */,\n\t\t);\n\n\t\t// Add new batch\n\t\tthis.batchIdsBySeqNum.set(sequenceNumber, batchId);\n\t\tthis.seqNumByBatchId.set(batchId, sequenceNumber);\n\t\tif (this.batchIdsBySeqNum.size > this.peakTrackedBatchCount) {\n\t\t\tthis.peakTrackedBatchCount = this.batchIdsBySeqNum.size;\n\t\t}\n\n\t\treturn { duplicate: false };\n\t}\n\n\t/**\n\t * Batches that started before the MSN are not at risk for a sequenced duplicate to arrive,\n\t * since the batch start has been processed by all clients, and local batches are deduped and the forked client would close.\n\t */\n\tprivate clearOldBatchIds(msn: number): void {\n\t\tfor (const [sequenceNumber, batchId] of this.batchIdsBySeqNum) {\n\t\t\tif (sequenceNumber < msn) {\n\t\t\t\tthis.batchIdsBySeqNum.delete(sequenceNumber);\n\t\t\t\tthis.seqNumByBatchId.delete(batchId);\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns a snapshot of the state of the detector which can be included in a summary\n\t * and used to \"rehydrate\" this class when loading from a snapshot.\n\t *\n\t * @returns A serializable object representing the state of the detector, or undefined if there is nothing to save.\n\t */\n\tpublic getRecentBatchInfoForSummary(\n\t\ttelemetryContext?: ITelemetryContext,\n\t): [number, string][] | undefined {\n\t\tif (telemetryContext !== undefined) {\n\t\t\tconst prefix = \"fluid_DuplicateBatchDetector_\";\n\t\t\ttelemetryContext.set(prefix, \"recentBatchCount\", this.batchIdsBySeqNum.size);\n\t\t\ttelemetryContext.set(prefix, \"peakRecentBatchCount\", this.peakTrackedBatchCount);\n\t\t\ttelemetryContext.set(prefix, \"processedBatchCount\", this.processedBatchCount);\n\t\t}\n\n\t\t// Reset per-window perf counters so each summary covers only the activity since the\n\t\t// previous one. Peak resets to the current size (the floor for the next window).\n\t\tthis.processedBatchCount = 0;\n\t\tthis.peakTrackedBatchCount = this.batchIdsBySeqNum.size;\n\n\t\tif (this.batchIdsBySeqNum.size === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn [...this.batchIdsBySeqNum.entries()];\n\t}\n}\n"]}
1
+ {"version":3,"file":"duplicateBatchDetector.js","sourceRoot":"","sources":["../../src/opLifecycle/duplicateBatchDetector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAG7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD;;;;;;;GAOG;AACH,MAAM,OAAO,sBAAsB;IAWlC;;OAEG;IACH,YAAY,oBAAoD;QAbhE;;WAEG;QACc,oBAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;QAE7D;;WAEG;QACc,qBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAC;QAM7D,IAAI,oBAAoB,EAAE,CAAC;YAC1B,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC;gBACtD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAC3C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC3C,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACI,mBAAmB,CACzB,UAA0B;QAE1B,MAAM,EAAE,cAAc,EAAE,qBAAqB,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC;QAExE,+GAA+G;QAC/G,6IAA6I;QAC7I,IAAI,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;QAE7C,6EAA6E;QAC7E,oGAAoG;QACpG,4EAA4E;QAC5E,4GAA4G;QAC5G,MAAM,OAAO,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;QAEhD,+DAA+D;QAC/D,MAAM,mBAAmB,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9D,IAAI,mBAAmB,KAAK,SAAS,EAAE,CAAC;YACvC,MAAM,CACL,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,mBAAmB,CAAC,KAAK,OAAO,EAC1D,KAAK,CAAC,yEAAyE,CAC/E,CAAC;YACF,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC;QACjD,CAAC;QAED,kFAAkF;QAClF,MAAM,CACL,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,cAAc,CAAC,EAC1C,KAAK,CAAC,2DAA2D,CACjE,CAAC;QAEF,gBAAgB;QAChB,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QACnD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QAElD,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACK,gBAAgB,CAAC,GAAW;QACnC,KAAK,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC/D,IAAI,cAAc,GAAG,GAAG,EAAE,CAAC;gBAC1B,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;gBAC7C,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACtC,CAAC;iBAAM,CAAC;gBACP,MAAM;YACP,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACI,4BAA4B,CAClC,gBAAoC;QAEpC,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACtC,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,gBAAgB,EAAE,GAAG,CACpB,+BAA+B,EAC/B,kBAAkB,EAClB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAC1B,CAAC;QAEF,OAAO,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7C,CAAC;CACD","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 type { ITelemetryContext } from \"@fluidframework/runtime-definitions/internal\";\n\nimport { getEffectiveBatchId } from \"./batchManager.js\";\nimport type { BatchStartInfo } from \"./remoteMessageProcessor.js\";\n\n/**\n * Detects duplicate batches that can arise from the \"parallel fork\" scenario:\n * Container 1 is serialized, and Containers 2 and 3 are rehydrated from that state.\n * They both catch up and (re)connect in parallel (at the same time), submitting the same local state,\n * sharing the same batchId and sequence number.\n *\n * For \"serial fork\" detection scenarios see PendingStateManager.\n */\nexport class DuplicateBatchDetector {\n\t/**\n\t * Map from batchId to sequenceNumber\n\t */\n\tprivate readonly seqNumByBatchId = new Map<string, number>();\n\n\t/**\n\t * We map from sequenceNumber to batchId to find which ones we can stop tracking as MSN advances\n\t */\n\tprivate readonly batchIdsBySeqNum = new Map<number, string>();\n\n\t/**\n\t * Initialize from snapshot data if provided - otherwise initialize empty\n\t */\n\tconstructor(batchIdsFromSnapshot: [number, string][] | undefined) {\n\t\tif (batchIdsFromSnapshot) {\n\t\t\tfor (const [seqNum, batchId] of batchIdsFromSnapshot) {\n\t\t\t\tthis.batchIdsBySeqNum.set(seqNum, batchId);\n\t\t\t\tthis.seqNumByBatchId.set(batchId, seqNum);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Records this batch's batchId, and checks if it's a duplicate of a batch we've already seen.\n\t * If it's a duplicate, also return the sequence number of the other batch for logging.\n\t *\n\t * @remarks We also use the minimumSequenceNumber to clear out old batchIds that are no longer at risk for duplicates.\n\t */\n\tpublic processInboundBatch(\n\t\tbatchStart: BatchStartInfo,\n\t): { duplicate: true; otherSequenceNumber: number } | { duplicate: false } {\n\t\tconst { sequenceNumber, minimumSequenceNumber } = batchStart.keyMessage;\n\n\t\t// Glance at this batch's MSN. Any batchIds we're tracking with a lower sequence number are now safe to forget.\n\t\t// Why? Because any other client holding the same batch locally would have seen the earlier batch and closed before submitting its duplicate.\n\t\tthis.clearOldBatchIds(minimumSequenceNumber);\n\n\t\t// getEffectiveBatchId is only needed in the SUPER rare/surprising case where\n\t\t// the original batch (not resubmitted, so no batchId) arrives in parallel with a resubmitted batch.\n\t\t// In the presence of typical network conditions, this would not be possible\n\t\t// (the original batch should roundtrip WAY before another container could rehydrate, connect, and resubmit)\n\t\tconst batchId = getEffectiveBatchId(batchStart);\n\n\t\t// O(1) duplicate check + get otherSequenceNumber in one lookup\n\t\tconst otherSequenceNumber = this.seqNumByBatchId.get(batchId);\n\t\tif (otherSequenceNumber !== undefined) {\n\t\t\tassert(\n\t\t\t\tthis.batchIdsBySeqNum.get(otherSequenceNumber) === batchId,\n\t\t\t\t0xce0 /* batchIdToSeqNum and seqNumToBatchId should be in sync for duplicate */,\n\t\t\t);\n\t\t\treturn { duplicate: true, otherSequenceNumber };\n\t\t}\n\n\t\t// Now we know it's not a duplicate, so add it to the tracked batchIds and return.\n\t\tassert(\n\t\t\t!this.batchIdsBySeqNum.has(sequenceNumber),\n\t\t\t0xce1 /* seqNumToBatchId and batchIdToSeqNum should be in sync */,\n\t\t);\n\n\t\t// Add new batch\n\t\tthis.batchIdsBySeqNum.set(sequenceNumber, batchId);\n\t\tthis.seqNumByBatchId.set(batchId, sequenceNumber);\n\n\t\treturn { duplicate: false };\n\t}\n\n\t/**\n\t * Batches that started before the MSN are not at risk for a sequenced duplicate to arrive,\n\t * since the batch start has been processed by all clients, and local batches are deduped and the forked client would close.\n\t */\n\tprivate clearOldBatchIds(msn: number): void {\n\t\tfor (const [sequenceNumber, batchId] of this.batchIdsBySeqNum) {\n\t\t\tif (sequenceNumber < msn) {\n\t\t\t\tthis.batchIdsBySeqNum.delete(sequenceNumber);\n\t\t\t\tthis.seqNumByBatchId.delete(batchId);\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns a snapshot of the state of the detector which can be included in a summary\n\t * and used to \"rehydrate\" this class when loading from a snapshot.\n\t *\n\t * @returns A serializable object representing the state of the detector, or undefined if there is nothing to save.\n\t */\n\tpublic getRecentBatchInfoForSummary(\n\t\ttelemetryContext?: ITelemetryContext,\n\t): [number, string][] | undefined {\n\t\tif (this.batchIdsBySeqNum.size === 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\ttelemetryContext?.set(\n\t\t\t\"fluid_DuplicateBatchDetector_\",\n\t\t\t\"recentBatchCount\",\n\t\t\tthis.batchIdsBySeqNum.size,\n\t\t);\n\n\t\treturn [...this.batchIdsBySeqNum.entries()];\n\t}\n}\n"]}
@@ -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.101.0";
8
+ export declare const pkgVersion = "2.101.1";
9
9
  //# sourceMappingURL=packageVersion.d.ts.map
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export const pkgName = "@fluidframework/container-runtime";
8
- export const pkgVersion = "2.101.0";
8
+ export const pkgVersion = "2.101.1";
9
9
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,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.101.0\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,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.101.1\";\n"]}
@@ -17,7 +17,7 @@ export declare const runtimeCoreCompatDetails: {
17
17
  /**
18
18
  * The package version of the Runtime layer.
19
19
  */
20
- readonly pkgVersion: "2.101.0";
20
+ readonly pkgVersion: "2.101.1";
21
21
  /**
22
22
  * The current generation of the Runtime layer.
23
23
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-runtime",
3
- "version": "2.101.0",
3
+ "version": "2.101.1",
4
4
  "description": "Fluid container runtime",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -119,18 +119,18 @@
119
119
  "temp-directory": "nyc/.nyc_output"
120
120
  },
121
121
  "dependencies": {
122
- "@fluid-internal/client-utils": "~2.101.0",
123
- "@fluidframework/container-definitions": "~2.101.0",
124
- "@fluidframework/container-runtime-definitions": "~2.101.0",
125
- "@fluidframework/core-interfaces": "~2.101.0",
126
- "@fluidframework/core-utils": "~2.101.0",
127
- "@fluidframework/datastore": "~2.101.0",
128
- "@fluidframework/driver-definitions": "~2.101.0",
129
- "@fluidframework/driver-utils": "~2.101.0",
130
- "@fluidframework/id-compressor": "~2.101.0",
131
- "@fluidframework/runtime-definitions": "~2.101.0",
132
- "@fluidframework/runtime-utils": "~2.101.0",
133
- "@fluidframework/telemetry-utils": "~2.101.0",
122
+ "@fluid-internal/client-utils": "~2.101.1",
123
+ "@fluidframework/container-definitions": "~2.101.1",
124
+ "@fluidframework/container-runtime-definitions": "~2.101.1",
125
+ "@fluidframework/core-interfaces": "~2.101.1",
126
+ "@fluidframework/core-utils": "~2.101.1",
127
+ "@fluidframework/datastore": "~2.101.1",
128
+ "@fluidframework/driver-definitions": "~2.101.1",
129
+ "@fluidframework/driver-utils": "~2.101.1",
130
+ "@fluidframework/id-compressor": "~2.101.1",
131
+ "@fluidframework/runtime-definitions": "~2.101.1",
132
+ "@fluidframework/runtime-utils": "~2.101.1",
133
+ "@fluidframework/telemetry-utils": "~2.101.1",
134
134
  "@tylerbu/sorted-btree-es6": "^2.1.1",
135
135
  "double-ended-queue": "^2.1.0-0",
136
136
  "lz4js": "^0.2.0",
@@ -140,16 +140,16 @@
140
140
  "devDependencies": {
141
141
  "@arethetypeswrong/cli": "^0.18.2",
142
142
  "@biomejs/biome": "~2.4.5",
143
- "@fluid-internal/mocha-test-setup": "~2.101.0",
144
- "@fluid-private/stochastic-test-utils": "~2.101.0",
145
- "@fluid-private/test-pairwise-generator": "~2.101.0",
143
+ "@fluid-internal/mocha-test-setup": "~2.101.1",
144
+ "@fluid-private/stochastic-test-utils": "~2.101.1",
145
+ "@fluid-private/test-pairwise-generator": "~2.101.1",
146
146
  "@fluid-tools/benchmark": "^0.59.0",
147
147
  "@fluid-tools/build-cli": "^0.65.0",
148
148
  "@fluidframework/build-common": "^2.0.3",
149
149
  "@fluidframework/build-tools": "^0.65.0",
150
150
  "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.92.0",
151
151
  "@fluidframework/eslint-config-fluid": "^9.0.0",
152
- "@fluidframework/test-runtime-utils": "~2.101.0",
152
+ "@fluidframework/test-runtime-utils": "~2.101.1",
153
153
  "@microsoft/api-extractor": "7.58.1",
154
154
  "@types/double-ended-queue": "^2.1.0",
155
155
  "@types/lz4js": "^0.2.0",
@@ -1890,38 +1890,20 @@ export class ContainerRuntime
1890
1890
  this.mc.config.getNumber("Fluid.ContainerRuntime.StagingModeAutoFlushThreshold") ??
1891
1891
  runtimeOptions.stagingModeAutoFlushThreshold ??
1892
1892
  defaultStagingModeAutoFlushThreshold;
1893
- // BatchId tracking powers DuplicateBatchDetector (catching forked-container duplicates)
1894
- // and is also a prerequisite for the Offline Load feature. It is enabled by default
1895
- // when both TurnBased flush mode and grouped batching are active; the kill-switch
1896
- // below allows disabling it without a code change if a regression is observed.
1897
- // Grouped batching is required because resubmits can produce empty batches that must
1898
- // still be sent on the wire as a placeholder grouped batch to preserve their batchId
1899
- // (see OpGroupingManager.createEmptyGroupedBatch / outbox.flushEmptyBatch).
1900
- // Offline Load requires both prerequisites, so a consumer that opts into it without
1901
- // them gets an explicit UsageError rather than silent degradation.
1902
- const offlineLoadRequested =
1903
- this.mc.config.getBoolean("Fluid.Container.enableOfflineFull") === true;
1904
- const disableBatchIdTracking =
1905
- this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableBatchIdTracking") === true;
1906
-
1907
- if (offlineLoadRequested && this._flushMode !== FlushMode.TurnBased) {
1893
+ this.batchIdTrackingEnabled =
1894
+ this.mc.config.getBoolean("Fluid.Container.enableOfflineFull") ??
1895
+ this.mc.config.getBoolean("Fluid.ContainerRuntime.enableBatchIdTracking") ??
1896
+ false;
1897
+
1898
+ if (this.batchIdTrackingEnabled && this._flushMode !== FlushMode.TurnBased) {
1908
1899
  const error = new UsageError("Offline mode is only supported in turn-based mode");
1909
1900
  this.closeFn(error);
1910
1901
  throw error;
1911
1902
  }
1912
- if (offlineLoadRequested && !this.groupedBatchingEnabled) {
1913
- const error = new UsageError("Offline mode requires grouped batching to be enabled");
1914
- this.closeFn(error);
1915
- throw error;
1916
- }
1917
-
1918
- this.batchIdTrackingEnabled =
1919
- !disableBatchIdTracking &&
1920
- this._flushMode === FlushMode.TurnBased &&
1921
- this.groupedBatchingEnabled;
1922
1903
 
1923
- // DuplicateBatchDetector maintains a cache of all batchIds/sequenceNumbers within the
1924
- // collab window. Skip allocating it when batchId tracking is off.
1904
+ // DuplicateBatchDetection is only enabled if Offline Load is enabled
1905
+ // It maintains a cache of all batchIds/sequenceNumbers within the collab window.
1906
+ // Don't waste resources doing so if not needed.
1925
1907
  if (this.batchIdTrackingEnabled) {
1926
1908
  this.duplicateBatchDetector = new DuplicateBatchDetector(recentBatchInfo);
1927
1909
  }
@@ -28,16 +28,6 @@ export class DuplicateBatchDetector {
28
28
  */
29
29
  private readonly batchIdsBySeqNum = new Map<number, string>();
30
30
 
31
- /**
32
- * Number of inbound batches processed since the last summary. Reset by getRecentBatchInfoForSummary.
33
- */
34
- private processedBatchCount = 0;
35
-
36
- /**
37
- * Largest tracked-batch count observed since the last summary. Reset by getRecentBatchInfoForSummary.
38
- */
39
- private peakTrackedBatchCount = 0;
40
-
41
31
  /**
42
32
  * Initialize from snapshot data if provided - otherwise initialize empty
43
33
  */
@@ -47,7 +37,6 @@ export class DuplicateBatchDetector {
47
37
  this.batchIdsBySeqNum.set(seqNum, batchId);
48
38
  this.seqNumByBatchId.set(batchId, seqNum);
49
39
  }
50
- this.peakTrackedBatchCount = this.batchIdsBySeqNum.size;
51
40
  }
52
41
  }
53
42
 
@@ -61,7 +50,6 @@ export class DuplicateBatchDetector {
61
50
  batchStart: BatchStartInfo,
62
51
  ): { duplicate: true; otherSequenceNumber: number } | { duplicate: false } {
63
52
  const { sequenceNumber, minimumSequenceNumber } = batchStart.keyMessage;
64
- this.processedBatchCount++;
65
53
 
66
54
  // Glance at this batch's MSN. Any batchIds we're tracking with a lower sequence number are now safe to forget.
67
55
  // Why? Because any other client holding the same batch locally would have seen the earlier batch and closed before submitting its duplicate.
@@ -92,9 +80,6 @@ export class DuplicateBatchDetector {
92
80
  // Add new batch
93
81
  this.batchIdsBySeqNum.set(sequenceNumber, batchId);
94
82
  this.seqNumByBatchId.set(batchId, sequenceNumber);
95
- if (this.batchIdsBySeqNum.size > this.peakTrackedBatchCount) {
96
- this.peakTrackedBatchCount = this.batchIdsBySeqNum.size;
97
- }
98
83
 
99
84
  return { duplicate: false };
100
85
  }
@@ -123,22 +108,16 @@ export class DuplicateBatchDetector {
123
108
  public getRecentBatchInfoForSummary(
124
109
  telemetryContext?: ITelemetryContext,
125
110
  ): [number, string][] | undefined {
126
- if (telemetryContext !== undefined) {
127
- const prefix = "fluid_DuplicateBatchDetector_";
128
- telemetryContext.set(prefix, "recentBatchCount", this.batchIdsBySeqNum.size);
129
- telemetryContext.set(prefix, "peakRecentBatchCount", this.peakTrackedBatchCount);
130
- telemetryContext.set(prefix, "processedBatchCount", this.processedBatchCount);
131
- }
132
-
133
- // Reset per-window perf counters so each summary covers only the activity since the
134
- // previous one. Peak resets to the current size (the floor for the next window).
135
- this.processedBatchCount = 0;
136
- this.peakTrackedBatchCount = this.batchIdsBySeqNum.size;
137
-
138
111
  if (this.batchIdsBySeqNum.size === 0) {
139
112
  return undefined;
140
113
  }
141
114
 
115
+ telemetryContext?.set(
116
+ "fluid_DuplicateBatchDetector_",
117
+ "recentBatchCount",
118
+ this.batchIdsBySeqNum.size,
119
+ );
120
+
142
121
  return [...this.batchIdsBySeqNum.entries()];
143
122
  }
144
123
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.101.0";
9
+ export const pkgVersion = "2.101.1";