@fluidframework/container-runtime 0.59.4001 → 1.1.0-75972

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 (157) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/blobManager.d.ts +2 -2
  3. package/dist/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager.js +12 -11
  5. package/dist/blobManager.js.map +1 -1
  6. package/dist/connectionTelemetry.d.ts +19 -0
  7. package/dist/connectionTelemetry.d.ts.map +1 -1
  8. package/dist/connectionTelemetry.js +23 -23
  9. package/dist/connectionTelemetry.js.map +1 -1
  10. package/dist/containerRuntime.d.ts +137 -29
  11. package/dist/containerRuntime.d.ts.map +1 -1
  12. package/dist/containerRuntime.js +338 -118
  13. package/dist/containerRuntime.js.map +1 -1
  14. package/dist/dataStore.d.ts.map +1 -1
  15. package/dist/dataStore.js +14 -3
  16. package/dist/dataStore.js.map +1 -1
  17. package/dist/dataStoreContext.d.ts +4 -2
  18. package/dist/dataStoreContext.d.ts.map +1 -1
  19. package/dist/dataStoreContext.js +16 -5
  20. package/dist/dataStoreContext.js.map +1 -1
  21. package/dist/dataStoreRegistry.d.ts +0 -4
  22. package/dist/dataStoreRegistry.d.ts.map +1 -1
  23. package/dist/dataStoreRegistry.js +12 -1
  24. package/dist/dataStoreRegistry.js.map +1 -1
  25. package/dist/dataStores.d.ts +4 -3
  26. package/dist/dataStores.d.ts.map +1 -1
  27. package/dist/dataStores.js +13 -7
  28. package/dist/dataStores.js.map +1 -1
  29. package/dist/garbageCollection.d.ts +23 -27
  30. package/dist/garbageCollection.d.ts.map +1 -1
  31. package/dist/garbageCollection.js +44 -119
  32. package/dist/garbageCollection.js.map +1 -1
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +2 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/orderedClientElection.js +0 -4
  38. package/dist/orderedClientElection.js.map +1 -1
  39. package/dist/packageVersion.d.ts +1 -1
  40. package/dist/packageVersion.d.ts.map +1 -1
  41. package/dist/packageVersion.js +1 -1
  42. package/dist/packageVersion.js.map +1 -1
  43. package/dist/pendingStateManager.d.ts +30 -29
  44. package/dist/pendingStateManager.d.ts.map +1 -1
  45. package/dist/pendingStateManager.js +72 -109
  46. package/dist/pendingStateManager.js.map +1 -1
  47. package/dist/runningSummarizer.d.ts +4 -3
  48. package/dist/runningSummarizer.d.ts.map +1 -1
  49. package/dist/runningSummarizer.js +11 -6
  50. package/dist/runningSummarizer.js.map +1 -1
  51. package/dist/serializedSnapshotStorage.d.ts +58 -0
  52. package/dist/serializedSnapshotStorage.d.ts.map +1 -0
  53. package/dist/serializedSnapshotStorage.js +108 -0
  54. package/dist/serializedSnapshotStorage.js.map +1 -0
  55. package/dist/summarizer.d.ts +11 -4
  56. package/dist/summarizer.d.ts.map +1 -1
  57. package/dist/summarizer.js +18 -9
  58. package/dist/summarizer.js.map +1 -1
  59. package/dist/summarizerHeuristics.d.ts +5 -3
  60. package/dist/summarizerHeuristics.d.ts.map +1 -1
  61. package/dist/summarizerHeuristics.js +10 -3
  62. package/dist/summarizerHeuristics.js.map +1 -1
  63. package/dist/summarizerTypes.d.ts +4 -2
  64. package/dist/summarizerTypes.d.ts.map +1 -1
  65. package/dist/summarizerTypes.js.map +1 -1
  66. package/dist/summaryManager.d.ts +3 -3
  67. package/dist/summaryManager.d.ts.map +1 -1
  68. package/dist/summaryManager.js +7 -7
  69. package/dist/summaryManager.js.map +1 -1
  70. package/garbageCollection.md +9 -1
  71. package/lib/blobManager.d.ts +2 -2
  72. package/lib/blobManager.d.ts.map +1 -1
  73. package/lib/blobManager.js +12 -11
  74. package/lib/blobManager.js.map +1 -1
  75. package/lib/connectionTelemetry.d.ts +19 -0
  76. package/lib/connectionTelemetry.d.ts.map +1 -1
  77. package/lib/connectionTelemetry.js +23 -23
  78. package/lib/connectionTelemetry.js.map +1 -1
  79. package/lib/containerRuntime.d.ts +137 -29
  80. package/lib/containerRuntime.d.ts.map +1 -1
  81. package/lib/containerRuntime.js +341 -121
  82. package/lib/containerRuntime.js.map +1 -1
  83. package/lib/dataStore.d.ts.map +1 -1
  84. package/lib/dataStore.js +15 -4
  85. package/lib/dataStore.js.map +1 -1
  86. package/lib/dataStoreContext.d.ts +4 -2
  87. package/lib/dataStoreContext.d.ts.map +1 -1
  88. package/lib/dataStoreContext.js +16 -5
  89. package/lib/dataStoreContext.js.map +1 -1
  90. package/lib/dataStoreRegistry.d.ts +0 -4
  91. package/lib/dataStoreRegistry.d.ts.map +1 -1
  92. package/lib/dataStoreRegistry.js +12 -1
  93. package/lib/dataStoreRegistry.js.map +1 -1
  94. package/lib/dataStores.d.ts +4 -3
  95. package/lib/dataStores.d.ts.map +1 -1
  96. package/lib/dataStores.js +14 -8
  97. package/lib/dataStores.js.map +1 -1
  98. package/lib/garbageCollection.d.ts +23 -27
  99. package/lib/garbageCollection.d.ts.map +1 -1
  100. package/lib/garbageCollection.js +43 -117
  101. package/lib/garbageCollection.js.map +1 -1
  102. package/lib/index.d.ts +2 -2
  103. package/lib/index.d.ts.map +1 -1
  104. package/lib/index.js +1 -1
  105. package/lib/index.js.map +1 -1
  106. package/lib/orderedClientElection.js +0 -4
  107. package/lib/orderedClientElection.js.map +1 -1
  108. package/lib/packageVersion.d.ts +1 -1
  109. package/lib/packageVersion.d.ts.map +1 -1
  110. package/lib/packageVersion.js +1 -1
  111. package/lib/packageVersion.js.map +1 -1
  112. package/lib/pendingStateManager.d.ts +30 -29
  113. package/lib/pendingStateManager.d.ts.map +1 -1
  114. package/lib/pendingStateManager.js +72 -109
  115. package/lib/pendingStateManager.js.map +1 -1
  116. package/lib/runningSummarizer.d.ts +4 -3
  117. package/lib/runningSummarizer.d.ts.map +1 -1
  118. package/lib/runningSummarizer.js +11 -6
  119. package/lib/runningSummarizer.js.map +1 -1
  120. package/lib/serializedSnapshotStorage.d.ts +58 -0
  121. package/lib/serializedSnapshotStorage.d.ts.map +1 -0
  122. package/lib/serializedSnapshotStorage.js +104 -0
  123. package/lib/serializedSnapshotStorage.js.map +1 -0
  124. package/lib/summarizer.d.ts +11 -4
  125. package/lib/summarizer.d.ts.map +1 -1
  126. package/lib/summarizer.js +18 -9
  127. package/lib/summarizer.js.map +1 -1
  128. package/lib/summarizerHeuristics.d.ts +5 -3
  129. package/lib/summarizerHeuristics.d.ts.map +1 -1
  130. package/lib/summarizerHeuristics.js +10 -3
  131. package/lib/summarizerHeuristics.js.map +1 -1
  132. package/lib/summarizerTypes.d.ts +4 -2
  133. package/lib/summarizerTypes.d.ts.map +1 -1
  134. package/lib/summarizerTypes.js.map +1 -1
  135. package/lib/summaryManager.d.ts +3 -3
  136. package/lib/summaryManager.d.ts.map +1 -1
  137. package/lib/summaryManager.js +7 -7
  138. package/lib/summaryManager.js.map +1 -1
  139. package/package.json +19 -32
  140. package/src/blobManager.ts +29 -15
  141. package/src/connectionTelemetry.ts +60 -39
  142. package/src/containerRuntime.ts +502 -156
  143. package/src/dataStore.ts +21 -4
  144. package/src/dataStoreContext.ts +27 -5
  145. package/src/dataStoreRegistry.ts +8 -1
  146. package/src/dataStores.ts +21 -8
  147. package/src/garbageCollection.ts +81 -166
  148. package/src/index.ts +7 -1
  149. package/src/orderedClientElection.ts +1 -1
  150. package/src/packageVersion.ts +1 -1
  151. package/src/pendingStateManager.ts +104 -123
  152. package/src/runningSummarizer.ts +20 -10
  153. package/src/serializedSnapshotStorage.ts +146 -0
  154. package/src/summarizer.ts +20 -16
  155. package/src/summarizerHeuristics.ts +21 -5
  156. package/src/summarizerTypes.ts +4 -2
  157. package/src/summaryManager.ts +5 -6
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.ScheduleManager = exports.unpackRuntimeMessage = exports.isRuntimeMessage = exports.RuntimeMessage = exports.RuntimeHeaders = exports.ContainerMessageType = void 0;
3
+ exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.ScheduleManager = exports.unpackRuntimeMessage = exports.isRuntimeMessage = exports.RuntimeMessage = exports.RuntimeHeaders = exports.DefaultSummaryConfiguration = exports.ContainerMessageType = void 0;
4
4
  const container_definitions_1 = require("@fluidframework/container-definitions");
5
5
  const common_utils_1 = require("@fluidframework/common-utils");
6
6
  const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
@@ -31,6 +31,7 @@ const runWhileConnectedCoordinator_1 = require("./runWhileConnectedCoordinator")
31
31
  const garbageCollection_1 = require("./garbageCollection");
32
32
  const dataStore_1 = require("./dataStore");
33
33
  const batchTracker_1 = require("./batchTracker");
34
+ const serializedSnapshotStorage_1 = require("./serializedSnapshotStorage");
34
35
  const opTelemetry_1 = require("./opTelemetry");
35
36
  var ContainerMessageType;
36
37
  (function (ContainerMessageType) {
@@ -47,17 +48,16 @@ var ContainerMessageType;
47
48
  // Sets the alias of a root data store
48
49
  ContainerMessageType["Alias"] = "alias";
49
50
  })(ContainerMessageType = exports.ContainerMessageType || (exports.ContainerMessageType = {}));
50
- // Consider idle 5s of no activity. And snapshot if a minute has gone by with no snapshot.
51
- const IdleDetectionTime = 5000;
52
- const DefaultSummaryConfiguration = {
53
- idleTime: IdleDetectionTime * 3,
54
- maxTime: IdleDetectionTime * 12,
55
- // Summarize if 1000 ops received since last snapshot.
56
- maxOps: 1000,
57
- // Wait 10 minutes for summary ack
58
- // this is less than maxSummarizeAckWaitTime
59
- // the min of the two will be chosen
60
- maxAckWaitTime: 600000,
51
+ exports.DefaultSummaryConfiguration = {
52
+ state: "enabled",
53
+ idleTime: 5000 * 3,
54
+ maxTime: 5000 * 12,
55
+ maxOps: 100,
56
+ minOpsForLastSummaryAttempt: 10,
57
+ maxAckWaitTime: 6 * 10 * 1000,
58
+ maxOpsSinceLastSummary: 7000,
59
+ initialSummarizerDelayMs: 5000,
60
+ summarizerClientElection: false,
61
61
  };
62
62
  /**
63
63
  * Accepted header keys for requests coming to the runtime.
@@ -208,7 +208,7 @@ class ScheduleManagerCore {
208
208
  }
209
209
  resumeQueue(startBatch, messageEndBatch) {
210
210
  const endBatch = messageEndBatch.sequenceNumber;
211
- const duration = common_utils_1.performance.now() - this.timePaused;
211
+ const duration = this.localPaused ? (common_utils_1.performance.now() - this.timePaused) : undefined;
212
212
  this.batchCount++;
213
213
  if (this.batchCount % 1000 === 1) {
214
214
  this.logger.sendTelemetryEvent({
@@ -227,7 +227,7 @@ class ScheduleManagerCore {
227
227
  }
228
228
  this.localPaused = false;
229
229
  // Random round number - we want to know when batch waiting paused op processing.
230
- if (duration > connectionTelemetry_1.latencyThreshold) {
230
+ if (duration !== undefined && duration > connectionTelemetry_1.latencyThreshold) {
231
231
  this.logger.sendErrorEvent({
232
232
  eventName: "MaxBatchWaitTimeExceeded",
233
233
  duration,
@@ -379,43 +379,32 @@ exports.getDeviceSpec = getDeviceSpec;
379
379
  * It will define the store level mappings.
380
380
  */
381
381
  class ContainerRuntime extends common_utils_1.TypedEventEmitter {
382
- constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, requestHandler) {
383
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
382
+ constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
383
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
384
+ if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, exports.DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
384
385
  super();
385
386
  this.context = context;
386
387
  this.registry = registry;
387
388
  this.runtimeOptions = runtimeOptions;
388
389
  this.containerScope = containerScope;
389
390
  this.logger = logger;
391
+ this._storage = _storage;
390
392
  this.requestHandler = requestHandler;
393
+ this.summaryConfiguration = summaryConfiguration;
391
394
  this.defaultMaxConsecutiveReconnects = 15;
392
395
  this._orderSequentiallyCalls = 0;
393
396
  this.needsFlush = false;
394
397
  this.flushTrigger = false;
395
- this.paused = false;
398
+ this.savedOps = [];
396
399
  this.consecutiveReconnects = 0;
397
400
  this._disposed = false;
398
401
  this.emitDirtyDocumentEvent = true;
399
- /**
400
- * Used to apply stashed ops at their reference sequence number.
401
- * Normal op processing is synchronous, but applying stashed ops is async since the
402
- * data store may not be loaded yet, so we pause DeltaManager between ops.
403
- * It's also important that we see each op so we know all stashed ops have
404
- * been applied by "connected" event, but process() doesn't see system ops,
405
- * so we listen directly from DeltaManager instead.
406
- */
407
- this.onOp = (op) => {
408
- (0, common_utils_1.assert)(!this.paused, 0x128 /* "Container should not already be paused before applying stashed ops" */);
409
- this.paused = true;
410
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
411
- this.context.deltaManager.inbound.pause();
412
- const stashP = this.pendingStateManager.applyStashedOpsAt(op.sequenceNumber);
413
- stashP.then(() => {
414
- this.paused = false;
415
- this.context.deltaManager.inbound.resume();
416
- }, (error) => {
417
- this.closeFn((0, telemetry_utils_1.normalizeError)(error));
418
- });
402
+ this.defaultTelemetrySignalSampleCount = 100;
403
+ this._perfSignalData = {
404
+ signalsLost: 0,
405
+ signalSequenceNumber: 0,
406
+ signalTimestamp: 0,
407
+ trackingSignalSequenceNumber: undefined,
419
408
  };
420
409
  this.summarizeOnDemand = (...args) => {
421
410
  if (this.clientDetails.type === summarizerClientElection_1.summarizerClientType) {
@@ -447,27 +436,45 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
447
436
  };
448
437
  this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
449
438
  // Default to false (enabled).
450
- this.disableIsolatedChannels = (_a = this.runtimeOptions.summaryOptions.disableIsolatedChannels) !== null && _a !== void 0 ? _a : false;
439
+ this.disableIsolatedChannels = (_b = this.runtimeOptions.summaryOptions.disableIsolatedChannels) !== null && _b !== void 0 ? _b : false;
451
440
  this._connected = this.context.connected;
452
441
  this.chunkMap = new Map(chunks);
453
442
  this.handleContext = new containerHandleContext_1.ContainerFluidHandleContext("", this);
454
443
  this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(this.logger, "ContainerRuntime"));
444
+ this.summariesDisabled = this.isSummariesDisabled();
445
+ this.heuristicsDisabled = this.isHeuristicsDisabled();
446
+ this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
447
+ this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
448
+ this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
455
449
  this._aliasingEnabled =
456
- ((_b = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _b !== void 0 ? _b : false) ||
457
- ((_c = runtimeOptions.useDataStoreAliasing) !== null && _c !== void 0 ? _c : false);
458
- this._maxOpSizeInBytes = ((_d = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _d !== void 0 ? _d : defaultMaxOpSizeInBytes);
450
+ ((_c = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _c !== void 0 ? _c : false) ||
451
+ ((_d = runtimeOptions.useDataStoreAliasing) !== null && _d !== void 0 ? _d : false);
452
+ this._maxOpSizeInBytes = ((_e = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _e !== void 0 ? _e : defaultMaxOpSizeInBytes);
459
453
  this.maxConsecutiveReconnects =
460
- (_e = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _e !== void 0 ? _e : this.defaultMaxConsecutiveReconnects;
454
+ (_f = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _f !== void 0 ? _f : this.defaultMaxConsecutiveReconnects;
461
455
  this._flushMode = runtimeOptions.flushMode;
462
- this.garbageCollector = garbageCollection_1.GarbageCollector.create(this, this.runtimeOptions.gcOptions, (nodePath) => this.getGCNodePackagePath(nodePath), () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; }, context.baseSnapshot, async (id) => (0, driver_utils_1.readAndParse)(this.storage, id), this.mc.logger, existing, metadata, this.context.clientDetails.type === summarizerClientElection_1.summarizerClientType);
456
+ const pendingRuntimeState = context.pendingLocalState;
457
+ const baseSnapshot = (_g = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _g !== void 0 ? _g : context.baseSnapshot;
458
+ this.garbageCollector = garbageCollection_1.GarbageCollector.create({
459
+ runtime: this,
460
+ gcOptions: this.runtimeOptions.gcOptions,
461
+ baseSnapshot,
462
+ baseLogger: this.mc.logger,
463
+ existing,
464
+ metadata,
465
+ isSummarizerClient: this.context.clientDetails.type === summarizerClientElection_1.summarizerClientType,
466
+ getNodePackagePath: (nodePath) => this.getGCNodePackagePath(nodePath),
467
+ getLastSummaryTimestampMs: () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; },
468
+ readAndParseBlob: async (id) => (0, driver_utils_1.readAndParse)(this.storage, id),
469
+ });
463
470
  const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
464
471
  this.summarizerNode = (0, runtime_utils_1.createRootSummarizerNodeWithGC)(telemetry_utils_1.ChildLogger.create(this.logger, "SummarizerNode"),
465
472
  // Summarize function to call when summarize is called. Summarizer node always tracks summary state.
466
- async (fullTree, trackState) => this.summarizeInternal(fullTree, trackState),
473
+ async (fullTree, trackState, telemetryContext) => this.summarizeInternal(fullTree, trackState, telemetryContext),
467
474
  // Latest change sequence number, no changes since summary applied yet
468
475
  loadedFromSequenceNumber,
469
476
  // Summary reference sequence number, undefined if no summary yet
470
- context.baseSnapshot ? loadedFromSequenceNumber : undefined, {
477
+ baseSnapshot ? loadedFromSequenceNumber : undefined, {
471
478
  // Must set to false to prevent sending summary handle which would be pointing to
472
479
  // a summary with an older protocol state.
473
480
  canReuseHandle: false,
@@ -477,26 +484,31 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
477
484
  // If GC should not run, let the summarizer node know so that it does not track GC state.
478
485
  gcDisabled: !this.garbageCollector.shouldRunGC,
479
486
  });
480
- if (this.context.baseSnapshot) {
481
- this.summarizerNode.loadBaseSummaryWithoutDifferential(this.context.baseSnapshot);
487
+ if (baseSnapshot) {
488
+ this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
482
489
  }
483
- this.dataStores = new dataStores_1.DataStores((0, dataStores_1.getSummaryForDatastores)(context.baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getBaseGCDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getBaseGCDetailsFn), (id) => this.summarizerNode.deleteChild(id), this.mc.logger, async () => this.garbageCollector.getBaseGCDetails(), (path, timestampMs, packagePath) => this.garbageCollector.nodeUpdated(path, "Changed", timestampMs, packagePath), new Map(dataStoreAliasMap), this.garbageCollector.writeDataAtRoot);
490
+ this.dataStores = new dataStores_1.DataStores((0, dataStores_1.getSummaryForDatastores)(baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getBaseGCDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getBaseGCDetailsFn), (id) => this.summarizerNode.deleteChild(id), this.mc.logger, async () => this.garbageCollector.getBaseGCDetails(), (path, timestampMs, packagePath) => this.garbageCollector.nodeUpdated(path, "Changed", timestampMs, packagePath), new Map(dataStoreAliasMap), this.garbageCollector.writeDataAtRoot);
484
491
  this.blobManager = new blobManager_1.BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId) => this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId }), (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, this.logger);
485
492
  this.scheduleManager = new ScheduleManager(context.deltaManager, this, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
486
493
  this.deltaSender = this.deltaManager;
487
- this.pendingStateManager = new pendingStateManager_1.PendingStateManager(this, async (type, content) => this.applyStashedOp(type, content), this._flushMode, context.pendingLocalState);
494
+ this.pendingStateManager = new pendingStateManager_1.PendingStateManager({
495
+ applyStashedOp: this.applyStashedOp.bind(this),
496
+ clientId: () => this.clientId,
497
+ close: this.closeFn,
498
+ connected: () => this.connected,
499
+ flush: this.flush.bind(this),
500
+ flushMode: () => this.flushMode,
501
+ reSubmit: this.reSubmit.bind(this),
502
+ rollback: this.rollback.bind(this),
503
+ setFlushMode: (mode) => this.setFlushMode(mode),
504
+ }, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
488
505
  this.context.quorum.on("removeMember", (clientId) => {
489
506
  this.clearPartialChunks(clientId);
490
507
  });
491
508
  this.summaryCollection = new summaryCollection_1.SummaryCollection(this.deltaManager, this.logger);
492
- const { attachState, pendingLocalState } = this.context;
493
- this.dirtyContainer = attachState !== container_definitions_1.AttachState.Attached
494
- || ((_f = pendingLocalState) === null || _f === void 0 ? void 0 : _f.pendingStates.length) > 0;
509
+ this.dirtyContainer = this.context.attachState !== container_definitions_1.AttachState.Attached
510
+ || this.pendingStateManager.hasPendingMessages();
495
511
  this.context.updateDirtyContainerState(this.dirtyContainer);
496
- // Map the deprecated generateSummaries flag to disableSummaries.
497
- if (this.runtimeOptions.summaryOptions.generateSummaries === false) {
498
- this.runtimeOptions.summaryOptions.disableSummaries = true;
499
- }
500
512
  if (this.summariesDisabled) {
501
513
  this.mc.logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
502
514
  }
@@ -504,9 +516,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
504
516
  const orderedClientLogger = telemetry_utils_1.ChildLogger.create(this.logger, "OrderedClientElection");
505
517
  const orderedClientCollection = new orderedClientElection_1.OrderedClientCollection(orderedClientLogger, this.context.deltaManager, this.context.quorum);
506
518
  const orderedClientElectionForSummarizer = new orderedClientElection_1.OrderedClientElection(orderedClientLogger, orderedClientCollection, electedSummarizerData !== null && electedSummarizerData !== void 0 ? electedSummarizerData : this.context.deltaManager.lastSequenceNumber, summarizerClientElection_1.SummarizerClientElection.isClientEligible);
507
- const summarizerClientElectionEnabled = (_g = this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) !== null && _g !== void 0 ? _g : ((_h = this.runtimeOptions.summaryOptions) === null || _h === void 0 ? void 0 : _h.summarizerClientElection) === true;
508
- const maxOpsSinceLastSummary = (_j = this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary) !== null && _j !== void 0 ? _j : 7000;
509
- this.summarizerClientElection = new summarizerClientElection_1.SummarizerClientElection(orderedClientLogger, this.summaryCollection, orderedClientElectionForSummarizer, maxOpsSinceLastSummary, summarizerClientElectionEnabled);
519
+ this.summarizerClientElection = new summarizerClientElection_1.SummarizerClientElection(orderedClientLogger, this.summaryCollection, orderedClientElectionForSummarizer, this.maxOpsSinceLastSummary, this.summarizerClientElectionEnabled);
510
520
  if (this.context.clientDetails.type === summarizerClientElection_1.summarizerClientType) {
511
521
  this._summarizer = new summarizer_1.Summarizer("/_summarizer", this /* ISummarizerRuntime */, () => this.summaryConfiguration, this /* ISummarizerInternalsProvider */, this.handleContext, this.summaryCollection, async (runtime) => runWhileConnectedCoordinator_1.RunWhileConnectedCoordinator.create(runtime));
512
522
  }
@@ -514,7 +524,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
514
524
  // Only create a SummaryManager and SummarizerClientElection
515
525
  // if summaries are enabled and we are not the summarizer client.
516
526
  const defaultAction = () => {
517
- if (this.summaryCollection.opsSinceLastAck > maxOpsSinceLastSummary) {
527
+ if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
518
528
  this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
519
529
  // unregister default to no log on every op after falling behind
520
530
  // and register summary ack handler to re-register this handler
@@ -535,8 +545,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
535
545
  30 * 1000, // 30 sec max delay
536
546
  // throttling function increases exponentially (0ms, 40ms, 80ms, 160ms, etc)
537
547
  (0, throttler_1.formExponentialFn)({ coefficient: 20, initialDelay: 0 })), {
538
- initialDelayMs: this.runtimeOptions.summaryOptions.initialSummarizerDelayMs,
539
- }, this.runtimeOptions.summaryOptions.summarizerOptions);
548
+ initialDelayMs: this.initialSummarizerDelayMs,
549
+ }, this.heuristicsDisabled);
540
550
  this.summaryManager.start();
541
551
  }
542
552
  }
@@ -559,9 +569,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
559
569
  (0, common_utils_1.assert)(!readonly || !this.connected, 0x125 /* "Unsafe to transition to read-only state!" */);
560
570
  this.replayPendingStates();
561
571
  });
562
- if (context.pendingLocalState !== undefined) {
563
- this.deltaManager.on("op", this.onOp);
564
- }
565
572
  // logging hardware telemetry
566
573
  logger.sendTelemetryEvent(Object.assign({ eventName: "DeviceSpec" }, getDeviceSpec()));
567
574
  let loadSummaryNumber;
@@ -574,7 +581,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
574
581
  };
575
582
  // back-compat 0.59.3000 - Older document may either write summaryCount or not write it at all. If it does
576
583
  // not write it, initialize summaryNumber to 0.
577
- loadSummaryNumber = (_l = (_k = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _k !== void 0 ? _k : metadata === null || metadata === void 0 ? void 0 : metadata.summaryCount) !== null && _l !== void 0 ? _l : 0;
584
+ loadSummaryNumber = (_j = (_h = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _h !== void 0 ? _h : metadata === null || metadata === void 0 ? void 0 : metadata.summaryCount) !== null && _j !== void 0 ? _j : 0;
578
585
  }
579
586
  else {
580
587
  this.createContainerMetadata = {
@@ -610,13 +617,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
610
617
  runtimeVersion: packageVersion_1.pkgVersion,
611
618
  },
612
619
  });
613
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", useDataStoreAliasing = false, flushMode = defaultFlushMode, } = runtimeOptions;
614
- const storage = context.storage;
620
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", useDataStoreAliasing = false, flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
621
+ const pendingRuntimeState = context.pendingLocalState;
622
+ const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
623
+ const storage = !pendingRuntimeState ?
624
+ context.storage :
625
+ new serializedSnapshotStorage_1.SerializedSnapshotStorage(() => { return context.storage; }, pendingRuntimeState.snapshotBlobs);
615
626
  const registry = new dataStoreRegistry_1.FluidDataStoreRegistry(registryEntries);
616
627
  const tryFetchBlob = async (blobName) => {
617
- var _a;
618
- const blobId = (_a = context.baseSnapshot) === null || _a === void 0 ? void 0 : _a.blobs[blobName];
619
- if (context.baseSnapshot && blobId) {
628
+ const blobId = baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.blobs[blobName];
629
+ if (baseSnapshot && blobId) {
620
630
  // IContainerContext storage api return type still has undefined in 0.39 package version.
621
631
  // So once we release 0.40 container-defn package we can remove this check.
622
632
  (0, common_utils_1.assert)(storage !== undefined, 0x1f5 /* "Attached state should have storage" */);
@@ -631,7 +641,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
631
641
  ]);
632
642
  const loadExisting = existing === true || context.existing === true;
633
643
  // read snapshot blobs needed for BlobManager to load
634
- const blobManagerSnapshot = await blobManager_1.BlobManager.load((_b = context.baseSnapshot) === null || _b === void 0 ? void 0 : _b.trees[summaryFormat_1.blobsTreeName], async (id) => {
644
+ const blobManagerSnapshot = await blobManager_1.BlobManager.load(baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[summaryFormat_1.blobsTreeName], async (id) => {
635
645
  // IContainerContext storage api return type still has undefined in 0.39 package version.
636
646
  // So once we release 0.40 container-defn package we can remove this check.
637
647
  (0, common_utils_1.assert)(storage !== undefined, 0x256 /* "storage undefined in attached container" */);
@@ -639,7 +649,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
639
649
  });
640
650
  // Verify summary runtime sequence number matches protocol sequence number.
641
651
  const runtimeSequenceNumber = (_c = metadata === null || metadata === void 0 ? void 0 : metadata.message) === null || _c === void 0 ? void 0 : _c.sequenceNumber;
642
- if (runtimeSequenceNumber !== undefined) {
652
+ // When we load with pending state, we reuse an old snapshot so we don't expect these numbers to match
653
+ if (!pendingRuntimeState && runtimeSequenceNumber !== undefined) {
643
654
  const protocolSequenceNumber = context.deltaManager.initialSequenceNumber;
644
655
  // Unless bypass is explicitly set, then take action when sequence numbers mismatch.
645
656
  if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
@@ -661,7 +672,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
661
672
  loadSequenceNumberVerification,
662
673
  useDataStoreAliasing,
663
674
  flushMode,
664
- }, containerScope, logger, loadExisting, blobManagerSnapshot, requestHandler);
675
+ enableOfflineLoad,
676
+ }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
677
+ if (pendingRuntimeState) {
678
+ await runtime.processSavedOps(pendingRuntimeState);
679
+ // delete these once runtime has seen them to save space
680
+ pendingRuntimeState.savedOps = [];
681
+ }
682
+ await runtime.getSnapshotBlobs();
665
683
  return runtime;
666
684
  }
667
685
  get options() {
@@ -677,7 +695,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
677
695
  return this.context.deltaManager;
678
696
  }
679
697
  get storage() {
680
- return this.context.storage;
698
+ return this._storage;
681
699
  }
682
700
  get reSubmitFn() {
683
701
  // eslint-disable-next-line @typescript-eslint/unbound-method
@@ -709,19 +727,70 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
709
727
  var _a;
710
728
  return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
711
729
  }
712
- get summaryConfiguration() {
713
- var _a;
714
- return Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_a = this.runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides);
715
- }
716
730
  get disposed() { return this._disposed; }
717
731
  get summarizer() {
718
732
  (0, common_utils_1.assert)(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
719
733
  return this._summarizer;
720
734
  }
721
- get summariesDisabled() {
735
+ isSummariesDisabled() {
736
+ // back-compat: disableSummaries was moved from ISummaryRuntimeOptions
737
+ // to ISummaryConfiguration in 0.60.
738
+ if (this.runtimeOptions.summaryOptions.disableSummaries === true) {
739
+ return true;
740
+ }
741
+ return this.summaryConfiguration.state === "disabled";
742
+ }
743
+ isHeuristicsDisabled() {
722
744
  var _a;
723
- return this.runtimeOptions.summaryOptions.disableSummaries === true ||
724
- ((_a = this.runtimeOptions.summaryOptions.summaryConfigOverrides) === null || _a === void 0 ? void 0 : _a.disableSummaries) === true;
745
+ // back-compat: disableHeuristics was moved from ISummarizerOptions
746
+ // to ISummaryConfiguration in 0.60.
747
+ if (((_a = this.runtimeOptions.summaryOptions.summarizerOptions) === null || _a === void 0 ? void 0 : _a.disableHeuristics) === true) {
748
+ return true;
749
+ }
750
+ return this.summaryConfiguration.state === "disableHeuristics";
751
+ }
752
+ isSummarizerClientElectionEnabled() {
753
+ var _a;
754
+ if (this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) {
755
+ return (_a = this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) !== null && _a !== void 0 ? _a : true;
756
+ }
757
+ // back-compat: summarizerClientElection was moved from ISummaryRuntimeOptions
758
+ // to ISummaryConfiguration in 0.60.
759
+ if (this.runtimeOptions.summaryOptions.summarizerClientElection === true) {
760
+ return true;
761
+ }
762
+ if (this.summaryConfiguration.state !== "disabled") {
763
+ return this.summaryConfiguration.summarizerClientElection === true;
764
+ }
765
+ else {
766
+ return false;
767
+ }
768
+ }
769
+ getMaxOpsSinceLastSummary() {
770
+ // back-compat: maxOpsSinceLastSummary was moved from ISummaryRuntimeOptions
771
+ // to ISummaryConfiguration in 0.60.
772
+ if (this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary !== undefined) {
773
+ return this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary;
774
+ }
775
+ if (this.summaryConfiguration.state !== "disabled") {
776
+ return this.summaryConfiguration.maxOpsSinceLastSummary;
777
+ }
778
+ else {
779
+ return 0;
780
+ }
781
+ }
782
+ getInitialSummarizerDelayMs() {
783
+ // back-compat: initialSummarizerDelayMs was moved from ISummaryRuntimeOptions
784
+ // to ISummaryConfiguration in 0.60.
785
+ if (this.runtimeOptions.summaryOptions.initialSummarizerDelayMs !== undefined) {
786
+ return this.runtimeOptions.summaryOptions.initialSummarizerDelayMs;
787
+ }
788
+ if (this.summaryConfiguration.state !== "disabled") {
789
+ return this.summaryConfiguration.initialSummarizerDelayMs;
790
+ }
791
+ else {
792
+ return 0;
793
+ }
725
794
  }
726
795
  dispose(error) {
727
796
  var _a;
@@ -820,12 +889,17 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
820
889
  return (0, runtime_utils_1.exceptionToResponse)(error);
821
890
  }
822
891
  }
892
+ internalId(maybeAlias) {
893
+ var _a;
894
+ return (_a = this.dataStores.aliases().get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
895
+ }
823
896
  async getDataStoreFromRequest(id, request) {
824
897
  var _a, _b, _c;
825
898
  const wait = typeof ((_a = request.headers) === null || _a === void 0 ? void 0 : _a[RuntimeHeaders.wait]) === "boolean"
826
899
  ? (_b = request.headers) === null || _b === void 0 ? void 0 : _b[RuntimeHeaders.wait]
827
900
  : true;
828
- const dataStoreContext = await this.dataStores.getDataStore(id, wait);
901
+ const internalId = this.internalId(id);
902
+ const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
829
903
  /**
830
904
  * If GC should run and this an external app request with "externalRequest" header, we need to return
831
905
  * an error if the data store being requested is marked as unreferenced as per the data store's base
@@ -862,7 +936,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
862
936
  message: (_a = (0, summaryFormat_1.extractSummaryMetadataMessage)(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.messageAtLastSummary });
863
937
  (0, runtime_utils_1.addBlobToSummary)(summaryTree, summaryFormat_1.metadataBlobName, JSON.stringify(metadata));
864
938
  }
865
- addContainerStateToSummary(summaryTree, fullTree, trackState) {
939
+ addContainerStateToSummary(summaryTree, fullTree, trackState, telemetryContext) {
866
940
  var _a;
867
941
  this.addMetadataToSummary(summaryTree);
868
942
  if (this.chunkMap.size > 0) {
@@ -884,7 +958,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
884
958
  (0, runtime_utils_1.addTreeToSummary)(summaryTree, summaryFormat_1.blobsTreeName, blobManagerSummary);
885
959
  }
886
960
  if (this.garbageCollector.writeDataAtRoot) {
887
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState);
961
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
888
962
  if (gcSummary !== undefined) {
889
963
  (0, runtime_utils_1.addSummarizeResultToSummary)(summaryTree, garbageCollection_1.gcTreeKey, gcSummary);
890
964
  }
@@ -905,7 +979,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
905
979
  this.resetReconnectCount();
906
980
  return true;
907
981
  }
908
- this.consecutiveReconnects++;
909
982
  if (this.consecutiveReconnects === Math.floor(this.maxConsecutiveReconnects / 2)) {
910
983
  // If we're halfway through the max reconnects, send an event in order
911
984
  // to better identify false positives, if any. If the rate of this event
@@ -914,6 +987,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
914
987
  this.mc.logger.sendTelemetryEvent({
915
988
  eventName: "ReconnectsWithNoProgress",
916
989
  attempts: this.consecutiveReconnects,
990
+ pendingMessages: this.pendingStateManager.pendingMessagesCount,
917
991
  });
918
992
  }
919
993
  return this.consecutiveReconnects < this.maxConsecutiveReconnects;
@@ -969,29 +1043,42 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
969
1043
  this.verifyNotClosed();
970
1044
  // There might be no change of state due to Container calling this API after loading runtime.
971
1045
  const changeOfState = this._connected !== connected;
1046
+ const reconnection = changeOfState && connected;
972
1047
  this._connected = connected;
973
- if (changeOfState) {
974
- this.deltaManager.off("op", this.onOp);
975
- this.context.pendingLocalState = undefined;
1048
+ if (!connected) {
1049
+ this._perfSignalData.signalsLost = 0;
1050
+ this._perfSignalData.signalTimestamp = 0;
1051
+ this._perfSignalData.trackingSignalSequenceNumber = undefined;
1052
+ }
1053
+ if (reconnection) {
1054
+ this.consecutiveReconnects++;
976
1055
  if (!this.shouldContinueReconnecting()) {
977
- this.closeFn(new container_utils_1.GenericError(
1056
+ this.closeFn(
978
1057
  // pre-0.58 error message: MaxReconnectsWithNoProgress
979
- "Runtime detected too many reconnects with no progress syncing local ops", undefined, // error
980
- { attempts: this.consecutiveReconnects }));
1058
+ container_utils_1.DataProcessingError.create("Runtime detected too many reconnects with no progress syncing local ops", "setConnectionState", undefined, {
1059
+ dataLoss: 1,
1060
+ attempts: this.consecutiveReconnects,
1061
+ pendingMessages: this.pendingStateManager.pendingMessagesCount,
1062
+ }));
981
1063
  return;
982
1064
  }
1065
+ }
1066
+ if (changeOfState) {
983
1067
  this.replayPendingStates();
984
1068
  }
985
1069
  this.dataStores.setConnectionState(connected, clientId);
986
1070
  (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, connected, clientId);
987
1071
  }
988
1072
  process(messageArg, local) {
989
- var _a;
1073
+ var _a, _b;
990
1074
  this.verifyNotClosed();
991
1075
  // If it's not message for runtime, bail out right away.
992
1076
  if (!isRuntimeMessage(messageArg)) {
993
1077
  return;
994
1078
  }
1079
+ if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
1080
+ this.savedOps.push(messageArg);
1081
+ }
995
1082
  // Do shallow copy of message, as methods below will modify it.
996
1083
  // There might be multiple container instances receiving same message
997
1084
  // We do not need to make deep copy, as each layer will just replace message.content itself,
@@ -1006,8 +1093,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1006
1093
  // Chunk processing must come first given that we will transform the message to the unchunked version
1007
1094
  // once all pieces are available
1008
1095
  message = this.processRemoteChunkedMessage(message);
1009
- // Call the PendingStateManager to process messages.
1010
- const { localAck, localOpMetadata } = this.pendingStateManager.processMessage(message, local);
1096
+ let localOpMetadata;
1097
+ if (local) {
1098
+ // Call the PendingStateManager to process local messages.
1099
+ // Do not process local chunked ops until all pieces are available.
1100
+ if (message.type !== ContainerMessageType.ChunkedOp) {
1101
+ localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
1102
+ }
1103
+ }
1011
1104
  // If there are no more pending messages after processing a local message,
1012
1105
  // the document is no longer dirty.
1013
1106
  if (!this.pendingStateManager.hasPendingMessages()) {
@@ -1015,17 +1108,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1015
1108
  }
1016
1109
  switch (message.type) {
1017
1110
  case ContainerMessageType.Attach:
1018
- this.dataStores.processAttachMessage(message, local || localAck);
1111
+ this.dataStores.processAttachMessage(message, local);
1019
1112
  break;
1020
1113
  case ContainerMessageType.Alias:
1021
1114
  this.processAliasMessage(message, localOpMetadata, local);
1022
1115
  break;
1023
1116
  case ContainerMessageType.FluidDataStoreOp:
1024
- // if localAck === true, treat this as a local op because it's one we sent on a previous container
1025
- this.dataStores.processFluidDataStoreOp(message, local || localAck, localOpMetadata);
1117
+ this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
1026
1118
  break;
1027
1119
  case ContainerMessageType.BlobAttach:
1028
- (0, common_utils_1.assert)((_a = message === null || message === void 0 ? void 0 : message.metadata) === null || _a === void 0 ? void 0 : _a.blobId, 0x12a /* "Missing blob id on metadata" */);
1120
+ (0, common_utils_1.assert)((_b = message === null || message === void 0 ? void 0 : message.metadata) === null || _b === void 0 ? void 0 : _b.blobId, 0x12a /* "Missing blob id on metadata" */);
1029
1121
  this.blobManager.processBlobAttachOp(message.metadata.blobId, local);
1030
1122
  break;
1031
1123
  default:
@@ -1047,6 +1139,20 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1047
1139
  processAliasMessage(message, localOpMetadata, local) {
1048
1140
  this.dataStores.processAliasMessage(message, localOpMetadata, local);
1049
1141
  }
1142
+ /**
1143
+ * Emits the Signal event and update the perf signal data.
1144
+ * @param clientSignalSequenceNumber - is the client signal sequence number to be uploaded.
1145
+ */
1146
+ sendSignalTelemetryEvent(clientSignalSequenceNumber) {
1147
+ const duration = Date.now() - this._perfSignalData.signalTimestamp;
1148
+ this.logger.sendPerformanceEvent({
1149
+ eventName: "SignalLatency",
1150
+ duration,
1151
+ signalsLost: this._perfSignalData.signalsLost,
1152
+ });
1153
+ this._perfSignalData.signalsLost = 0;
1154
+ this._perfSignalData.signalTimestamp = 0;
1155
+ }
1050
1156
  processSignal(message, local) {
1051
1157
  const envelope = message.content;
1052
1158
  const transformed = {
@@ -1054,6 +1160,26 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1054
1160
  content: envelope.contents.content,
1055
1161
  type: envelope.contents.type,
1056
1162
  };
1163
+ // Only collect signal telemetry for messages sent by the current client.
1164
+ if (message.clientId === this.clientId && this.connected) {
1165
+ // Check to see if the signal was lost.
1166
+ if (this._perfSignalData.trackingSignalSequenceNumber !== undefined &&
1167
+ envelope.clientSignalSequenceNumber > this._perfSignalData.trackingSignalSequenceNumber) {
1168
+ this._perfSignalData.signalsLost++;
1169
+ this._perfSignalData.trackingSignalSequenceNumber = undefined;
1170
+ this.logger.sendErrorEvent({
1171
+ eventName: "SignalLost",
1172
+ type: envelope.contents.type,
1173
+ signalsLost: this._perfSignalData.signalsLost,
1174
+ trackingSequenceNumber: this._perfSignalData.trackingSignalSequenceNumber,
1175
+ clientSignalSequenceNumber: envelope.clientSignalSequenceNumber,
1176
+ });
1177
+ }
1178
+ else if (envelope.clientSignalSequenceNumber === this._perfSignalData.trackingSignalSequenceNumber) {
1179
+ this.sendSignalTelemetryEvent(envelope.clientSignalSequenceNumber);
1180
+ this._perfSignalData.trackingSignalSequenceNumber = undefined;
1181
+ }
1182
+ }
1057
1183
  if (envelope.address === undefined) {
1058
1184
  // No address indicates a container signal message.
1059
1185
  this.emit("signal", transformed, local);
@@ -1062,7 +1188,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1062
1188
  this.dataStores.processSignal(envelope.address, transformed, local);
1063
1189
  }
1064
1190
  async getRootDataStore(id, wait = true) {
1065
- const context = await this.dataStores.getDataStore(id, wait);
1191
+ const internalId = this.internalId(id);
1192
+ const context = await this.dataStores.getDataStore(internalId, wait);
1066
1193
  (0, common_utils_1.assert)(await context.isRoot(), 0x12b /* "did not get root data store" */);
1067
1194
  return context.realize();
1068
1195
  }
@@ -1117,18 +1244,32 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1117
1244
  }
1118
1245
  const savedFlushMode = this.flushMode;
1119
1246
  this.setFlushMode(runtime_definitions_1.FlushMode.TurnBased);
1120
- this.trackOrderSequentiallyCalls(callback);
1121
- this.flush();
1122
- this.setFlushMode(savedFlushMode);
1247
+ try {
1248
+ this.trackOrderSequentiallyCalls(callback);
1249
+ this.flush();
1250
+ }
1251
+ finally {
1252
+ this.setFlushMode(savedFlushMode);
1253
+ }
1123
1254
  }
1124
1255
  trackOrderSequentiallyCalls(callback) {
1256
+ let checkpoint;
1257
+ if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1258
+ checkpoint = this.pendingStateManager.checkpoint();
1259
+ }
1125
1260
  try {
1126
1261
  this._orderSequentiallyCalls++;
1127
1262
  callback();
1128
1263
  }
1129
1264
  catch (error) {
1130
- // pre-0.58 error message: orderSequentiallyCallbackException
1131
- this.closeFn(new container_utils_1.GenericError("orderSequentially callback exception", error));
1265
+ if (checkpoint) {
1266
+ // This will throw and close the container if rollback fails
1267
+ checkpoint.rollback();
1268
+ }
1269
+ else {
1270
+ // pre-0.58 error message: orderSequentiallyCallbackException
1271
+ this.closeFn(new container_utils_1.GenericError("orderSequentially callback exception", error));
1272
+ }
1132
1273
  throw error; // throw the original error for the consumer of the runtime
1133
1274
  }
1134
1275
  finally {
@@ -1157,7 +1298,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1157
1298
  }
1158
1299
  return fluidDataStore;
1159
1300
  }
1301
+ /**
1302
+ * @deprecated - will be removed in an upcoming release. See #9660.
1303
+ */
1160
1304
  async createRootDataStore(pkg, rootDataStoreId) {
1305
+ if (rootDataStoreId.includes("/")) {
1306
+ throw new container_utils_1.UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
1307
+ }
1161
1308
  return this._aliasingEnabled === true ?
1162
1309
  this.createAndAliasDataStore(pkg, rootDataStoreId) :
1163
1310
  this.createRootDataStoreLegacy(pkg, rootDataStoreId);
@@ -1194,6 +1341,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1194
1341
  return aliasedDataStore;
1195
1342
  }
1196
1343
  createDetachedRootDataStore(pkg, rootDataStoreId) {
1344
+ if (rootDataStoreId.includes("/")) {
1345
+ throw new container_utils_1.UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
1346
+ }
1197
1347
  return this.dataStores.createDetachedDataStoreCore(pkg, true, rootDataStoreId);
1198
1348
  }
1199
1349
  createDetachedDataStore(pkg) {
@@ -1267,6 +1417,21 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1267
1417
  }
1268
1418
  return true;
1269
1419
  }
1420
+ createNewSignalEnvelope(address, type, content) {
1421
+ const newSequenceNumber = ++this._perfSignalData.signalSequenceNumber;
1422
+ const newEnvelope = {
1423
+ address,
1424
+ clientSignalSequenceNumber: newSequenceNumber,
1425
+ contents: { type, content },
1426
+ };
1427
+ // We should not track any signals in case we already have a tracking number.
1428
+ if (newSequenceNumber % this.defaultTelemetrySignalSampleCount === 1 &&
1429
+ this._perfSignalData.trackingSignalSequenceNumber === undefined) {
1430
+ this._perfSignalData.signalTimestamp = Date.now();
1431
+ this._perfSignalData.trackingSignalSequenceNumber = newSequenceNumber;
1432
+ }
1433
+ return newEnvelope;
1434
+ }
1270
1435
  /**
1271
1436
  * Submits the signal to be sent to other clients.
1272
1437
  * @param type - Type of the signal.
@@ -1274,11 +1439,11 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1274
1439
  */
1275
1440
  submitSignal(type, content) {
1276
1441
  this.verifyNotClosed();
1277
- const envelope = { address: undefined, contents: { type, content } };
1442
+ const envelope = this.createNewSignalEnvelope(undefined /* address */, type, content);
1278
1443
  return this.context.submitSignalFn(envelope);
1279
1444
  }
1280
1445
  submitDataStoreSignal(address, type, content) {
1281
- const envelope = { address, contents: { type, content } };
1446
+ const envelope = this.createNewSignalEnvelope(address, type, content);
1282
1447
  return this.context.submitSignalFn(envelope);
1283
1448
  }
1284
1449
  setAttachState(attachState) {
@@ -1300,17 +1465,18 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1300
1465
  * @param blobRedirectTable - A table passed during the attach process. While detached, blob upload is supported
1301
1466
  * using IDs generated locally. After attach, these IDs cannot be used, so this table maps the old local IDs to the
1302
1467
  * new storage IDs so requests can be redirected.
1468
+ * @param telemetryContext - summary data passed through the layers for telemetry purposes
1303
1469
  */
1304
- createSummary(blobRedirectTable) {
1470
+ createSummary(blobRedirectTable, telemetryContext) {
1305
1471
  if (blobRedirectTable) {
1306
1472
  this.blobManager.setRedirectTable(blobRedirectTable);
1307
1473
  }
1308
- const summarizeResult = this.dataStores.createSummary();
1474
+ const summarizeResult = this.dataStores.createSummary(telemetryContext);
1309
1475
  if (!this.disableIsolatedChannels) {
1310
1476
  // Wrap data store summaries in .channels subtree.
1311
1477
  (0, summaryFormat_1.wrapSummaryInChannelsTree)(summarizeResult);
1312
1478
  }
1313
- this.addContainerStateToSummary(summarizeResult, true /* fullTree */, false /* trackState */);
1479
+ this.addContainerStateToSummary(summarizeResult, true /* fullTree */, false /* trackState */, telemetryContext);
1314
1480
  return summarizeResult.summary;
1315
1481
  }
1316
1482
  async getAbsoluteUrl(relativeUrl) {
@@ -1322,15 +1488,15 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1322
1488
  }
1323
1489
  return this.context.getAbsoluteUrl(relativeUrl);
1324
1490
  }
1325
- async summarizeInternal(fullTree, trackState) {
1326
- const summarizeResult = await this.dataStores.summarize(fullTree, trackState);
1491
+ async summarizeInternal(fullTree, trackState, telemetryContext) {
1492
+ const summarizeResult = await this.dataStores.summarize(fullTree, trackState, telemetryContext);
1327
1493
  let pathPartsForChildren;
1328
1494
  if (!this.disableIsolatedChannels) {
1329
1495
  // Wrap data store summaries in .channels subtree.
1330
1496
  (0, summaryFormat_1.wrapSummaryInChannelsTree)(summarizeResult);
1331
1497
  pathPartsForChildren = [runtime_definitions_1.channelsTreeName];
1332
1498
  }
1333
- this.addContainerStateToSummary(summarizeResult, fullTree, trackState);
1499
+ this.addContainerStateToSummary(summarizeResult, fullTree, trackState, telemetryContext);
1334
1500
  return Object.assign(Object.assign({}, summarizeResult), { id: "", pathPartsForChildren });
1335
1501
  }
1336
1502
  /**
@@ -1343,7 +1509,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1343
1509
  if (runGC) {
1344
1510
  gcStats = await this.collectGarbage({ logger: summaryLogger, runSweep, fullGC });
1345
1511
  }
1346
- const { stats, summary } = await this.summarizerNode.summarize(fullTree, trackState);
1512
+ const telemetryContext = new runtime_utils_1.TelemetryContext();
1513
+ const { stats, summary } = await this.summarizerNode.summarize(fullTree, trackState, telemetryContext);
1514
+ this.logger.sendTelemetryEvent({ eventName: "SummarizeTelemetry", details: telemetryContext.serialize() });
1347
1515
  (0, common_utils_1.assert)(summary.type === protocol_definitions_1.SummaryType.Tree, 0x12f /* "Container Runtime's summarize should always return a tree" */);
1348
1516
  return { stats, summary, gcStats };
1349
1517
  }
@@ -1707,9 +1875,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1707
1875
  }
1708
1876
  submit(type, content, localOpMetadata = undefined, opMetadata = undefined) {
1709
1877
  this.verifyNotClosed();
1710
- if (this.context.pendingLocalState !== undefined) {
1711
- this.closeFn(new container_utils_1.GenericError("containerRuntimeSubmitWithPendingLocalState"));
1712
- }
1713
1878
  // There should be no ops in detached container state!
1714
1879
  (0, common_utils_1.assert)(this.attachState !== container_definitions_1.AttachState.Detached, 0x132 /* "sending ops in detached container" */);
1715
1880
  let clientSequenceNumber = -1;
@@ -1840,6 +2005,17 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1840
2005
  (0, common_utils_1.unreachableCase)(type, `Unknown ContainerMessageType: ${type}`);
1841
2006
  }
1842
2007
  }
2008
+ rollback(type, content, localOpMetadata) {
2009
+ switch (type) {
2010
+ case ContainerMessageType.FluidDataStoreOp:
2011
+ // For operations, call rollbackDataStoreOp which will find the right store
2012
+ // and trigger rollback on it.
2013
+ this.dataStores.rollbackDataStoreOp(content, localOpMetadata);
2014
+ break;
2015
+ default:
2016
+ throw new Error(`Can't rollback ${type}`);
2017
+ }
2018
+ }
1843
2019
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
1844
2020
  async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
1845
2021
  const readAndParseBlob = async (id) => (0, driver_utils_1.readAndParse)(this.storage, id);
@@ -1884,8 +2060,43 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1884
2060
  return maybeSnapshot;
1885
2061
  });
1886
2062
  }
2063
+ notifyAttaching(snapshot) {
2064
+ var _a;
2065
+ if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
2066
+ this.baseSnapshotBlobs = serializedSnapshotStorage_1.SerializedSnapshotStorage.serializeTreeWithBlobContents(snapshot);
2067
+ }
2068
+ }
2069
+ async getSnapshotBlobs() {
2070
+ var _a;
2071
+ if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) ||
2072
+ this.attachState !== container_definitions_1.AttachState.Attached || this.context.pendingLocalState) {
2073
+ return;
2074
+ }
2075
+ (0, common_utils_1.assert)(!!this.context.baseSnapshot, 0x2e5 /* "Must have a base snapshot" */);
2076
+ this.baseSnapshotBlobs = await serializedSnapshotStorage_1.SerializedSnapshotStorage.serializeTree(this.context.baseSnapshot, this.storage);
2077
+ }
1887
2078
  getPendingLocalState() {
1888
- return this.pendingStateManager.getLocalState();
2079
+ var _a;
2080
+ if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad)) {
2081
+ throw new container_utils_1.UsageError("can't get state when offline load disabled");
2082
+ }
2083
+ const previousPendingState = this.context.pendingLocalState;
2084
+ if (previousPendingState) {
2085
+ return {
2086
+ pending: this.pendingStateManager.getLocalState(),
2087
+ snapshotBlobs: previousPendingState.snapshotBlobs,
2088
+ baseSnapshot: previousPendingState.baseSnapshot,
2089
+ savedOps: this.savedOps,
2090
+ };
2091
+ }
2092
+ (0, common_utils_1.assert)(!!this.context.baseSnapshot, 0x2e6 /* "Must have a base snapshot" */);
2093
+ (0, common_utils_1.assert)(!!this.baseSnapshotBlobs, 0x2e7 /* "Must serialize base snapshot blobs before getting runtime state" */);
2094
+ return {
2095
+ pending: this.pendingStateManager.getLocalState(),
2096
+ snapshotBlobs: this.baseSnapshotBlobs,
2097
+ baseSnapshot: this.context.baseSnapshot,
2098
+ savedOps: this.savedOps,
2099
+ };
1889
2100
  }
1890
2101
  /**
1891
2102
  * * Forms a function that will request a Summarizer.
@@ -1913,6 +2124,15 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1913
2124
  return summarizer;
1914
2125
  };
1915
2126
  }
2127
+ async processSavedOps(state) {
2128
+ for (const op of state.savedOps) {
2129
+ this.process(op, false);
2130
+ await this.pendingStateManager.applyStashedOpsAt(op.sequenceNumber);
2131
+ }
2132
+ // we may not have seen every sequence number (because of system ops) so apply everything once we
2133
+ // don't have any more saved ops
2134
+ await this.pendingStateManager.applyStashedOpsAt();
2135
+ }
1916
2136
  }
1917
2137
  exports.ContainerRuntime = ContainerRuntime;
1918
2138
  /**