@fluidframework/container-runtime 0.51.3 → 0.53.0-46105

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 (112) hide show
  1. package/dist/connectionTelemetry.d.ts +4 -0
  2. package/dist/connectionTelemetry.d.ts.map +1 -1
  3. package/dist/connectionTelemetry.js +6 -2
  4. package/dist/connectionTelemetry.js.map +1 -1
  5. package/dist/containerHandleContext.d.ts +0 -1
  6. package/dist/containerHandleContext.d.ts.map +1 -1
  7. package/dist/containerHandleContext.js +0 -1
  8. package/dist/containerHandleContext.js.map +1 -1
  9. package/dist/containerRuntime.d.ts +40 -13
  10. package/dist/containerRuntime.d.ts.map +1 -1
  11. package/dist/containerRuntime.js +255 -133
  12. package/dist/containerRuntime.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +10 -7
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +16 -13
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStores.d.ts +8 -8
  18. package/dist/dataStores.d.ts.map +1 -1
  19. package/dist/dataStores.js +20 -36
  20. package/dist/dataStores.js.map +1 -1
  21. package/dist/garbageCollection.d.ts +61 -14
  22. package/dist/garbageCollection.d.ts.map +1 -1
  23. package/dist/garbageCollection.js +275 -20
  24. package/dist/garbageCollection.js.map +1 -1
  25. package/dist/index.d.ts +2 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +11 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/packageVersion.d.ts +1 -1
  30. package/dist/packageVersion.d.ts.map +1 -1
  31. package/dist/packageVersion.js +1 -1
  32. package/dist/packageVersion.js.map +1 -1
  33. package/dist/summarizer.d.ts +1 -3
  34. package/dist/summarizer.d.ts.map +1 -1
  35. package/dist/summarizer.js +0 -12
  36. package/dist/summarizer.js.map +1 -1
  37. package/dist/summarizerTypes.d.ts +14 -3
  38. package/dist/summarizerTypes.d.ts.map +1 -1
  39. package/dist/summarizerTypes.js +3 -0
  40. package/dist/summarizerTypes.js.map +1 -1
  41. package/dist/summaryFormat.d.ts +9 -1
  42. package/dist/summaryFormat.d.ts.map +1 -1
  43. package/dist/summaryFormat.js +2 -1
  44. package/dist/summaryFormat.js.map +1 -1
  45. package/dist/summaryGenerator.d.ts.map +1 -1
  46. package/dist/summaryGenerator.js +1 -3
  47. package/dist/summaryGenerator.js.map +1 -1
  48. package/dist/summaryManager.d.ts.map +1 -1
  49. package/dist/summaryManager.js.map +1 -1
  50. package/lib/connectionTelemetry.d.ts +4 -0
  51. package/lib/connectionTelemetry.d.ts.map +1 -1
  52. package/lib/connectionTelemetry.js +5 -1
  53. package/lib/connectionTelemetry.js.map +1 -1
  54. package/lib/containerHandleContext.d.ts +0 -1
  55. package/lib/containerHandleContext.d.ts.map +1 -1
  56. package/lib/containerHandleContext.js +0 -1
  57. package/lib/containerHandleContext.js.map +1 -1
  58. package/lib/containerRuntime.d.ts +40 -13
  59. package/lib/containerRuntime.d.ts.map +1 -1
  60. package/lib/containerRuntime.js +259 -138
  61. package/lib/containerRuntime.js.map +1 -1
  62. package/lib/dataStoreContext.d.ts +10 -7
  63. package/lib/dataStoreContext.d.ts.map +1 -1
  64. package/lib/dataStoreContext.js +16 -13
  65. package/lib/dataStoreContext.js.map +1 -1
  66. package/lib/dataStores.d.ts +8 -8
  67. package/lib/dataStores.d.ts.map +1 -1
  68. package/lib/dataStores.js +23 -39
  69. package/lib/dataStores.js.map +1 -1
  70. package/lib/garbageCollection.d.ts +61 -14
  71. package/lib/garbageCollection.d.ts.map +1 -1
  72. package/lib/garbageCollection.js +276 -21
  73. package/lib/garbageCollection.js.map +1 -1
  74. package/lib/index.d.ts +2 -2
  75. package/lib/index.d.ts.map +1 -1
  76. package/lib/index.js +2 -1
  77. package/lib/index.js.map +1 -1
  78. package/lib/packageVersion.d.ts +1 -1
  79. package/lib/packageVersion.d.ts.map +1 -1
  80. package/lib/packageVersion.js +1 -1
  81. package/lib/packageVersion.js.map +1 -1
  82. package/lib/summarizer.d.ts +1 -3
  83. package/lib/summarizer.d.ts.map +1 -1
  84. package/lib/summarizer.js +0 -12
  85. package/lib/summarizer.js.map +1 -1
  86. package/lib/summarizerTypes.d.ts +14 -3
  87. package/lib/summarizerTypes.d.ts.map +1 -1
  88. package/lib/summarizerTypes.js +3 -0
  89. package/lib/summarizerTypes.js.map +1 -1
  90. package/lib/summaryFormat.d.ts +9 -1
  91. package/lib/summaryFormat.d.ts.map +1 -1
  92. package/lib/summaryFormat.js +2 -1
  93. package/lib/summaryFormat.js.map +1 -1
  94. package/lib/summaryGenerator.d.ts.map +1 -1
  95. package/lib/summaryGenerator.js +1 -3
  96. package/lib/summaryGenerator.js.map +1 -1
  97. package/lib/summaryManager.d.ts.map +1 -1
  98. package/lib/summaryManager.js.map +1 -1
  99. package/package.json +16 -16
  100. package/src/connectionTelemetry.ts +6 -1
  101. package/src/containerHandleContext.ts +0 -1
  102. package/src/containerRuntime.ts +327 -160
  103. package/src/dataStoreContext.ts +21 -20
  104. package/src/dataStores.ts +32 -50
  105. package/src/garbageCollection.ts +390 -18
  106. package/src/index.ts +20 -2
  107. package/src/packageVersion.ts +1 -1
  108. package/src/summarizer.ts +0 -15
  109. package/src/summarizerTypes.ts +16 -4
  110. package/src/summaryFormat.ts +10 -1
  111. package/src/summaryGenerator.ts +2 -3
  112. package/src/summaryManager.ts +8 -3
@@ -4,13 +4,12 @@
4
4
  * Licensed under the MIT License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.ContainerRuntime = exports.agentSchedulerId = exports.ScheduleManager = exports.unpackRuntimeMessage = exports.isRuntimeMessage = exports.ContainerMessageType = void 0;
7
+ exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.ScheduleManager = exports.unpackRuntimeMessage = exports.isRuntimeMessage = exports.ContainerMessageType = void 0;
8
8
  const container_definitions_1 = require("@fluidframework/container-definitions");
9
9
  const common_utils_1 = require("@fluidframework/common-utils");
10
10
  const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
11
11
  const driver_utils_1 = require("@fluidframework/driver-utils");
12
12
  const container_utils_1 = require("@fluidframework/container-utils");
13
- const protocol_base_1 = require("@fluidframework/protocol-base");
14
13
  const protocol_definitions_1 = require("@fluidframework/protocol-definitions");
15
14
  const runtime_definitions_1 = require("@fluidframework/runtime-definitions");
16
15
  const runtime_utils_1 = require("@fluidframework/runtime-utils");
@@ -97,13 +96,16 @@ function unpackRuntimeMessage(message) {
97
96
  return message;
98
97
  }
99
98
  exports.unpackRuntimeMessage = unpackRuntimeMessage;
100
- class ScheduleManager {
101
- constructor(deltaManager, emitter, logger) {
99
+ /**
100
+ * This class controls pausing and resuming of inbound queue to ensure that we never
101
+ * start processing ops in a batch IF we do not have all ops in the batch.
102
+ */
103
+ class ScheduleManagerCore {
104
+ constructor(deltaManager, logger) {
102
105
  this.deltaManager = deltaManager;
103
- this.emitter = emitter;
104
106
  this.logger = logger;
105
107
  this.localPaused = false;
106
- this.deltaScheduler = new deltaScheduler_1.DeltaScheduler(this.deltaManager, telemetry_utils_1.ChildLogger.create(this.logger, "DeltaScheduler"));
108
+ this.timePaused = 0;
107
109
  // Listen for delta manager sends and add batch metadata to messages
108
110
  this.deltaManager.on("prepareSend", (messages) => {
109
111
  if (messages.length === 0) {
@@ -111,7 +113,7 @@ class ScheduleManager {
111
113
  }
112
114
  // First message will have the batch flag set to true if doing a batched send
113
115
  const firstMessageMetadata = messages[0].metadata;
114
- if (!firstMessageMetadata || !firstMessageMetadata.batch) {
116
+ if (!(firstMessageMetadata === null || firstMessageMetadata === void 0 ? void 0 : firstMessageMetadata.batch)) {
115
117
  return;
116
118
  }
117
119
  // If the batch contains only a single op, clear the batch flag.
@@ -126,29 +128,143 @@ class ScheduleManager {
126
128
  // Listen for updates and peek at the inbound
127
129
  this.deltaManager.inbound.on("push", (message) => {
128
130
  this.trackPending(message);
129
- this.updatePauseState(message.sequenceNumber);
130
131
  });
132
+ // Start with baseline - empty inbound queue.
133
+ common_utils_1.assert(!this.localPaused, 0x293 /* "initial state" */);
131
134
  const allPending = this.deltaManager.inbound.toArray();
132
135
  for (const pending of allPending) {
133
136
  this.trackPending(pending);
134
137
  }
135
- // Based on track pending update the pause state
136
- this.updatePauseState(this.deltaManager.lastSequenceNumber);
137
138
  }
138
- beginOperation(message) {
139
+ /**
140
+ * The only public function in this class - called when we processed an op,
141
+ * to make decision if op processing should be paused or not afer that.
142
+ */
143
+ afterOpProcessing(sequenceNumber) {
144
+ common_utils_1.assert(!this.localPaused, 0x294 /* "can't have op processing paused if we are processing an op" */);
145
+ // If the inbound queue is ever empty, nothing to do!
146
+ if (this.deltaManager.inbound.length === 0) {
147
+ common_utils_1.assert(this.pauseSequenceNumber === undefined, 0x295 /* "there should be no pending batch if we have no ops" */);
148
+ return;
149
+ }
150
+ // The queue is
151
+ // 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
152
+ // - here (processing ops until reaching start of incomplete batch)
153
+ // - in trackPending(), when queue was empty and start of batch showed up.
154
+ // 2. resumed when batch end comes in (in trackPending())
155
+ // do we have incomplete batch to worry about?
156
+ if (this.pauseSequenceNumber !== undefined) {
157
+ common_utils_1.assert(sequenceNumber < this.pauseSequenceNumber, 0x296 /* "we should never start processing incomplete batch!" */);
158
+ // If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
159
+ if (sequenceNumber + 1 === this.pauseSequenceNumber) {
160
+ this.pauseQueue();
161
+ }
162
+ }
163
+ }
164
+ pauseQueue() {
165
+ common_utils_1.assert(!this.localPaused, 0x297 /* "always called from resumed state" */);
166
+ this.localPaused = true;
167
+ this.timePaused = common_utils_1.performance.now();
168
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
169
+ this.deltaManager.inbound.pause();
170
+ }
171
+ resumeQueue(startBatch, endBatch) {
172
+ // Return early if no change in value
173
+ if (!this.localPaused) {
174
+ return;
175
+ }
176
+ this.localPaused = false;
177
+ const duration = common_utils_1.performance.now() - this.timePaused;
178
+ // Random round number - we want to know when batch waiting paused op processing.
179
+ if (duration > connectionTelemetry_1.latencyThreshold) {
180
+ this.logger.sendErrorEvent({
181
+ eventName: "MaxBatchWaitTimeExceeded",
182
+ duration,
183
+ sequenceNumber: endBatch,
184
+ length: endBatch - startBatch,
185
+ });
186
+ }
187
+ this.deltaManager.inbound.resume();
188
+ }
189
+ /**
190
+ * Called for each incoming op (i.e. inbound "push" notification)
191
+ */
192
+ trackPending(message) {
193
+ common_utils_1.assert(this.deltaManager.inbound.length !== 0, 0x298 /* "we have something in the queue that generates this event" */);
194
+ common_utils_1.assert((this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined), 0x299 /* "non-synchronized state" */);
195
+ const metadata = message.metadata;
196
+ const batchMetadata = metadata === null || metadata === void 0 ? void 0 : metadata.batch;
197
+ // Protocol messages are never part of a runtime batch of messages
198
+ if (!isRuntimeMessage(message)) {
199
+ // Protocol messages should never show up in the middle of the batch!
200
+ common_utils_1.assert(this.currentBatchClientId === undefined, 0x29a /* "System message in the middle of batch!" */);
201
+ common_utils_1.assert(batchMetadata === undefined, 0x29b /* "system op in a batch?" */);
202
+ common_utils_1.assert(!this.localPaused, 0x29c /* "we should be processing ops when there is no active batch" */);
203
+ return;
204
+ }
205
+ if (this.currentBatchClientId === undefined && batchMetadata === undefined) {
206
+ common_utils_1.assert(!this.localPaused, 0x29d /* "we should be processing ops when there is no active batch" */);
207
+ return;
208
+ }
209
+ // If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
210
+ // If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
211
+ // the previous one
212
+ if (this.currentBatchClientId !== undefined || batchMetadata === false) {
213
+ if (this.currentBatchClientId !== message.clientId) {
214
+ // "Batch not closed, yet message from another client!"
215
+ throw new container_utils_1.DataCorruptionError("OpBatchIncomplete", Object.assign({ batchClientId: this.currentBatchClientId }, container_utils_1.extractSafePropertiesFromMessage(message)));
216
+ }
217
+ }
218
+ // The queue is
219
+ // 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
220
+ // - in afterOpProcessing() - processing ops until reaching start of incomplete batch
221
+ // - here (batchMetadata == false below), when queue was empty and start of batch showed up.
222
+ // 2. resumed when batch end comes in (batchMetadata === true case below)
223
+ if (batchMetadata) {
224
+ common_utils_1.assert(this.currentBatchClientId === undefined, 0x29e /* "there can't be active batch" */);
225
+ common_utils_1.assert(!this.localPaused, 0x29f /* "we should be processing ops when there is no active batch" */);
226
+ this.pauseSequenceNumber = message.sequenceNumber;
227
+ this.currentBatchClientId = message.clientId;
228
+ // Start of the batch
229
+ // Only pause processing if queue has no other ops!
230
+ // If there are any other ops in the queue, processing will be stopped when they are processed!
231
+ if (this.deltaManager.inbound.length === 1) {
232
+ this.pauseQueue();
233
+ }
234
+ }
235
+ else if (batchMetadata === false) {
236
+ common_utils_1.assert(this.pauseSequenceNumber !== undefined, 0x2a0 /* "batch presence was validated above" */);
237
+ // Batch is complete, we can process it!
238
+ this.resumeQueue(this.pauseSequenceNumber, message.sequenceNumber);
239
+ this.pauseSequenceNumber = undefined;
240
+ this.currentBatchClientId = undefined;
241
+ }
242
+ else {
243
+ // Continuation of current batch. Do nothing
244
+ common_utils_1.assert(this.currentBatchClientId !== undefined, 0x2a1 /* "logic error" */);
245
+ }
246
+ }
247
+ }
248
+ /**
249
+ * This class has the following responsibilities:
250
+ * 1. It tracks batches as we process ops and raises "batchBegin" and "batchEnd" events.
251
+ * As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)
252
+ * 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch
253
+ * unless all ops of the batch are in.
254
+ */
255
+ class ScheduleManager {
256
+ constructor(deltaManager, emitter, logger) {
257
+ this.deltaManager = deltaManager;
258
+ this.emitter = emitter;
259
+ this.logger = logger;
260
+ this.hitError = false;
261
+ this.deltaScheduler = new deltaScheduler_1.DeltaScheduler(this.deltaManager, telemetry_utils_1.ChildLogger.create(this.logger, "DeltaScheduler"));
262
+ this.scheduler = new ScheduleManagerCore(deltaManager, logger);
263
+ }
264
+ beforeOpProcessing(message) {
139
265
  var _a;
140
266
  if (this.batchClientId !== message.clientId) {
141
- // As a back stop for any bugs marking the end of a batch - if the client ID flipped, we
142
- // consider the previous batch over.
143
- if (this.batchClientId) {
144
- this.emitter.emit("batchEnd", "Did not receive real batchEnd message", undefined);
145
- this.deltaScheduler.batchEnd();
146
- this.logger.sendTelemetryEvent({
147
- eventName: "BatchEndNotReceived",
148
- clientId: this.batchClientId,
149
- sequenceNumber: message.sequenceNumber,
150
- });
151
- }
267
+ common_utils_1.assert(this.batchClientId === undefined, 0x2a2 /* "Batch is interrupted by other client op. Should be caught by trackPending()" */);
152
268
  // This could be the beginning of a new batch or an individual message.
153
269
  this.emitter.emit("batchBegin", message);
154
270
  this.deltaScheduler.batchBegin();
@@ -161,79 +277,32 @@ class ScheduleManager {
161
277
  }
162
278
  }
163
279
  }
164
- endOperation(error, message) {
280
+ afterOpProcessing(error, message) {
165
281
  var _a;
282
+ // If this is no longer true, we need to revisit what we do where we set this.hitError.
283
+ common_utils_1.assert(!this.hitError, 0x2a3 /* "container should be closed on any error" */);
284
+ // Let the scheduler know how far we progressed, to decide if op processing
285
+ // should be paused or not.
286
+ this.scheduler.afterOpProcessing(message.sequenceNumber);
166
287
  if (error) {
288
+ // We assume here that loader will close container and stop processing all future ops.
289
+ // This is implicit dependency. If this flow changes, this code might no longer be correct.
290
+ this.hitError = true;
167
291
  this.batchClientId = undefined;
168
292
  this.emitter.emit("batchEnd", error, message);
169
293
  this.deltaScheduler.batchEnd();
170
294
  return;
171
295
  }
172
- this.updatePauseState(message.sequenceNumber);
173
296
  const batch = (_a = message === null || message === void 0 ? void 0 : message.metadata) === null || _a === void 0 ? void 0 : _a.batch;
174
297
  // If no batchClientId has been set then we're in an individual batch. Else, if we get
175
298
  // batch end metadata, this is end of the current batch.
176
- if (!this.batchClientId || batch === false) {
299
+ if (this.batchClientId === undefined || batch === false) {
177
300
  this.batchClientId = undefined;
178
301
  this.emitter.emit("batchEnd", undefined, message);
179
302
  this.deltaScheduler.batchEnd();
180
303
  return;
181
304
  }
182
305
  }
183
- setPaused(localPaused) {
184
- // Return early if no change in value
185
- if (this.localPaused === localPaused) {
186
- return;
187
- }
188
- this.localPaused = localPaused;
189
- if (localPaused) {
190
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
191
- this.deltaManager.inbound.pause();
192
- }
193
- else {
194
- this.deltaManager.inbound.resume();
195
- }
196
- }
197
- updatePauseState(sequenceNumber) {
198
- // If the inbound queue is ever empty we pause it and wait for new events
199
- if (this.deltaManager.inbound.length === 0) {
200
- this.setPaused(true);
201
- return;
202
- }
203
- // If no message has caused the pause flag to be set, or the next message up is not the one we need to pause at
204
- // then we simply continue processing
205
- if (!this.pauseSequenceNumber || sequenceNumber + 1 < this.pauseSequenceNumber) {
206
- this.setPaused(false);
207
- }
208
- else {
209
- // Otherwise the next message requires us to pause
210
- this.setPaused(true);
211
- }
212
- }
213
- trackPending(message) {
214
- const metadata = message.metadata;
215
- // Protocol messages are never part of a runtime batch of messages
216
- if (!isRuntimeMessage(message)) {
217
- this.pauseSequenceNumber = undefined;
218
- this.pauseClientId = undefined;
219
- return;
220
- }
221
- const batchMetadata = metadata ? metadata.batch : undefined;
222
- // If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
223
- if (this.pauseClientId === message.clientId) {
224
- if (batchMetadata !== undefined) {
225
- // If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
226
- // the previous one
227
- this.pauseSequenceNumber = batchMetadata ? message.sequenceNumber : undefined;
228
- this.pauseClientId = batchMetadata ? this.pauseClientId : undefined;
229
- }
230
- }
231
- else {
232
- // We check the batch flag for the new clientID - if true we pause otherwise we reset the tracking data
233
- this.pauseSequenceNumber = batchMetadata ? message.sequenceNumber : undefined;
234
- this.pauseClientId = batchMetadata ? message.clientId : undefined;
235
- }
236
- }
237
306
  }
238
307
  exports.ScheduleManager = ScheduleManager;
239
308
  /**
@@ -242,6 +311,21 @@ exports.ScheduleManager = ScheduleManager;
242
311
  * ContainerRuntime's perspective.
243
312
  */
244
313
  exports.agentSchedulerId = "_scheduler";
314
+ // safely check navigator and get the hardware spec value
315
+ function getDeviceSpec() {
316
+ try {
317
+ if (typeof navigator === "object" && navigator !== null) {
318
+ return {
319
+ deviceMemory: navigator.deviceMemory,
320
+ hardwareConcurrency: navigator.hardwareConcurrency,
321
+ };
322
+ }
323
+ }
324
+ catch (_a) {
325
+ }
326
+ return {};
327
+ }
328
+ exports.getDeviceSpec = getDeviceSpec;
245
329
  /**
246
330
  * Represents the runtime of the container. Contains helper functions/state of the container.
247
331
  * It will define the store level mappings.
@@ -305,9 +389,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
305
389
  }
306
390
  else {
307
391
  // If we're not the summarizer, and we don't have a summaryManager, we expect that
308
- // generateSummaries is turned off. We are throwing instead of returning a failure here,
392
+ // disableSummaries is turned on. We are throwing instead of returning a failure here,
309
393
  // because it is a misuse of the API rather than an expected failure.
310
- throw new Error(`Can't summarize, generateSummaries: ${this.runtimeOptions.summaryOptions.generateSummaries}`);
394
+ throw new Error(`Can't summarize, disableSummaries: ${this.summariesDisabled()}`);
311
395
  }
312
396
  };
313
397
  this.enqueueSummarize = (...args) => {
@@ -321,18 +405,43 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
321
405
  // If we're not the summarizer, and we don't have a summaryManager, we expect that
322
406
  // generateSummaries is turned off. We are throwing instead of returning a failure here,
323
407
  // because it is a misuse of the API rather than an expected failure.
324
- throw new Error(`Can't summarize, generateSummaries: ${this.runtimeOptions.summaryOptions.generateSummaries}`);
408
+ throw new Error(`Can't summarize, disableSummaries: ${this.summariesDisabled()}`);
325
409
  }
326
410
  };
327
411
  this.baseSummaryMessage = metadata === null || metadata === void 0 ? void 0 : metadata.message;
412
+ // If this is an existing container, we get values from metadata.
413
+ // otherwise, we initialize them.
414
+ if (existing) {
415
+ this.createContainerMetadata = {
416
+ createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
417
+ createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
418
+ };
419
+ this.summaryCount = metadata === null || metadata === void 0 ? void 0 : metadata.summaryCount;
420
+ }
421
+ else {
422
+ this.createContainerMetadata = {
423
+ createContainerRuntimeVersion: packageVersion_1.pkgVersion,
424
+ createContainerTimestamp: Date.now(),
425
+ };
426
+ }
328
427
  // Default to false (enabled).
329
428
  this.disableIsolatedChannels = (_a = this.runtimeOptions.summaryOptions.disableIsolatedChannels) !== null && _a !== void 0 ? _a : false;
330
429
  this._connected = this.context.connected;
331
430
  this.chunkMap = new Map(chunks);
332
431
  this.IFluidHandleContext = new containerHandleContext_1.ContainerFluidHandleContext("", this);
333
- this.IFluidSerializer = new runtime_utils_1.FluidSerializer(this.IFluidHandleContext);
334
432
  this._logger = telemetry_utils_1.ChildLogger.create(this.logger, "ContainerRuntime");
335
- this.garbageCollector = garbageCollection_1.GarbageCollector.create(this, this.runtimeOptions.gcOptions, (unusedRoutes) => this.dataStores.deleteUnusedRoutes(unusedRoutes), this._logger, existing, metadata);
433
+ /**
434
+ * Function that return the current server timestamp. This is used by the garbage collector to set the
435
+ * time when a node becomes unreferenced.
436
+ * For now, we use the timestamp of the last op for gcTimestamp. However, there can be cases where
437
+ * we don't have an op (on demand summaries for instance). In those cases, we will use the timestamp
438
+ * of this client's connection - https://github.com/microsoft/FluidFramework/issues/8375.
439
+ */
440
+ const getCurrentTimestamp = () => {
441
+ var _a, _b;
442
+ return (_b = (_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.timestamp) !== null && _b !== void 0 ? _b : Date.now();
443
+ };
444
+ this.garbageCollector = garbageCollection_1.GarbageCollector.create(this, this.runtimeOptions.gcOptions, (unusedRoutes) => this.dataStores.deleteUnusedRoutes(unusedRoutes), getCurrentTimestamp, context.baseSnapshot, async (id) => driver_utils_1.readAndParse(this.storage, id), this._logger, existing, metadata);
336
445
  const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
337
446
  this.summarizerNode = runtime_utils_1.createRootSummarizerNodeWithGC(telemetry_utils_1.ChildLogger.create(this.logger, "SummarizerNode"),
338
447
  // Summarize function to call when summarize is called. Summarizer node always tracks summary state.
@@ -349,13 +458,11 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
349
458
  throwOnFailure: true,
350
459
  // If GC should not run, let the summarizer node know so that it does not track GC state.
351
460
  gcDisabled: !this.garbageCollector.shouldRunGC,
352
- // The max duration for which objects can be unreferenced before they are eligible for deletion.
353
- maxUnreferencedDurationMs: this.runtimeOptions.gcOptions.maxUnreferencedDurationMs,
354
461
  });
355
462
  if (this.context.baseSnapshot) {
356
463
  this.summarizerNode.loadBaseSummaryWithoutDifferential(this.context.baseSnapshot);
357
464
  }
358
- this.dataStores = new dataStores_1.DataStores(dataStores_1.getSummaryForDatastores(context.baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getInitialGCSummaryDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getInitialGCSummaryDetailsFn), (id) => this.summarizerNode.deleteChild(id), this._logger);
465
+ this.dataStores = new dataStores_1.DataStores(dataStores_1.getSummaryForDatastores(context.baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getInitialGCSummaryDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getInitialGCSummaryDetailsFn), (id) => this.summarizerNode.deleteChild(id), this._logger, async () => this.garbageCollector.getDataStoreBaseGCDetails(), (id) => this.garbageCollector.nodeChanged(id));
359
466
  this.blobManager = new blobManager_1.BlobManager(this.IFluidHandleContext, blobManagerSnapshot, () => this.storage, (blobId) => this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId }), this, this.logger);
360
467
  this.scheduleManager = new ScheduleManager(context.deltaManager, this, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
361
468
  this.deltaSender = this.deltaManager;
@@ -363,14 +470,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
363
470
  this.context.quorum.on("removeMember", (clientId) => {
364
471
  this.clearPartialChunks(clientId);
365
472
  });
366
- this.context.quorum.on("addProposal", (proposal) => {
367
- if (proposal.key === "code" || proposal.key === "code2") {
368
- this.emit("codeDetailsProposed", proposal.value, proposal);
369
- }
370
- });
371
473
  this.summaryCollection = new summaryCollection_1.SummaryCollection(this.deltaManager, this.logger);
372
474
  // Only create a SummaryManager if summaries are enabled and we are not the summarizer client
475
+ // Map the deprecated generateSummaries flag to disableSummaries.
373
476
  if (this.runtimeOptions.summaryOptions.generateSummaries === false) {
477
+ this.runtimeOptions.summaryOptions.disableSummaries = true;
478
+ }
479
+ if (this.summariesDisabled()) {
374
480
  this._logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
375
481
  }
376
482
  else {
@@ -420,7 +526,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
420
526
  this.deltaManager.on("readonly", (readonly) => {
421
527
  // we accumulate ops while being in read-only state.
422
528
  // once user gets write permissions and we have active connection, flush all pending ops.
423
- common_utils_1.assert(readonly === this.deltaManager.readonly, 0x124 /* "inconsistent readonly property/event state" */);
529
+ // eslint-disable-next-line max-len
530
+ common_utils_1.assert(readonly === this.deltaManager.readOnlyInfo.readonly, 0x124 /* "inconsistent readonly property/event state" */);
424
531
  // We need to be very careful with when we (re)send pending ops, to ensure that we only send ops
425
532
  // when we either never send an op, or attempted to send it but we know for sure it was not
426
533
  // sequenced by server and will never be sequenced (i.e. was lost)
@@ -439,6 +546,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
439
546
  if (context.pendingLocalState !== undefined) {
440
547
  this.deltaManager.on("op", this.onOp);
441
548
  }
549
+ // logging hardware telemetry
550
+ logger.sendTelemetryEvent(Object.assign({ eventName: "DeviceSpec" }, getDeviceSpec()));
551
+ // logging container load stats
552
+ this.logger.sendTelemetryEvent(Object.assign(Object.assign(Object.assign({ eventName: "ContainerLoadStats" }, this.createContainerMetadata), this.dataStores.containerLoadStats), { summaryCount: this.summaryCount, summaryFormatVersion: metadata === null || metadata === void 0 ? void 0 : metadata.summaryFormatVersion, disableIsolatedChannels: metadata === null || metadata === void 0 ? void 0 : metadata.disableIsolatedChannels, gcVersion: metadata === null || metadata === void 0 ? void 0 : metadata.gcFeature }));
442
553
  connectionTelemetry_1.ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
443
554
  }
444
555
  get IContainerRuntime() { return this; }
@@ -460,7 +571,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
460
571
  runtimeVersion: packageVersion_1.pkgVersion,
461
572
  },
462
573
  });
463
- const { summaryOptions = { generateSummaries: true }, gcOptions = {}, loadSequenceNumberVerification = "close", } = runtimeOptions;
574
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", } = runtimeOptions;
464
575
  // We pack at data store level only. If isolated channels are disabled,
465
576
  // then there are no .channel layers, we pack at level 1, otherwise we pack at level 2
466
577
  const packingLevel = summaryOptions.disableIsolatedChannels ? 1 : 2;
@@ -511,7 +622,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
511
622
  const protocolSequenceNumber = context.deltaManager.initialSequenceNumber;
512
623
  // Unless bypass is explicitly set, then take action when sequence numbers mismatch.
513
624
  if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
514
- const error = new container_utils_1.DataCorruptionError("Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber", { runtimeSequenceNumber, protocolSequenceNumber });
625
+ // "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
626
+ const error = new container_utils_1.DataCorruptionError("SummaryMetadataMismatch", { runtimeSequenceNumber, protocolSequenceNumber });
515
627
  if (loadSequenceNumberVerification === "log") {
516
628
  logger.sendErrorEvent({ eventName: "SequenceNumberMismatch" }, error);
517
629
  }
@@ -590,6 +702,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
590
702
  return Object.assign(Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_b = (_a = this.context) === null || _a === void 0 ? void 0 : _a.serviceConfiguration) === null || _b === void 0 ? void 0 : _b.summary), (_c = this.runtimeOptions.summaryOptions) === null || _c === void 0 ? void 0 : _c.summaryConfigOverrides);
591
703
  }
592
704
  get disposed() { return this._disposed; }
705
+ /**
706
+ * True, if GC data should be written at root of the summary tree.
707
+ * False, if data stores should write GC blobs in their summary tree.
708
+ */
709
+ get writeGCDataAtRoot() {
710
+ return this.garbageCollector.writeDataAtRoot;
711
+ }
593
712
  static get defaultFlushMode() {
594
713
  return localStorageFeatureGates_1.getLocalStorageFeatureGate(turnBasedFlushModeKey) ? runtime_definitions_1.FlushMode.TurnBased : runtime_definitions_1.FlushMode.Immediate;
595
714
  }
@@ -710,14 +829,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
710
829
  }
711
830
  formMetadata() {
712
831
  var _a;
713
- return {
714
- summaryFormatVersion: 1,
715
- disableIsolatedChannels: this.disableIsolatedChannels || undefined,
716
- gcFeature: this.garbageCollector.gcSummaryFeatureVersion,
832
+ return Object.assign(Object.assign({}, this.createContainerMetadata), { summaryCount: this.summaryCount, summaryFormatVersion: 1, disableIsolatedChannels: this.disableIsolatedChannels || undefined, gcFeature: this.garbageCollector.gcSummaryFeatureVersion,
717
833
  // The last message processed at the time of summary. If there are no messages, nothing has changed from
718
834
  // the base summary we loaded from. So, use the message from its metadata blob.
719
- message: (_a = summaryFormat_1.extractSummaryMetadataMessage(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.baseSummaryMessage,
720
- };
835
+ message: (_a = summaryFormat_1.extractSummaryMetadataMessage(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.baseSummaryMessage });
721
836
  }
722
837
  /**
723
838
  * Retrieves the runtime for a data store if it's referenced as per the initially summary that it is loaded with.
@@ -744,22 +859,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
744
859
  * @deprecated - Use summarize to get summary of the container runtime.
745
860
  */
746
861
  async snapshot() {
747
- if (this.garbageCollector.shouldRunGC) {
748
- await this.collectGarbage({ logger: this.logger, fullGC: true /* fullGC */ });
749
- }
750
- const root = { entries: [] };
751
- const entries = await this.dataStores.snapshot();
752
- if (this.disableIsolatedChannels) {
753
- root.entries = root.entries.concat(entries);
754
- }
755
- else {
756
- root.entries.push(new protocol_base_1.TreeTreeEntry(runtime_definitions_1.channelsTreeName, { entries }));
757
- }
758
- root.entries.push(new protocol_base_1.BlobTreeEntry(summaryFormat_1.metadataBlobName, JSON.stringify(this.formMetadata())));
759
- if (this.chunkMap.size > 0) {
760
- root.entries.push(new protocol_base_1.BlobTreeEntry(summaryFormat_1.chunksBlobName, JSON.stringify([...this.chunkMap])));
761
- }
762
- return root;
862
+ const summaryResult = await this.summarize({
863
+ summaryLogger: this.logger,
864
+ fullTree: true,
865
+ trackState: false,
866
+ runGC: this.garbageCollector.shouldRunGC,
867
+ fullGC: true,
868
+ });
869
+ return runtime_utils_1.convertSummaryTreeToITree(summaryResult.summary);
763
870
  }
764
871
  addContainerBlobsToSummary(summaryTree) {
765
872
  var _a;
@@ -779,6 +886,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
779
886
  const blobsTree = runtime_utils_1.convertToSummaryTree(snapshot, false);
780
887
  runtime_utils_1.addTreeToSummary(summaryTree, summaryFormat_1.blobsTreeName, blobsTree);
781
888
  }
889
+ if (this.writeGCDataAtRoot) {
890
+ const gcSummary = this.garbageCollector.summarize();
891
+ if (gcSummary !== undefined) {
892
+ runtime_utils_1.addTreeToSummary(summaryTree, garbageCollection_1.gcTreeKey, gcSummary);
893
+ }
894
+ }
782
895
  }
783
896
  replayPendingStates() {
784
897
  // We need to be able to send ops to replay states
@@ -848,11 +961,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
848
961
  // We do not need to make deep copy, as each layer will just replace message.content itself,
849
962
  // but would not modify contents details
850
963
  let message = Object.assign({}, messageArg);
851
- let error;
852
964
  // Surround the actual processing of the operation with messages to the schedule manager indicating
853
965
  // the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
854
966
  // messages once a batch has been fully processed.
855
- this.scheduleManager.beginOperation(message);
967
+ this.scheduleManager.beforeOpProcessing(message);
856
968
  try {
857
969
  message = unpackRuntimeMessage(message);
858
970
  // Chunk processing must come first given that we will transform the message to the unchunked version
@@ -880,14 +992,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
880
992
  default:
881
993
  }
882
994
  this.emit("op", message);
995
+ this.scheduleManager.afterOpProcessing(undefined, message);
883
996
  }
884
997
  catch (e) {
885
- error = e;
998
+ this.scheduleManager.afterOpProcessing(e, message);
886
999
  throw e;
887
1000
  }
888
- finally {
889
- this.scheduleManager.endOperation(error, message);
890
- }
891
1001
  }
892
1002
  processSignal(message, local) {
893
1003
  const envelope = message.content;
@@ -1099,7 +1209,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1099
1209
  await this.collectGarbage({ logger: summaryLogger, runSweep, fullGC });
1100
1210
  }
1101
1211
  const summarizeResult = await this.summarizerNode.summarize(fullTree, trackState);
1102
- common_utils_1.assert(summarizeResult.summary.type === 1 /* Tree */, 0x12f /* "Container Runtime's summarize should always return a tree" */);
1212
+ common_utils_1.assert(summarizeResult.summary.type === protocol_definitions_1.SummaryType.Tree, 0x12f /* "Container Runtime's summarize should always return a tree" */);
1103
1213
  return summarizeResult;
1104
1214
  }
1105
1215
  /**
@@ -1114,19 +1224,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1114
1224
  * Implementation of IGarbageCollectionRuntime::updateUsedRoutes.
1115
1225
  * After GC has run, called to notify this container's nodes of routes that are used in it.
1116
1226
  * @param usedRoutes - The routes that are used in all nodes in this Container.
1227
+ * @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
1228
+ * unreferenced as part of this GC run, this should be used to update the time when it happens.
1117
1229
  * @returns the statistics of the used state of the data stores.
1118
1230
  */
1119
- updateUsedRoutes(usedRoutes) {
1120
- var _a;
1231
+ updateUsedRoutes(usedRoutes, gcTimestamp) {
1121
1232
  // Update our summarizer node's used routes. Updating used routes in summarizer node before
1122
1233
  // summarizing is required and asserted by the the summarizer node. We are the root and are
1123
1234
  // always referenced, so the used routes is only self-route (empty string).
1124
1235
  this.summarizerNode.updateUsedRoutes([""]);
1125
- return this.dataStores.updateUsedRoutes(usedRoutes, (_a =
1126
- // For now, we use the timestamp of the last op for gcTimestamp. However, there can be cases where
1127
- // we don't have an op (on demand summaries for instance). In those cases, we will use the timestamp
1128
- // of this client's connection - https://github.com/microsoft/FluidFramework/issues/7152.
1129
- this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.timestamp);
1236
+ return this.dataStores.updateUsedRoutes(usedRoutes, gcTimestamp);
1130
1237
  }
1131
1238
  /**
1132
1239
  * Runs garbage collection and udpates the reference / used state of the nodes in the container.
@@ -1194,6 +1301,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1194
1301
  if (!continueResult.continue) {
1195
1302
  return { stage: "base", referenceSequenceNumber: summaryRefSeqNum, error: continueResult.error };
1196
1303
  }
1304
+ // increment summary count
1305
+ if (this.summaryCount !== undefined) {
1306
+ this.summaryCount++;
1307
+ }
1308
+ else {
1309
+ this.summaryCount = 1;
1310
+ }
1197
1311
  const trace = common_utils_1.Trace.start();
1198
1312
  let summarizeResult;
1199
1313
  try {
@@ -1215,8 +1329,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1215
1329
  // Because handles are unchanged dataStores in the current logic,
1216
1330
  // summarized dataStore count is total dataStore count minus handle count
1217
1331
  const dataStoreTree = this.disableIsolatedChannels ? summaryTree : summaryTree.tree[runtime_definitions_1.channelsTreeName];
1218
- common_utils_1.assert(dataStoreTree.type === 1 /* Tree */, 0x1fc /* "summary is not a tree" */);
1219
- const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === 3 /* Handle */).length;
1332
+ common_utils_1.assert(dataStoreTree.type === protocol_definitions_1.SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
1333
+ const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === protocol_definitions_1.SummaryType.Handle).length;
1220
1334
  const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount }, partialStats);
1221
1335
  const generateSummaryData = {
1222
1336
  referenceSequenceNumber: summaryRefSeqNum,
@@ -1493,6 +1607,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1493
1607
  getPendingLocalState() {
1494
1608
  return this.pendingStateManager.getLocalState();
1495
1609
  }
1610
+ /**
1611
+ * @returns true if summaries are explicitly disabled for this ContainerRuntime, false otherwise
1612
+ */
1613
+ summariesDisabled() {
1614
+ var _a;
1615
+ return this.runtimeOptions.summaryOptions.disableSummaries === true ||
1616
+ ((_a = this.runtimeOptions.summaryOptions.summaryConfigOverrides) === null || _a === void 0 ? void 0 : _a.disableSummaries) === true;
1617
+ }
1496
1618
  }
1497
1619
  exports.ContainerRuntime = ContainerRuntime;
1498
1620
  /**