@fluidframework/container-runtime 2.0.0-dev-rc.5.0.0.271262 → 2.0.0-dev-rc.5.0.0.272251

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.
Files changed (212) hide show
  1. package/api-extractor/api-extractor-lint-bundle.json +5 -0
  2. package/api-extractor/api-extractor-lint-legacy.cjs.json +5 -0
  3. package/api-extractor/api-extractor-lint-legacy.esm.json +5 -0
  4. package/api-extractor/api-extractor-lint-public.cjs.json +5 -0
  5. package/api-extractor/api-extractor-lint-public.esm.json +5 -0
  6. package/api-report/container-runtime.alpha.api.md +1 -1
  7. package/container-runtime.test-files.tar +0 -0
  8. package/dist/batchTracker.js +1 -5
  9. package/dist/batchTracker.js.map +1 -1
  10. package/dist/blobManager.js +14 -39
  11. package/dist/blobManager.js.map +1 -1
  12. package/dist/channelCollection.d.ts +12 -2
  13. package/dist/channelCollection.d.ts.map +1 -1
  14. package/dist/channelCollection.js +95 -110
  15. package/dist/channelCollection.js.map +1 -1
  16. package/dist/connectionTelemetry.js +14 -32
  17. package/dist/connectionTelemetry.js.map +1 -1
  18. package/dist/containerHandleContext.js +0 -4
  19. package/dist/containerHandleContext.js.map +1 -1
  20. package/dist/containerRuntime.d.ts +2 -1
  21. package/dist/containerRuntime.d.ts.map +1 -1
  22. package/dist/containerRuntime.js +51 -142
  23. package/dist/containerRuntime.js.map +1 -1
  24. package/dist/dataStore.js +1 -9
  25. package/dist/dataStore.js.map +1 -1
  26. package/dist/dataStoreContext.d.ts +2 -1
  27. package/dist/dataStoreContext.d.ts.map +1 -1
  28. package/dist/dataStoreContext.js +102 -143
  29. package/dist/dataStoreContext.js.map +1 -1
  30. package/dist/dataStoreContexts.js +28 -29
  31. package/dist/dataStoreContexts.js.map +1 -1
  32. package/dist/dataStoreRegistry.js +0 -1
  33. package/dist/dataStoreRegistry.js.map +1 -1
  34. package/dist/deltaManagerProxies.js +28 -33
  35. package/dist/deltaManagerProxies.js.map +1 -1
  36. package/dist/deltaScheduler.js +9 -13
  37. package/dist/deltaScheduler.js.map +1 -1
  38. package/dist/error.js +1 -2
  39. package/dist/error.js.map +1 -1
  40. package/dist/gc/garbageCollection.d.ts +4 -2
  41. package/dist/gc/garbageCollection.d.ts.map +1 -1
  42. package/dist/gc/garbageCollection.js +25 -43
  43. package/dist/gc/garbageCollection.js.map +1 -1
  44. package/dist/gc/gcDefinitions.d.ts +7 -4
  45. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  46. package/dist/gc/gcDefinitions.js.map +1 -1
  47. package/dist/gc/gcSummaryStateTracker.js +14 -19
  48. package/dist/gc/gcSummaryStateTracker.js.map +1 -1
  49. package/dist/gc/gcTelemetry.d.ts +1 -1
  50. package/dist/gc/gcTelemetry.d.ts.map +1 -1
  51. package/dist/gc/gcTelemetry.js +5 -17
  52. package/dist/gc/gcTelemetry.js.map +1 -1
  53. package/dist/gc/gcUnreferencedStateTracker.js +1 -12
  54. package/dist/gc/gcUnreferencedStateTracker.js.map +1 -1
  55. package/dist/opLifecycle/batchManager.js +3 -5
  56. package/dist/opLifecycle/batchManager.js.map +1 -1
  57. package/dist/opLifecycle/opCompressor.js +0 -1
  58. package/dist/opLifecycle/opCompressor.js.map +1 -1
  59. package/dist/opLifecycle/opDecompressor.js +4 -6
  60. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  61. package/dist/opLifecycle/opGroupingManager.js +1 -3
  62. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  63. package/dist/opLifecycle/opSplitter.js +0 -6
  64. package/dist/opLifecycle/opSplitter.js.map +1 -1
  65. package/dist/opLifecycle/outbox.js +10 -15
  66. package/dist/opLifecycle/outbox.js.map +1 -1
  67. package/dist/opLifecycle/remoteMessageProcessor.js +0 -3
  68. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  69. package/dist/packageVersion.d.ts +1 -1
  70. package/dist/packageVersion.js +1 -1
  71. package/dist/packageVersion.js.map +1 -1
  72. package/dist/pendingStateManager.js +14 -20
  73. package/dist/pendingStateManager.js.map +1 -1
  74. package/dist/scheduleManager.js +4 -15
  75. package/dist/scheduleManager.js.map +1 -1
  76. package/dist/storageServiceWithAttachBlobs.js +0 -1
  77. package/dist/storageServiceWithAttachBlobs.js.map +1 -1
  78. package/dist/summary/documentSchema.js +1 -17
  79. package/dist/summary/documentSchema.js.map +1 -1
  80. package/dist/summary/orderedClientElection.js +11 -19
  81. package/dist/summary/orderedClientElection.js.map +1 -1
  82. package/dist/summary/runWhileConnectedCoordinator.js +2 -4
  83. package/dist/summary/runWhileConnectedCoordinator.js.map +1 -1
  84. package/dist/summary/runningSummarizer.js +38 -56
  85. package/dist/summary/runningSummarizer.js.map +1 -1
  86. package/dist/summary/summarizer.js +8 -17
  87. package/dist/summary/summarizer.js.map +1 -1
  88. package/dist/summary/summarizerClientElection.js +7 -18
  89. package/dist/summary/summarizerClientElection.js.map +1 -1
  90. package/dist/summary/summarizerHeuristics.js +25 -30
  91. package/dist/summary/summarizerHeuristics.js.map +1 -1
  92. package/dist/summary/summarizerNode/summarizerNode.js +3 -12
  93. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  94. package/dist/summary/summarizerNode/summarizerNodeUtils.js +0 -2
  95. package/dist/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  96. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +6 -20
  97. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  98. package/dist/summary/summaryCollection.js +11 -23
  99. package/dist/summary/summaryCollection.js.map +1 -1
  100. package/dist/summary/summaryGenerator.js +5 -12
  101. package/dist/summary/summaryGenerator.js.map +1 -1
  102. package/dist/summary/summaryManager.js +48 -58
  103. package/dist/summary/summaryManager.js.map +1 -1
  104. package/dist/throttler.js +1 -4
  105. package/dist/throttler.js.map +1 -1
  106. package/lib/batchTracker.js +1 -5
  107. package/lib/batchTracker.js.map +1 -1
  108. package/lib/blobManager.js +14 -39
  109. package/lib/blobManager.js.map +1 -1
  110. package/lib/channelCollection.d.ts +12 -2
  111. package/lib/channelCollection.d.ts.map +1 -1
  112. package/lib/channelCollection.js +95 -110
  113. package/lib/channelCollection.js.map +1 -1
  114. package/lib/connectionTelemetry.js +14 -32
  115. package/lib/connectionTelemetry.js.map +1 -1
  116. package/lib/containerHandleContext.js +0 -4
  117. package/lib/containerHandleContext.js.map +1 -1
  118. package/lib/containerRuntime.d.ts +2 -1
  119. package/lib/containerRuntime.d.ts.map +1 -1
  120. package/lib/containerRuntime.js +52 -143
  121. package/lib/containerRuntime.js.map +1 -1
  122. package/lib/dataStore.js +1 -9
  123. package/lib/dataStore.js.map +1 -1
  124. package/lib/dataStoreContext.d.ts +2 -1
  125. package/lib/dataStoreContext.d.ts.map +1 -1
  126. package/lib/dataStoreContext.js +102 -143
  127. package/lib/dataStoreContext.js.map +1 -1
  128. package/lib/dataStoreContexts.js +28 -29
  129. package/lib/dataStoreContexts.js.map +1 -1
  130. package/lib/dataStoreRegistry.js +0 -1
  131. package/lib/dataStoreRegistry.js.map +1 -1
  132. package/lib/deltaManagerProxies.js +28 -33
  133. package/lib/deltaManagerProxies.js.map +1 -1
  134. package/lib/deltaScheduler.js +9 -13
  135. package/lib/deltaScheduler.js.map +1 -1
  136. package/lib/error.js +1 -2
  137. package/lib/error.js.map +1 -1
  138. package/lib/gc/garbageCollection.d.ts +4 -2
  139. package/lib/gc/garbageCollection.d.ts.map +1 -1
  140. package/lib/gc/garbageCollection.js +25 -43
  141. package/lib/gc/garbageCollection.js.map +1 -1
  142. package/lib/gc/gcDefinitions.d.ts +7 -4
  143. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  144. package/lib/gc/gcDefinitions.js.map +1 -1
  145. package/lib/gc/gcSummaryStateTracker.js +14 -19
  146. package/lib/gc/gcSummaryStateTracker.js.map +1 -1
  147. package/lib/gc/gcTelemetry.d.ts +1 -1
  148. package/lib/gc/gcTelemetry.d.ts.map +1 -1
  149. package/lib/gc/gcTelemetry.js +5 -17
  150. package/lib/gc/gcTelemetry.js.map +1 -1
  151. package/lib/gc/gcUnreferencedStateTracker.js +1 -12
  152. package/lib/gc/gcUnreferencedStateTracker.js.map +1 -1
  153. package/lib/opLifecycle/batchManager.js +3 -5
  154. package/lib/opLifecycle/batchManager.js.map +1 -1
  155. package/lib/opLifecycle/opCompressor.js +0 -1
  156. package/lib/opLifecycle/opCompressor.js.map +1 -1
  157. package/lib/opLifecycle/opDecompressor.js +4 -6
  158. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  159. package/lib/opLifecycle/opGroupingManager.js +1 -3
  160. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  161. package/lib/opLifecycle/opSplitter.js +0 -6
  162. package/lib/opLifecycle/opSplitter.js.map +1 -1
  163. package/lib/opLifecycle/outbox.js +10 -15
  164. package/lib/opLifecycle/outbox.js.map +1 -1
  165. package/lib/opLifecycle/remoteMessageProcessor.js +0 -3
  166. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  167. package/lib/packageVersion.d.ts +1 -1
  168. package/lib/packageVersion.js +1 -1
  169. package/lib/packageVersion.js.map +1 -1
  170. package/lib/pendingStateManager.js +14 -20
  171. package/lib/pendingStateManager.js.map +1 -1
  172. package/lib/scheduleManager.js +4 -15
  173. package/lib/scheduleManager.js.map +1 -1
  174. package/lib/storageServiceWithAttachBlobs.js +0 -1
  175. package/lib/storageServiceWithAttachBlobs.js.map +1 -1
  176. package/lib/summary/documentSchema.js +1 -17
  177. package/lib/summary/documentSchema.js.map +1 -1
  178. package/lib/summary/orderedClientElection.js +11 -19
  179. package/lib/summary/orderedClientElection.js.map +1 -1
  180. package/lib/summary/runWhileConnectedCoordinator.js +2 -4
  181. package/lib/summary/runWhileConnectedCoordinator.js.map +1 -1
  182. package/lib/summary/runningSummarizer.js +38 -56
  183. package/lib/summary/runningSummarizer.js.map +1 -1
  184. package/lib/summary/summarizer.js +8 -17
  185. package/lib/summary/summarizer.js.map +1 -1
  186. package/lib/summary/summarizerClientElection.js +7 -18
  187. package/lib/summary/summarizerClientElection.js.map +1 -1
  188. package/lib/summary/summarizerHeuristics.js +25 -30
  189. package/lib/summary/summarizerHeuristics.js.map +1 -1
  190. package/lib/summary/summarizerNode/summarizerNode.js +3 -12
  191. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  192. package/lib/summary/summarizerNode/summarizerNodeUtils.js +0 -2
  193. package/lib/summary/summarizerNode/summarizerNodeUtils.js.map +1 -1
  194. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +6 -20
  195. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  196. package/lib/summary/summaryCollection.js +11 -23
  197. package/lib/summary/summaryCollection.js.map +1 -1
  198. package/lib/summary/summaryGenerator.js +5 -12
  199. package/lib/summary/summaryGenerator.js.map +1 -1
  200. package/lib/summary/summaryManager.js +48 -58
  201. package/lib/summary/summaryManager.js.map +1 -1
  202. package/lib/throttler.js +1 -4
  203. package/lib/throttler.js.map +1 -1
  204. package/package.json +27 -18
  205. package/src/channelCollection.ts +133 -123
  206. package/src/containerRuntime.ts +31 -4
  207. package/src/dataStoreContext.ts +3 -2
  208. package/src/gc/garbageCollection.ts +24 -7
  209. package/src/gc/gcDefinitions.ts +16 -4
  210. package/src/gc/gcTelemetry.ts +1 -7
  211. package/src/packageVersion.ts +1 -1
  212. package/tsdoc.json +4 -0
@@ -400,7 +400,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
400
400
  const foundGCData = processAttachMessageGCData(attachMessage.snapshot, (nodeId, toPath) => {
401
401
  // nodeId is the relative path under the node being attached. Always starts with "/", but no trailing "/" after an id
402
402
  const fromPath = `/${attachMessage.id}${nodeId === "/" ? "" : nodeId}`;
403
- this.parentContext.addedGCOutboundRoute(fromPath, toPath);
403
+ this.parentContext.addedGCOutboundRoute(fromPath, toPath, message.timestamp);
404
404
  });
405
405
 
406
406
  // Only log once per container to avoid noise/cost.
@@ -495,13 +495,18 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
495
495
  const aliasResult = this.processAliasMessageCore(
496
496
  aliasMessage.internalId,
497
497
  aliasMessage.alias,
498
+ message.timestamp,
498
499
  );
499
500
  if (local) {
500
501
  resolve(aliasResult);
501
502
  }
502
503
  }
503
504
 
504
- public processAliasMessageCore(internalId: string, alias: string): boolean {
505
+ public processAliasMessageCore(
506
+ internalId: string,
507
+ alias: string,
508
+ messageTimestampMs?: number,
509
+ ): boolean {
505
510
  if (this.alreadyProcessed(alias)) {
506
511
  return false;
507
512
  }
@@ -521,7 +526,11 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
521
526
  return false;
522
527
  }
523
528
 
524
- this.parentContext.addedGCOutboundRoute("/", `/${internalId}`);
529
+ // If message timestamp doesn't exist, this is called in a detached container. Don't notify GC in that case
530
+ // because it doesn't run in detached container and doesn't need to know about this route.
531
+ if (messageTimestampMs) {
532
+ this.parentContext.addedGCOutboundRoute("/", `/${internalId}`, messageTimestampMs);
533
+ }
525
534
 
526
535
  this.aliasMap.set(alias, context.id);
527
536
  this.aliasedDataStores.add(context.id);
@@ -829,7 +838,11 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
829
838
  envelope.address,
830
839
  transformed.contents,
831
840
  (fromPath: string, toPath: string) =>
832
- this.parentContext.addedGCOutboundRoute(fromPath, toPath),
841
+ this.parentContext.addedGCOutboundRoute(
842
+ fromPath,
843
+ toPath,
844
+ message.timestamp,
845
+ ),
833
846
  );
834
847
  break;
835
848
  }
@@ -1075,86 +1088,30 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
1075
1088
  return this.contexts.size;
1076
1089
  }
1077
1090
 
1078
- public async summarize(
1079
- fullTree: boolean,
1080
- trackState: boolean,
1081
- telemetryContext?: ITelemetryContext,
1082
- ): Promise<ISummaryTreeWithStats> {
1083
- const summaryBuilder = new SummaryTreeBuilder();
1084
-
1085
- // Iterate over each store and ask it to snapshot
1086
- await Promise.all(
1087
- Array.from(this.contexts)
1088
- .filter(([_, context]) => {
1089
- // Summarizer works only with clients with no local changes. A data store in attaching
1090
- // state indicates an op was sent to attach a local data store, and the the attach op
1091
- // had not yet round tripped back to the client.
1092
- if (context.attachState === AttachState.Attaching) {
1093
- // Formerly assert 0x588
1094
- const error = DataProcessingError.create(
1095
- "Local data store detected in attaching state during summarize",
1096
- "summarize",
1097
- );
1098
- throw error;
1099
- }
1100
- return context.attachState === AttachState.Attached;
1101
- })
1102
- .map(async ([contextId, context]) => {
1103
- const contextSummary = await context.summarize(
1104
- fullTree,
1105
- trackState,
1106
- telemetryContext,
1107
- );
1108
- summaryBuilder.addWithStats(contextId, contextSummary);
1109
- }),
1110
- );
1111
-
1112
- return summaryBuilder.getSummaryTree();
1113
- }
1114
-
1115
1091
  /**
1116
1092
  * Create a summary. Used when attaching or serializing a detached container.
1117
1093
  */
1118
1094
  public getAttachSummary(telemetryContext?: ITelemetryContext): ISummaryTreeWithStats {
1119
1095
  const builder = new SummaryTreeBuilder();
1120
- // Attaching graph of some stores can cause other stores to get bound too.
1121
- // So keep taking summary until no new stores get bound.
1122
- let notBoundContextsLength: number;
1123
- do {
1124
- const builderTree = builder.summary.tree;
1125
- notBoundContextsLength = this.contexts.notBoundLength();
1126
- // Iterate over each data store and ask it to snapshot
1127
- Array.from(this.contexts)
1128
- .filter(
1129
- ([key, _]) =>
1130
- // Take summary of bounded data stores only, make sure we haven't summarized them already
1131
- // and no attach op has been fired for that data store because for loader versions <= 0.24
1132
- // we set attach state as "attaching" before taking createNew summary.
1133
- !(
1134
- this.contexts.isNotBound(key) ||
1135
- builderTree[key] ||
1136
- this.attachOpFiredForDataStore.has(key)
1137
- ),
1138
- )
1139
- .map(([key, value]) => {
1140
- let dataStoreSummary: ISummarizeResult;
1141
- if (value.isLoaded) {
1142
- dataStoreSummary = value.getAttachSummary(telemetryContext);
1143
- } else {
1144
- // If this data store is not yet loaded, then there should be no changes in the snapshot from
1145
- // which it was created as it is detached container. So just use the previous snapshot.
1146
- assert(
1147
- !!this.baseSnapshot,
1148
- 0x166 /* "BaseSnapshot should be there as detached container loaded from snapshot" */,
1149
- );
1150
- dataStoreSummary = convertSnapshotTreeToSummaryTree(
1151
- getSnapshotTree(this.baseSnapshot).trees[key],
1152
- );
1153
- }
1154
- builder.addWithStats(key, dataStoreSummary);
1155
- });
1156
- } while (notBoundContextsLength !== this.contexts.notBoundLength());
1157
-
1096
+ this.visitLocalBoundContextsDuringAttach(
1097
+ (contextId: string, context: FluidDataStoreContext) => {
1098
+ let dataStoreSummary: ISummarizeResult;
1099
+ if (context.isLoaded) {
1100
+ dataStoreSummary = context.getAttachSummary(telemetryContext);
1101
+ } else {
1102
+ // If this data store is not yet loaded, then there should be no changes in the snapshot from
1103
+ // which it was created as it is detached container. So just use the previous snapshot.
1104
+ assert(
1105
+ !!this.baseSnapshot,
1106
+ 0x166 /* "BaseSnapshot should be there as detached container loaded from snapshot" */,
1107
+ );
1108
+ dataStoreSummary = convertSnapshotTreeToSummaryTree(
1109
+ getSnapshotTree(this.baseSnapshot).trees[contextId],
1110
+ );
1111
+ }
1112
+ builder.addWithStats(contextId, dataStoreSummary);
1113
+ },
1114
+ );
1158
1115
  return builder.getSummaryTree();
1159
1116
  }
1160
1117
 
@@ -1163,31 +1120,100 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
1163
1120
  */
1164
1121
  public getAttachGCData(telemetryContext?: ITelemetryContext): IGarbageCollectionData {
1165
1122
  const builder = new GCDataBuilder();
1166
- // Attaching graph of some stores can cause other stores to get bound too.
1167
- // So keep taking summary until no new stores get bound.
1168
- let notBoundContextsLength: number;
1169
- do {
1170
- notBoundContextsLength = this.contexts.notBoundLength();
1171
- // Iterate over each data store and ask for its GC data.
1172
- Array.from(this.contexts)
1173
- .filter(
1174
- ([key, _]) =>
1175
- // Take GC data of bounded data stores only.
1176
- !this.contexts.isNotBound(key),
1177
- )
1178
- .map(([key, value]) => {
1179
- const contextGCData = value.getAttachGCData(telemetryContext);
1180
- // Prefix the child's id to the ids of its GC nodes so they can be identified as belonging to the child.
1181
- // This also gradually builds the id of each node to be a path from the root.
1182
- builder.prefixAndAddNodes(key, contextGCData.gcNodes);
1183
- });
1184
- } while (notBoundContextsLength !== this.contexts.notBoundLength());
1185
-
1123
+ this.visitLocalBoundContextsDuringAttach(
1124
+ (contextId: string, context: FluidDataStoreContext) => {
1125
+ const contextGCData = context.getAttachGCData(telemetryContext);
1126
+ // Prefix the child's id to the ids of its GC nodes so they can be identified as belonging to the child.
1127
+ // This also gradually builds the id of each node to be a path from the root.
1128
+ builder.prefixAndAddNodes(contextId, contextGCData.gcNodes);
1129
+ },
1130
+ );
1186
1131
  // Get the outbound routes (aliased data stores) and add a GC node for this channel.
1187
1132
  builder.addNode("/", Array.from(this.aliasedDataStores));
1188
1133
  return builder.getGCData();
1189
1134
  }
1190
1135
 
1136
+ /**
1137
+ * Helper method for preparing to attach this channel.
1138
+ * Runs the callback for each bound context to incorporate its data however the caller specifies
1139
+ */
1140
+ private visitLocalBoundContextsDuringAttach(
1141
+ visitor: (contextId: string, context: FluidDataStoreContext) => void,
1142
+ ): void {
1143
+ const visitedContexts = new Set<string>();
1144
+ let visitedLength = -1;
1145
+ let notBoundContextsLength = -1;
1146
+ while (
1147
+ visitedLength !== visitedContexts.size &&
1148
+ notBoundContextsLength !== this.contexts.notBoundLength()
1149
+ ) {
1150
+ // detect changes in the visitedContexts set, as on visiting a context
1151
+ // it could could make contexts available by removing other contexts
1152
+ // from the not bound context list, so we need to ensure those get processed as well.
1153
+ // only once the loop can run with no new contexts added to the visitedContexts set do we
1154
+ // know for sure all possible contexts have been visited.
1155
+ visitedLength = visitedContexts.size;
1156
+ notBoundContextsLength = this.contexts.notBoundLength();
1157
+ for (const [contextId, context] of this.contexts) {
1158
+ if (
1159
+ !(
1160
+ visitedContexts.has(contextId) ||
1161
+ this.contexts.isNotBound(contextId) ||
1162
+ this.attachOpFiredForDataStore.has(contextId)
1163
+ )
1164
+ ) {
1165
+ visitor(contextId, context);
1166
+ visitedContexts.add(contextId);
1167
+ }
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ /**
1173
+ * Helper method for preparing to summarize this channel.
1174
+ * Runs the callback for each bound context to incorporate its data however the caller specifies
1175
+ */
1176
+ private async visitContextsDuringSummary(
1177
+ visitor: (contextId: string, context: FluidDataStoreContext) => Promise<void>,
1178
+ ): Promise<void> {
1179
+ for (const [contextId, context] of this.contexts) {
1180
+ // Summarizer client and hence GC works only with clients with no local changes. A data store in
1181
+ // attaching state indicates an op was sent to attach a local data store, and the the attach op
1182
+ // had not yet round tripped back to the client.
1183
+ // Formerly assert 0x589
1184
+ if (context.attachState === AttachState.Attaching) {
1185
+ const error = DataProcessingError.create(
1186
+ "Local data store detected in attaching state",
1187
+ "summarize/getGCData",
1188
+ );
1189
+ throw error;
1190
+ }
1191
+
1192
+ if (context.attachState === AttachState.Attached) {
1193
+ await visitor(contextId, context);
1194
+ }
1195
+ }
1196
+ }
1197
+
1198
+ public async summarize(
1199
+ fullTree: boolean,
1200
+ trackState: boolean,
1201
+ telemetryContext?: ITelemetryContext,
1202
+ ): Promise<ISummaryTreeWithStats> {
1203
+ const summaryBuilder = new SummaryTreeBuilder();
1204
+ await this.visitContextsDuringSummary(
1205
+ async (contextId: string, context: FluidDataStoreContext) => {
1206
+ const contextSummary = await context.summarize(
1207
+ fullTree,
1208
+ trackState,
1209
+ telemetryContext,
1210
+ );
1211
+ summaryBuilder.addWithStats(contextId, contextSummary);
1212
+ },
1213
+ );
1214
+ return summaryBuilder.getSummaryTree();
1215
+ }
1216
+
1191
1217
  /**
1192
1218
  * Generates data used for garbage collection. It does the following:
1193
1219
  *
@@ -1203,30 +1229,13 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
1203
1229
  */
1204
1230
  public async getGCData(fullGC: boolean = false): Promise<IGarbageCollectionData> {
1205
1231
  const builder = new GCDataBuilder();
1206
- // Iterate over each store and get their GC data.
1207
- await Promise.all(
1208
- Array.from(this.contexts)
1209
- .filter(([_, context]) => {
1210
- // Summarizer client and hence GC works only with clients with no local changes. A data store in
1211
- // attaching state indicates an op was sent to attach a local data store, and the the attach op
1212
- // had not yet round tripped back to the client.
1213
- // Formerly assert 0x589
1214
- if (context.attachState === AttachState.Attaching) {
1215
- const error = DataProcessingError.create(
1216
- "Local data store detected in attaching state while running GC",
1217
- "getGCData",
1218
- );
1219
- throw error;
1220
- }
1221
-
1222
- return context.attachState === AttachState.Attached;
1223
- })
1224
- .map(async ([contextId, context]) => {
1225
- const contextGCData = await context.getGCData(fullGC);
1226
- // Prefix the child's id to the ids of its GC nodes so they can be identified as belonging to the child.
1227
- // This also gradually builds the id of each node to be a path from the root.
1228
- builder.prefixAndAddNodes(contextId, contextGCData.gcNodes);
1229
- }),
1232
+ await this.visitContextsDuringSummary(
1233
+ async (contextId: string, context: FluidDataStoreContext) => {
1234
+ const contextGCData = await context.getGCData(fullGC);
1235
+ // Prefix the child's id to the ids of its GC nodes so they can be identified as belonging to the child.
1236
+ // This also gradually builds the id of each node to be a path from the root.
1237
+ builder.prefixAndAddNodes(contextId, contextGCData.gcNodes);
1238
+ },
1230
1239
  );
1231
1240
 
1232
1241
  // Get the outbound routes and add a GC node for this channel.
@@ -1432,6 +1441,7 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
1432
1441
  packagePath: details.pkg,
1433
1442
  request,
1434
1443
  headerData,
1444
+ timestampMs: undefined, // This will be added by the parent context if needed.
1435
1445
  });
1436
1446
 
1437
1447
  const dataStore = await dataStoreContext.realize();
@@ -121,6 +121,7 @@ import {
121
121
  loggerToMonitoringContext,
122
122
  raiseConnectedEvent,
123
123
  wrapError,
124
+ tagCodeArtifacts,
124
125
  } from "@fluidframework/telemetry-utils/internal";
125
126
  import { v4 as uuid } from "uuid";
126
127
 
@@ -1663,7 +1664,11 @@ export class ContainerRuntime
1663
1664
  snapshot,
1664
1665
  parentContext,
1665
1666
  this.mc.logger,
1666
- (props) => this.garbageCollector.nodeUpdated(props),
1667
+ (props) =>
1668
+ this.garbageCollector.nodeUpdated({
1669
+ ...props,
1670
+ timestampMs: props.timestampMs ?? this.getCurrentReferenceTimestampMs(),
1671
+ }),
1667
1672
  (path: string) => this.garbageCollector.isNodeDeleted(path),
1668
1673
  new Map<string, string>(dataStoreAliasMap),
1669
1674
  async (runtime: ChannelCollection) => provideEntryPoint,
@@ -1689,6 +1694,7 @@ export class ContainerRuntime
1689
1694
  this.garbageCollector.nodeUpdated({
1690
1695
  node: { type: "Blob", path: blobPath },
1691
1696
  reason: "Loaded",
1697
+ timestampMs: this.getCurrentReferenceTimestampMs(),
1692
1698
  }),
1693
1699
  isBlobDeleted: (blobPath: string) => this.garbageCollector.isNodeDeleted(blobPath),
1694
1700
  runtime: this,
@@ -2715,7 +2721,11 @@ export class ContainerRuntime
2715
2721
  }
2716
2722
  break;
2717
2723
  case ContainerMessageType.GC:
2718
- this.garbageCollector.processMessage(messageWithContext.message, local);
2724
+ this.garbageCollector.processMessage(
2725
+ messageWithContext.message,
2726
+ messageWithContext.message.timestamp,
2727
+ local,
2728
+ );
2719
2729
  break;
2720
2730
  case ContainerMessageType.ChunkedOp:
2721
2731
  // From observability POV, we should not exppse the rest of the system (including "op" events on object) to these messages.
@@ -2949,6 +2959,7 @@ export class ContainerRuntime
2949
2959
  node: { type: "DataStore", path: `/${internalId}` },
2950
2960
  reason: "Loaded",
2951
2961
  packagePath: context.packagePath,
2962
+ timestampMs: this.getCurrentReferenceTimestampMs(),
2952
2963
  });
2953
2964
  return channel.entryPoint;
2954
2965
  }
@@ -3402,9 +3413,25 @@ export class ContainerRuntime
3402
3413
  * all references added in the system.
3403
3414
  * @param fromPath - The absolute path of the node that added the reference.
3404
3415
  * @param toPath - The absolute path of the outbound node that is referenced.
3416
+ * @param messageTimestampMs - The timestamp of the message that added the reference.
3405
3417
  */
3406
- public addedGCOutboundRoute(fromPath: string, toPath: string) {
3407
- this.garbageCollector.addedOutboundReference(fromPath, toPath);
3418
+ public addedGCOutboundRoute(fromPath: string, toPath: string, messageTimestampMs?: number) {
3419
+ // This is always called when processing an op so messageTimestampMs should exist. Due to back-compat
3420
+ // across the data store runtime / container runtime boundary, this may be undefined and if so, get
3421
+ // the timestamp from the last processed message which should exist.
3422
+ // If a timestamp doesn't exist, log so we can learn about these cases and return.
3423
+ const timestampMs = messageTimestampMs ?? this.getCurrentReferenceTimestampMs();
3424
+ if (timestampMs === undefined) {
3425
+ this.mc.logger.sendTelemetryEvent({
3426
+ eventName: "NoTimestampInGCOutboundRoute",
3427
+ ...tagCodeArtifacts({
3428
+ id: toPath,
3429
+ fromId: fromPath,
3430
+ }),
3431
+ });
3432
+ return;
3433
+ }
3434
+ this.garbageCollector.addedOutboundReference(fromPath, toPath, timestampMs);
3408
3435
  }
3409
3436
 
3410
3437
  /**
@@ -719,9 +719,10 @@ export abstract class FluidDataStoreContext
719
719
  *
720
720
  * @param fromPath - The absolute path of the node that added the reference.
721
721
  * @param toPath - The absolute path of the outbound node that is referenced.
722
+ * @param messageTimestampMs - The timestamp of the message that added the reference.
722
723
  */
723
- public addedGCOutboundRoute(fromPath: string, toPath: string) {
724
- this.parentContext.addedGCOutboundRoute(fromPath, toPath);
724
+ public addedGCOutboundRoute(fromPath: string, toPath: string, messageTimestampMs?: number) {
725
+ this.parentContext.addedGCOutboundRoute(fromPath, toPath, messageTimestampMs);
725
726
  }
726
727
 
727
728
  /**
@@ -892,9 +892,14 @@ export class GarbageCollector implements IGarbageCollector {
892
892
  /**
893
893
  * Process a GC message.
894
894
  * @param message - The GC message from the container runtime.
895
+ * @param messageTimestampMs - The timestamp of the message.
895
896
  * @param local - Whether it was send by this client.
896
897
  */
897
- public processMessage(message: ContainerRuntimeGCMessage, local: boolean) {
898
+ public processMessage(
899
+ message: ContainerRuntimeGCMessage,
900
+ messageTimestampMs: number,
901
+ local: boolean,
902
+ ) {
898
903
  const gcMessageType = message.contents.type;
899
904
  switch (gcMessageType) {
900
905
  case GarbageCollectionMessageType.Sweep: {
@@ -909,7 +914,12 @@ export class GarbageCollector implements IGarbageCollector {
909
914
 
910
915
  // Mark the node as referenced to ensure it isn't Swept
911
916
  const tombstonedNodePath = message.contents.nodePath;
912
- this.addedOutboundReference("/", tombstonedNodePath, true /* autorecovery */);
917
+ this.addedOutboundReference(
918
+ "/",
919
+ tombstonedNodePath,
920
+ messageTimestampMs,
921
+ true /* autorecovery */,
922
+ );
913
923
 
914
924
  // In case the cause of the TombstoneLoaded event is incorrect GC Data (i.e. the object is actually reachable),
915
925
  // do fullGC on the next run to get a chance to repair (in the likely case the bug is not deterministic)
@@ -990,7 +1000,9 @@ export class GarbageCollector implements IGarbageCollector {
990
1000
  request,
991
1001
  headerData,
992
1002
  }: IGCNodeUpdatedProps) {
993
- if (!this.shouldRunGC) {
1003
+ // If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
1004
+ // logging as nothing interesting would have happened worth logging.
1005
+ if (!this.shouldRunGC || timestampMs === undefined) {
994
1006
  return;
995
1007
  }
996
1008
 
@@ -1005,8 +1017,7 @@ export class GarbageCollector implements IGarbageCollector {
1005
1017
  this.telemetryTracker.nodeUsed(trackedId, {
1006
1018
  id: fullPath,
1007
1019
  usageType: reason,
1008
- currentReferenceTimestampMs:
1009
- timestampMs ?? this.runtime.getCurrentReferenceTimestampMs(),
1020
+ currentReferenceTimestampMs: timestampMs,
1010
1021
  packagePath,
1011
1022
  completedGCRuns: this.completedRuns,
1012
1023
  isTombstoned,
@@ -1096,9 +1107,15 @@ export class GarbageCollector implements IGarbageCollector {
1096
1107
  *
1097
1108
  * @param fromNodePath - The node from which the reference is added.
1098
1109
  * @param toNodePath - The node to which the reference is added.
1110
+ * @param timestampMs - The timestamp of the message that added the reference.
1099
1111
  * @param autorecovery - This reference is added artificially, for autorecovery. Used for logging.
1100
1112
  */
1101
- public addedOutboundReference(fromNodePath: string, toNodePath: string, autorecovery?: true) {
1113
+ public addedOutboundReference(
1114
+ fromNodePath: string,
1115
+ toNodePath: string,
1116
+ timestampMs: number,
1117
+ autorecovery?: true,
1118
+ ) {
1102
1119
  if (!this.shouldRunGC) {
1103
1120
  return;
1104
1121
  }
@@ -1129,7 +1146,7 @@ export class GarbageCollector implements IGarbageCollector {
1129
1146
  id: toNodePath,
1130
1147
  fromId: fromNodePath,
1131
1148
  usageType: "Revived",
1132
- currentReferenceTimestampMs: this.runtime.getCurrentReferenceTimestampMs(),
1149
+ currentReferenceTimestampMs: timestampMs,
1133
1150
  packagePath: undefined,
1134
1151
  completedGCRuns: this.completedRuns,
1135
1152
  isTombstoned: this.tombstones.includes(trackedId),
@@ -365,9 +365,18 @@ export interface IGarbageCollector {
365
365
  */
366
366
  nodeUpdated(props: IGCNodeUpdatedProps): void;
367
367
  /** Called when a reference is added to a node. Used to identify nodes that were referenced between summaries. */
368
- addedOutboundReference(fromNodePath: string, toNodePath: string, autorecovery?: true): void;
368
+ addedOutboundReference(
369
+ fromNodePath: string,
370
+ toNodePath: string,
371
+ timestampMs: number,
372
+ autorecovery?: true,
373
+ ): void;
369
374
  /** Called to process a garbage collection message. */
370
- processMessage(message: ContainerRuntimeGCMessage, local: boolean): void;
375
+ processMessage(
376
+ message: ContainerRuntimeGCMessage,
377
+ messageTimestampMs: number,
378
+ local: boolean,
379
+ ): void;
371
380
  /** Returns true if this node has been deleted by GC during sweep phase. */
372
381
  isNodeDeleted(nodePath: string): boolean;
373
382
  setConnectionState(connected: boolean, clientId?: string): void;
@@ -383,8 +392,11 @@ export interface IGCNodeUpdatedProps {
383
392
  node: { type: (typeof GCNodeType)["DataStore" | "Blob"]; path: string };
384
393
  /** Whether the node (or a subpath) was loaded or changed. */
385
394
  reason: "Loaded" | "Changed";
386
- /** The op-based timestamp when the node changed, if applicable */
387
- timestampMs?: number;
395
+ /**
396
+ * The op-based timestamp when the node changed. If the update is from receiving an op, this should
397
+ * be the timestamp of the op. If not, this should be the timestamp of the last op processed.
398
+ */
399
+ timestampMs: number | undefined;
388
400
  /** The package path of the node. This may not be available if the node hasn't been loaded yet */
389
401
  packagePath?: readonly string[];
390
402
  /** The original request for loads to preserve it in telemetry */
@@ -63,7 +63,7 @@ interface INodeUsageProps extends ICommonProps {
63
63
  /** The full path (in GC Path format) to the node in question */
64
64
  id: string;
65
65
  /** Latest timestamp received from the server, as a baseline for computing GC state/age */
66
- currentReferenceTimestampMs: number | undefined;
66
+ currentReferenceTimestampMs: number;
67
67
  /** The package path of the node. This may not be available if the node hasn't been loaded yet */
68
68
  packagePath: readonly string[] | undefined;
69
69
  /** In case of Revived - what node added the reference? */
@@ -165,12 +165,6 @@ export class GCTelemetryTracker {
165
165
  ...otherNodeUsageProps
166
166
  }: INodeUsageProps,
167
167
  ) {
168
- // If there is no reference timestamp to work with, no ops have been processed after creation. If so, skip
169
- // logging as nothing interesting would have happened worth logging.
170
- if (currentReferenceTimestampMs === undefined) {
171
- return;
172
- }
173
-
174
168
  // Note: For SubDataStore Load usage, trackedId will be the DataStore's id, not the full path in question.
175
169
  // This is necessary because the SubDataStore path may be unrecognized by GC (if suited for a custom request handler)
176
170
  const nodeStateTracker = this.getNodeStateTracker(trackedId);
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.0.0-dev-rc.5.0.0.271262";
9
+ export const pkgVersion = "2.0.0-dev-rc.5.0.0.272251";
package/tsdoc.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
3
+ "extends": ["../../../common/build/build-common/tsdoc-base.json"]
4
+ }