@fluidframework/container-runtime 0.59.4000 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) 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.js +3 -3
  7. package/dist/connectionTelemetry.js.map +1 -1
  8. package/dist/containerRuntime.d.ts +125 -29
  9. package/dist/containerRuntime.d.ts.map +1 -1
  10. package/dist/containerRuntime.js +242 -110
  11. package/dist/containerRuntime.js.map +1 -1
  12. package/dist/dataStoreContext.d.ts +4 -2
  13. package/dist/dataStoreContext.d.ts.map +1 -1
  14. package/dist/dataStoreContext.js +16 -5
  15. package/dist/dataStoreContext.js.map +1 -1
  16. package/dist/dataStores.d.ts +4 -3
  17. package/dist/dataStores.d.ts.map +1 -1
  18. package/dist/dataStores.js +9 -3
  19. package/dist/dataStores.js.map +1 -1
  20. package/dist/garbageCollection.d.ts +14 -3
  21. package/dist/garbageCollection.d.ts.map +1 -1
  22. package/dist/garbageCollection.js +56 -26
  23. package/dist/garbageCollection.js.map +1 -1
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/orderedClientElection.js +0 -4
  29. package/dist/orderedClientElection.js.map +1 -1
  30. package/dist/packageVersion.d.ts +1 -1
  31. package/dist/packageVersion.d.ts.map +1 -1
  32. package/dist/packageVersion.js +1 -1
  33. package/dist/packageVersion.js.map +1 -1
  34. package/dist/pendingStateManager.d.ts +30 -29
  35. package/dist/pendingStateManager.d.ts.map +1 -1
  36. package/dist/pendingStateManager.js +72 -109
  37. package/dist/pendingStateManager.js.map +1 -1
  38. package/dist/runningSummarizer.d.ts +4 -3
  39. package/dist/runningSummarizer.d.ts.map +1 -1
  40. package/dist/runningSummarizer.js +11 -6
  41. package/dist/runningSummarizer.js.map +1 -1
  42. package/dist/serializedSnapshotStorage.d.ts +58 -0
  43. package/dist/serializedSnapshotStorage.d.ts.map +1 -0
  44. package/dist/serializedSnapshotStorage.js +108 -0
  45. package/dist/serializedSnapshotStorage.js.map +1 -0
  46. package/dist/summarizer.d.ts +11 -4
  47. package/dist/summarizer.d.ts.map +1 -1
  48. package/dist/summarizer.js +18 -9
  49. package/dist/summarizer.js.map +1 -1
  50. package/dist/summarizerHeuristics.d.ts +5 -3
  51. package/dist/summarizerHeuristics.d.ts.map +1 -1
  52. package/dist/summarizerHeuristics.js +10 -3
  53. package/dist/summarizerHeuristics.js.map +1 -1
  54. package/dist/summarizerTypes.d.ts +4 -2
  55. package/dist/summarizerTypes.d.ts.map +1 -1
  56. package/dist/summarizerTypes.js.map +1 -1
  57. package/dist/summaryManager.d.ts +3 -3
  58. package/dist/summaryManager.d.ts.map +1 -1
  59. package/dist/summaryManager.js +7 -7
  60. package/dist/summaryManager.js.map +1 -1
  61. package/garbageCollection.md +9 -1
  62. package/lib/blobManager.d.ts +2 -2
  63. package/lib/blobManager.d.ts.map +1 -1
  64. package/lib/blobManager.js +12 -11
  65. package/lib/blobManager.js.map +1 -1
  66. package/lib/connectionTelemetry.js +3 -3
  67. package/lib/connectionTelemetry.js.map +1 -1
  68. package/lib/containerRuntime.d.ts +125 -29
  69. package/lib/containerRuntime.d.ts.map +1 -1
  70. package/lib/containerRuntime.js +243 -111
  71. package/lib/containerRuntime.js.map +1 -1
  72. package/lib/dataStoreContext.d.ts +4 -2
  73. package/lib/dataStoreContext.d.ts.map +1 -1
  74. package/lib/dataStoreContext.js +16 -5
  75. package/lib/dataStoreContext.js.map +1 -1
  76. package/lib/dataStores.d.ts +4 -3
  77. package/lib/dataStores.d.ts.map +1 -1
  78. package/lib/dataStores.js +9 -3
  79. package/lib/dataStores.js.map +1 -1
  80. package/lib/garbageCollection.d.ts +14 -3
  81. package/lib/garbageCollection.d.ts.map +1 -1
  82. package/lib/garbageCollection.js +54 -6
  83. package/lib/garbageCollection.js.map +1 -1
  84. package/lib/index.d.ts +2 -2
  85. package/lib/index.d.ts.map +1 -1
  86. package/lib/index.js +1 -1
  87. package/lib/index.js.map +1 -1
  88. package/lib/orderedClientElection.js +0 -4
  89. package/lib/orderedClientElection.js.map +1 -1
  90. package/lib/packageVersion.d.ts +1 -1
  91. package/lib/packageVersion.d.ts.map +1 -1
  92. package/lib/packageVersion.js +1 -1
  93. package/lib/packageVersion.js.map +1 -1
  94. package/lib/pendingStateManager.d.ts +30 -29
  95. package/lib/pendingStateManager.d.ts.map +1 -1
  96. package/lib/pendingStateManager.js +72 -109
  97. package/lib/pendingStateManager.js.map +1 -1
  98. package/lib/runningSummarizer.d.ts +4 -3
  99. package/lib/runningSummarizer.d.ts.map +1 -1
  100. package/lib/runningSummarizer.js +11 -6
  101. package/lib/runningSummarizer.js.map +1 -1
  102. package/lib/serializedSnapshotStorage.d.ts +58 -0
  103. package/lib/serializedSnapshotStorage.d.ts.map +1 -0
  104. package/lib/serializedSnapshotStorage.js +104 -0
  105. package/lib/serializedSnapshotStorage.js.map +1 -0
  106. package/lib/summarizer.d.ts +11 -4
  107. package/lib/summarizer.d.ts.map +1 -1
  108. package/lib/summarizer.js +18 -9
  109. package/lib/summarizer.js.map +1 -1
  110. package/lib/summarizerHeuristics.d.ts +5 -3
  111. package/lib/summarizerHeuristics.d.ts.map +1 -1
  112. package/lib/summarizerHeuristics.js +10 -3
  113. package/lib/summarizerHeuristics.js.map +1 -1
  114. package/lib/summarizerTypes.d.ts +4 -2
  115. package/lib/summarizerTypes.d.ts.map +1 -1
  116. package/lib/summarizerTypes.js.map +1 -1
  117. package/lib/summaryManager.d.ts +3 -3
  118. package/lib/summaryManager.d.ts.map +1 -1
  119. package/lib/summaryManager.js +7 -7
  120. package/lib/summaryManager.js.map +1 -1
  121. package/package.json +45 -31
  122. package/src/blobManager.ts +29 -15
  123. package/src/connectionTelemetry.ts +3 -3
  124. package/src/containerRuntime.ts +388 -135
  125. package/src/dataStoreContext.ts +27 -5
  126. package/src/dataStores.ts +15 -3
  127. package/src/garbageCollection.ts +69 -12
  128. package/src/index.ts +7 -1
  129. package/src/orderedClientElection.ts +1 -1
  130. package/src/packageVersion.ts +1 -1
  131. package/src/pendingStateManager.ts +104 -123
  132. package/src/runningSummarizer.ts +20 -10
  133. package/src/serializedSnapshotStorage.ts +146 -0
  134. package/src/summarizer.ts +20 -16
  135. package/src/summarizerHeuristics.ts +21 -5
  136. package/src/summarizerTypes.ts +4 -2
  137. 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.
@@ -379,44 +379,26 @@ 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
- });
419
- };
420
402
  this.summarizeOnDemand = (...args) => {
421
403
  if (this.clientDetails.type === summarizerClientElection_1.summarizerClientType) {
422
404
  return this.summarizer.summarizeOnDemand(...args);
@@ -447,27 +429,34 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
447
429
  };
448
430
  this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
449
431
  // Default to false (enabled).
450
- this.disableIsolatedChannels = (_a = this.runtimeOptions.summaryOptions.disableIsolatedChannels) !== null && _a !== void 0 ? _a : false;
432
+ this.disableIsolatedChannels = (_b = this.runtimeOptions.summaryOptions.disableIsolatedChannels) !== null && _b !== void 0 ? _b : false;
451
433
  this._connected = this.context.connected;
452
434
  this.chunkMap = new Map(chunks);
453
435
  this.handleContext = new containerHandleContext_1.ContainerFluidHandleContext("", this);
454
436
  this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(this.logger, "ContainerRuntime"));
437
+ this.summariesDisabled = this.isSummariesDisabled();
438
+ this.heuristicsDisabled = this.isHeuristicsDisabled();
439
+ this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
440
+ this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
441
+ this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
455
442
  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);
443
+ ((_c = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _c !== void 0 ? _c : false) ||
444
+ ((_d = runtimeOptions.useDataStoreAliasing) !== null && _d !== void 0 ? _d : false);
445
+ this._maxOpSizeInBytes = ((_e = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _e !== void 0 ? _e : defaultMaxOpSizeInBytes);
459
446
  this.maxConsecutiveReconnects =
460
- (_e = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _e !== void 0 ? _e : this.defaultMaxConsecutiveReconnects;
447
+ (_f = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _f !== void 0 ? _f : this.defaultMaxConsecutiveReconnects;
461
448
  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);
449
+ const pendingRuntimeState = context.pendingLocalState;
450
+ const baseSnapshot = (_g = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _g !== void 0 ? _g : context.baseSnapshot;
451
+ 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; }, baseSnapshot, async (id) => (0, driver_utils_1.readAndParse)(this.storage, id), this.mc.logger, existing, metadata, this.context.clientDetails.type === summarizerClientElection_1.summarizerClientType);
463
452
  const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
464
453
  this.summarizerNode = (0, runtime_utils_1.createRootSummarizerNodeWithGC)(telemetry_utils_1.ChildLogger.create(this.logger, "SummarizerNode"),
465
454
  // Summarize function to call when summarize is called. Summarizer node always tracks summary state.
466
- async (fullTree, trackState) => this.summarizeInternal(fullTree, trackState),
455
+ async (fullTree, trackState, telemetryContext) => this.summarizeInternal(fullTree, trackState, telemetryContext),
467
456
  // Latest change sequence number, no changes since summary applied yet
468
457
  loadedFromSequenceNumber,
469
458
  // Summary reference sequence number, undefined if no summary yet
470
- context.baseSnapshot ? loadedFromSequenceNumber : undefined, {
459
+ baseSnapshot ? loadedFromSequenceNumber : undefined, {
471
460
  // Must set to false to prevent sending summary handle which would be pointing to
472
461
  // a summary with an older protocol state.
473
462
  canReuseHandle: false,
@@ -477,26 +466,31 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
477
466
  // If GC should not run, let the summarizer node know so that it does not track GC state.
478
467
  gcDisabled: !this.garbageCollector.shouldRunGC,
479
468
  });
480
- if (this.context.baseSnapshot) {
481
- this.summarizerNode.loadBaseSummaryWithoutDifferential(this.context.baseSnapshot);
469
+ if (baseSnapshot) {
470
+ this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
482
471
  }
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);
472
+ 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
473
  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
474
  this.scheduleManager = new ScheduleManager(context.deltaManager, this, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
486
475
  this.deltaSender = this.deltaManager;
487
- this.pendingStateManager = new pendingStateManager_1.PendingStateManager(this, async (type, content) => this.applyStashedOp(type, content), this._flushMode, context.pendingLocalState);
476
+ this.pendingStateManager = new pendingStateManager_1.PendingStateManager({
477
+ applyStashedOp: this.applyStashedOp.bind(this),
478
+ clientId: () => this.clientId,
479
+ close: this.closeFn,
480
+ connected: () => this.connected,
481
+ flush: this.flush.bind(this),
482
+ flushMode: () => this.flushMode,
483
+ reSubmit: this.reSubmit.bind(this),
484
+ rollback: this.rollback.bind(this),
485
+ setFlushMode: (mode) => this.setFlushMode(mode),
486
+ }, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
488
487
  this.context.quorum.on("removeMember", (clientId) => {
489
488
  this.clearPartialChunks(clientId);
490
489
  });
491
490
  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;
491
+ this.dirtyContainer = this.context.attachState !== container_definitions_1.AttachState.Attached
492
+ || this.pendingStateManager.hasPendingMessages();
495
493
  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
494
  if (this.summariesDisabled) {
501
495
  this.mc.logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
502
496
  }
@@ -504,9 +498,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
504
498
  const orderedClientLogger = telemetry_utils_1.ChildLogger.create(this.logger, "OrderedClientElection");
505
499
  const orderedClientCollection = new orderedClientElection_1.OrderedClientCollection(orderedClientLogger, this.context.deltaManager, this.context.quorum);
506
500
  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);
501
+ this.summarizerClientElection = new summarizerClientElection_1.SummarizerClientElection(orderedClientLogger, this.summaryCollection, orderedClientElectionForSummarizer, this.maxOpsSinceLastSummary, this.summarizerClientElectionEnabled);
510
502
  if (this.context.clientDetails.type === summarizerClientElection_1.summarizerClientType) {
511
503
  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
504
  }
@@ -514,7 +506,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
514
506
  // Only create a SummaryManager and SummarizerClientElection
515
507
  // if summaries are enabled and we are not the summarizer client.
516
508
  const defaultAction = () => {
517
- if (this.summaryCollection.opsSinceLastAck > maxOpsSinceLastSummary) {
509
+ if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
518
510
  this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
519
511
  // unregister default to no log on every op after falling behind
520
512
  // and register summary ack handler to re-register this handler
@@ -535,8 +527,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
535
527
  30 * 1000, // 30 sec max delay
536
528
  // throttling function increases exponentially (0ms, 40ms, 80ms, 160ms, etc)
537
529
  (0, throttler_1.formExponentialFn)({ coefficient: 20, initialDelay: 0 })), {
538
- initialDelayMs: this.runtimeOptions.summaryOptions.initialSummarizerDelayMs,
539
- }, this.runtimeOptions.summaryOptions.summarizerOptions);
530
+ initialDelayMs: this.initialSummarizerDelayMs,
531
+ }, this.heuristicsDisabled);
540
532
  this.summaryManager.start();
541
533
  }
542
534
  }
@@ -559,9 +551,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
559
551
  (0, common_utils_1.assert)(!readonly || !this.connected, 0x125 /* "Unsafe to transition to read-only state!" */);
560
552
  this.replayPendingStates();
561
553
  });
562
- if (context.pendingLocalState !== undefined) {
563
- this.deltaManager.on("op", this.onOp);
564
- }
565
554
  // logging hardware telemetry
566
555
  logger.sendTelemetryEvent(Object.assign({ eventName: "DeviceSpec" }, getDeviceSpec()));
567
556
  let loadSummaryNumber;
@@ -574,7 +563,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
574
563
  };
575
564
  // back-compat 0.59.3000 - Older document may either write summaryCount or not write it at all. If it does
576
565
  // 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;
566
+ 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
567
  }
579
568
  else {
580
569
  this.createContainerMetadata = {
@@ -610,13 +599,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
610
599
  runtimeVersion: packageVersion_1.pkgVersion,
611
600
  },
612
601
  });
613
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", useDataStoreAliasing = false, flushMode = defaultFlushMode, } = runtimeOptions;
614
- const storage = context.storage;
602
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", useDataStoreAliasing = false, flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
603
+ const pendingRuntimeState = context.pendingLocalState;
604
+ const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
605
+ const storage = !pendingRuntimeState ?
606
+ context.storage :
607
+ new serializedSnapshotStorage_1.SerializedSnapshotStorage(() => { return context.storage; }, pendingRuntimeState.snapshotBlobs);
615
608
  const registry = new dataStoreRegistry_1.FluidDataStoreRegistry(registryEntries);
616
609
  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) {
610
+ const blobId = baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.blobs[blobName];
611
+ if (baseSnapshot && blobId) {
620
612
  // IContainerContext storage api return type still has undefined in 0.39 package version.
621
613
  // So once we release 0.40 container-defn package we can remove this check.
622
614
  (0, common_utils_1.assert)(storage !== undefined, 0x1f5 /* "Attached state should have storage" */);
@@ -631,7 +623,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
631
623
  ]);
632
624
  const loadExisting = existing === true || context.existing === true;
633
625
  // 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) => {
626
+ const blobManagerSnapshot = await blobManager_1.BlobManager.load(baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[summaryFormat_1.blobsTreeName], async (id) => {
635
627
  // IContainerContext storage api return type still has undefined in 0.39 package version.
636
628
  // So once we release 0.40 container-defn package we can remove this check.
637
629
  (0, common_utils_1.assert)(storage !== undefined, 0x256 /* "storage undefined in attached container" */);
@@ -661,7 +653,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
661
653
  loadSequenceNumberVerification,
662
654
  useDataStoreAliasing,
663
655
  flushMode,
664
- }, containerScope, logger, loadExisting, blobManagerSnapshot, requestHandler);
656
+ enableOfflineLoad,
657
+ }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
658
+ if (pendingRuntimeState) {
659
+ await runtime.processSavedOps(pendingRuntimeState);
660
+ // delete these once runtime has seen them to save space
661
+ pendingRuntimeState.savedOps = [];
662
+ }
663
+ await runtime.getSnapshotBlobs();
665
664
  return runtime;
666
665
  }
667
666
  get options() {
@@ -677,7 +676,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
677
676
  return this.context.deltaManager;
678
677
  }
679
678
  get storage() {
680
- return this.context.storage;
679
+ return this._storage;
681
680
  }
682
681
  get reSubmitFn() {
683
682
  // eslint-disable-next-line @typescript-eslint/unbound-method
@@ -709,19 +708,70 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
709
708
  var _a;
710
709
  return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
711
710
  }
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
711
  get disposed() { return this._disposed; }
717
712
  get summarizer() {
718
713
  (0, common_utils_1.assert)(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
719
714
  return this._summarizer;
720
715
  }
721
- get summariesDisabled() {
716
+ isSummariesDisabled() {
717
+ // back-compat: disableSummaries was moved from ISummaryRuntimeOptions
718
+ // to ISummaryConfiguration in 0.60.
719
+ if (this.runtimeOptions.summaryOptions.disableSummaries === true) {
720
+ return true;
721
+ }
722
+ return this.summaryConfiguration.state === "disabled";
723
+ }
724
+ isHeuristicsDisabled() {
722
725
  var _a;
723
- return this.runtimeOptions.summaryOptions.disableSummaries === true ||
724
- ((_a = this.runtimeOptions.summaryOptions.summaryConfigOverrides) === null || _a === void 0 ? void 0 : _a.disableSummaries) === true;
726
+ // back-compat: disableHeuristics was moved from ISummarizerOptions
727
+ // to ISummaryConfiguration in 0.60.
728
+ if (((_a = this.runtimeOptions.summaryOptions.summarizerOptions) === null || _a === void 0 ? void 0 : _a.disableHeuristics) === true) {
729
+ return true;
730
+ }
731
+ return this.summaryConfiguration.state === "disableHeuristics";
732
+ }
733
+ isSummarizerClientElectionEnabled() {
734
+ var _a;
735
+ if (this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) {
736
+ return (_a = this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) !== null && _a !== void 0 ? _a : true;
737
+ }
738
+ // back-compat: summarizerClientElection was moved from ISummaryRuntimeOptions
739
+ // to ISummaryConfiguration in 0.60.
740
+ if (this.runtimeOptions.summaryOptions.summarizerClientElection === true) {
741
+ return true;
742
+ }
743
+ if (this.summaryConfiguration.state !== "disabled") {
744
+ return this.summaryConfiguration.summarizerClientElection === true;
745
+ }
746
+ else {
747
+ return false;
748
+ }
749
+ }
750
+ getMaxOpsSinceLastSummary() {
751
+ // back-compat: maxOpsSinceLastSummary was moved from ISummaryRuntimeOptions
752
+ // to ISummaryConfiguration in 0.60.
753
+ if (this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary !== undefined) {
754
+ return this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary;
755
+ }
756
+ if (this.summaryConfiguration.state !== "disabled") {
757
+ return this.summaryConfiguration.maxOpsSinceLastSummary;
758
+ }
759
+ else {
760
+ return 0;
761
+ }
762
+ }
763
+ getInitialSummarizerDelayMs() {
764
+ // back-compat: initialSummarizerDelayMs was moved from ISummaryRuntimeOptions
765
+ // to ISummaryConfiguration in 0.60.
766
+ if (this.runtimeOptions.summaryOptions.initialSummarizerDelayMs !== undefined) {
767
+ return this.runtimeOptions.summaryOptions.initialSummarizerDelayMs;
768
+ }
769
+ if (this.summaryConfiguration.state !== "disabled") {
770
+ return this.summaryConfiguration.initialSummarizerDelayMs;
771
+ }
772
+ else {
773
+ return 0;
774
+ }
725
775
  }
726
776
  dispose(error) {
727
777
  var _a;
@@ -862,7 +912,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
862
912
  message: (_a = (0, summaryFormat_1.extractSummaryMetadataMessage)(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.messageAtLastSummary });
863
913
  (0, runtime_utils_1.addBlobToSummary)(summaryTree, summaryFormat_1.metadataBlobName, JSON.stringify(metadata));
864
914
  }
865
- addContainerStateToSummary(summaryTree, fullTree, trackState) {
915
+ addContainerStateToSummary(summaryTree, fullTree, trackState, telemetryContext) {
866
916
  var _a;
867
917
  this.addMetadataToSummary(summaryTree);
868
918
  if (this.chunkMap.size > 0) {
@@ -884,7 +934,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
884
934
  (0, runtime_utils_1.addTreeToSummary)(summaryTree, summaryFormat_1.blobsTreeName, blobManagerSummary);
885
935
  }
886
936
  if (this.garbageCollector.writeDataAtRoot) {
887
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState);
937
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
888
938
  if (gcSummary !== undefined) {
889
939
  (0, runtime_utils_1.addSummarizeResultToSummary)(summaryTree, garbageCollection_1.gcTreeKey, gcSummary);
890
940
  }
@@ -905,7 +955,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
905
955
  this.resetReconnectCount();
906
956
  return true;
907
957
  }
908
- this.consecutiveReconnects++;
909
958
  if (this.consecutiveReconnects === Math.floor(this.maxConsecutiveReconnects / 2)) {
910
959
  // If we're halfway through the max reconnects, send an event in order
911
960
  // to better identify false positives, if any. If the rate of this event
@@ -914,6 +963,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
914
963
  this.mc.logger.sendTelemetryEvent({
915
964
  eventName: "ReconnectsWithNoProgress",
916
965
  attempts: this.consecutiveReconnects,
966
+ pendingMessages: this.pendingStateManager.pendingMessagesCount,
917
967
  });
918
968
  }
919
969
  return this.consecutiveReconnects < this.maxConsecutiveReconnects;
@@ -969,29 +1019,37 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
969
1019
  this.verifyNotClosed();
970
1020
  // There might be no change of state due to Container calling this API after loading runtime.
971
1021
  const changeOfState = this._connected !== connected;
1022
+ const reconnection = changeOfState && connected;
972
1023
  this._connected = connected;
973
- if (changeOfState) {
974
- this.deltaManager.off("op", this.onOp);
975
- this.context.pendingLocalState = undefined;
1024
+ if (reconnection) {
1025
+ this.consecutiveReconnects++;
976
1026
  if (!this.shouldContinueReconnecting()) {
977
1027
  this.closeFn(new container_utils_1.GenericError(
978
1028
  // pre-0.58 error message: MaxReconnectsWithNoProgress
979
1029
  "Runtime detected too many reconnects with no progress syncing local ops", undefined, // error
980
- { attempts: this.consecutiveReconnects }));
1030
+ {
1031
+ attempts: this.consecutiveReconnects,
1032
+ pendingMessages: this.pendingStateManager.pendingMessagesCount,
1033
+ }));
981
1034
  return;
982
1035
  }
1036
+ }
1037
+ if (changeOfState) {
983
1038
  this.replayPendingStates();
984
1039
  }
985
1040
  this.dataStores.setConnectionState(connected, clientId);
986
1041
  (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, connected, clientId);
987
1042
  }
988
1043
  process(messageArg, local) {
989
- var _a;
1044
+ var _a, _b;
990
1045
  this.verifyNotClosed();
991
1046
  // If it's not message for runtime, bail out right away.
992
1047
  if (!isRuntimeMessage(messageArg)) {
993
1048
  return;
994
1049
  }
1050
+ if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
1051
+ this.savedOps.push(messageArg);
1052
+ }
995
1053
  // Do shallow copy of message, as methods below will modify it.
996
1054
  // There might be multiple container instances receiving same message
997
1055
  // We do not need to make deep copy, as each layer will just replace message.content itself,
@@ -1006,8 +1064,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1006
1064
  // Chunk processing must come first given that we will transform the message to the unchunked version
1007
1065
  // once all pieces are available
1008
1066
  message = this.processRemoteChunkedMessage(message);
1009
- // Call the PendingStateManager to process messages.
1010
- const { localAck, localOpMetadata } = this.pendingStateManager.processMessage(message, local);
1067
+ let localOpMetadata;
1068
+ if (local) {
1069
+ // Call the PendingStateManager to process local messages.
1070
+ // Do not process local chunked ops until all pieces are available.
1071
+ if (message.type !== ContainerMessageType.ChunkedOp) {
1072
+ localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
1073
+ }
1074
+ }
1011
1075
  // If there are no more pending messages after processing a local message,
1012
1076
  // the document is no longer dirty.
1013
1077
  if (!this.pendingStateManager.hasPendingMessages()) {
@@ -1015,17 +1079,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1015
1079
  }
1016
1080
  switch (message.type) {
1017
1081
  case ContainerMessageType.Attach:
1018
- this.dataStores.processAttachMessage(message, local || localAck);
1082
+ this.dataStores.processAttachMessage(message, local);
1019
1083
  break;
1020
1084
  case ContainerMessageType.Alias:
1021
1085
  this.processAliasMessage(message, localOpMetadata, local);
1022
1086
  break;
1023
1087
  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);
1088
+ this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
1026
1089
  break;
1027
1090
  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" */);
1091
+ (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
1092
  this.blobManager.processBlobAttachOp(message.metadata.blobId, local);
1030
1093
  break;
1031
1094
  default:
@@ -1117,18 +1180,32 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1117
1180
  }
1118
1181
  const savedFlushMode = this.flushMode;
1119
1182
  this.setFlushMode(runtime_definitions_1.FlushMode.TurnBased);
1120
- this.trackOrderSequentiallyCalls(callback);
1121
- this.flush();
1122
- this.setFlushMode(savedFlushMode);
1183
+ try {
1184
+ this.trackOrderSequentiallyCalls(callback);
1185
+ this.flush();
1186
+ }
1187
+ finally {
1188
+ this.setFlushMode(savedFlushMode);
1189
+ }
1123
1190
  }
1124
1191
  trackOrderSequentiallyCalls(callback) {
1192
+ let checkpoint;
1193
+ if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1194
+ checkpoint = this.pendingStateManager.checkpoint();
1195
+ }
1125
1196
  try {
1126
1197
  this._orderSequentiallyCalls++;
1127
1198
  callback();
1128
1199
  }
1129
1200
  catch (error) {
1130
- // pre-0.58 error message: orderSequentiallyCallbackException
1131
- this.closeFn(new container_utils_1.GenericError("orderSequentially callback exception", error));
1201
+ if (checkpoint) {
1202
+ // This will throw and close the container if rollback fails
1203
+ checkpoint.rollback();
1204
+ }
1205
+ else {
1206
+ // pre-0.58 error message: orderSequentiallyCallbackException
1207
+ this.closeFn(new container_utils_1.GenericError("orderSequentially callback exception", error));
1208
+ }
1132
1209
  throw error; // throw the original error for the consumer of the runtime
1133
1210
  }
1134
1211
  finally {
@@ -1300,17 +1377,18 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1300
1377
  * @param blobRedirectTable - A table passed during the attach process. While detached, blob upload is supported
1301
1378
  * using IDs generated locally. After attach, these IDs cannot be used, so this table maps the old local IDs to the
1302
1379
  * new storage IDs so requests can be redirected.
1380
+ * @param telemetryContext - summary data passed through the layers for telemetry purposes
1303
1381
  */
1304
- createSummary(blobRedirectTable) {
1382
+ createSummary(blobRedirectTable, telemetryContext) {
1305
1383
  if (blobRedirectTable) {
1306
1384
  this.blobManager.setRedirectTable(blobRedirectTable);
1307
1385
  }
1308
- const summarizeResult = this.dataStores.createSummary();
1386
+ const summarizeResult = this.dataStores.createSummary(telemetryContext);
1309
1387
  if (!this.disableIsolatedChannels) {
1310
1388
  // Wrap data store summaries in .channels subtree.
1311
1389
  (0, summaryFormat_1.wrapSummaryInChannelsTree)(summarizeResult);
1312
1390
  }
1313
- this.addContainerStateToSummary(summarizeResult, true /* fullTree */, false /* trackState */);
1391
+ this.addContainerStateToSummary(summarizeResult, true /* fullTree */, false /* trackState */, telemetryContext);
1314
1392
  return summarizeResult.summary;
1315
1393
  }
1316
1394
  async getAbsoluteUrl(relativeUrl) {
@@ -1322,15 +1400,15 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1322
1400
  }
1323
1401
  return this.context.getAbsoluteUrl(relativeUrl);
1324
1402
  }
1325
- async summarizeInternal(fullTree, trackState) {
1326
- const summarizeResult = await this.dataStores.summarize(fullTree, trackState);
1403
+ async summarizeInternal(fullTree, trackState, telemetryContext) {
1404
+ const summarizeResult = await this.dataStores.summarize(fullTree, trackState, telemetryContext);
1327
1405
  let pathPartsForChildren;
1328
1406
  if (!this.disableIsolatedChannels) {
1329
1407
  // Wrap data store summaries in .channels subtree.
1330
1408
  (0, summaryFormat_1.wrapSummaryInChannelsTree)(summarizeResult);
1331
1409
  pathPartsForChildren = [runtime_definitions_1.channelsTreeName];
1332
1410
  }
1333
- this.addContainerStateToSummary(summarizeResult, fullTree, trackState);
1411
+ this.addContainerStateToSummary(summarizeResult, fullTree, trackState, telemetryContext);
1334
1412
  return Object.assign(Object.assign({}, summarizeResult), { id: "", pathPartsForChildren });
1335
1413
  }
1336
1414
  /**
@@ -1343,7 +1421,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1343
1421
  if (runGC) {
1344
1422
  gcStats = await this.collectGarbage({ logger: summaryLogger, runSweep, fullGC });
1345
1423
  }
1346
- const { stats, summary } = await this.summarizerNode.summarize(fullTree, trackState);
1424
+ const telemetryContext = new runtime_utils_1.TelemetryContext();
1425
+ const { stats, summary } = await this.summarizerNode.summarize(fullTree, trackState, telemetryContext);
1426
+ this.logger.sendTelemetryEvent({ eventName: "SummarizeTelemetry", details: telemetryContext.serialize() });
1347
1427
  (0, common_utils_1.assert)(summary.type === protocol_definitions_1.SummaryType.Tree, 0x12f /* "Container Runtime's summarize should always return a tree" */);
1348
1428
  return { stats, summary, gcStats };
1349
1429
  }
@@ -1707,9 +1787,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1707
1787
  }
1708
1788
  submit(type, content, localOpMetadata = undefined, opMetadata = undefined) {
1709
1789
  this.verifyNotClosed();
1710
- if (this.context.pendingLocalState !== undefined) {
1711
- this.closeFn(new container_utils_1.GenericError("containerRuntimeSubmitWithPendingLocalState"));
1712
- }
1713
1790
  // There should be no ops in detached container state!
1714
1791
  (0, common_utils_1.assert)(this.attachState !== container_definitions_1.AttachState.Detached, 0x132 /* "sending ops in detached container" */);
1715
1792
  let clientSequenceNumber = -1;
@@ -1840,6 +1917,17 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1840
1917
  (0, common_utils_1.unreachableCase)(type, `Unknown ContainerMessageType: ${type}`);
1841
1918
  }
1842
1919
  }
1920
+ rollback(type, content, localOpMetadata) {
1921
+ switch (type) {
1922
+ case ContainerMessageType.FluidDataStoreOp:
1923
+ // For operations, call rollbackDataStoreOp which will find the right store
1924
+ // and trigger rollback on it.
1925
+ this.dataStores.rollbackDataStoreOp(content, localOpMetadata);
1926
+ break;
1927
+ default:
1928
+ throw new Error(`Can't rollback ${type}`);
1929
+ }
1930
+ }
1843
1931
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
1844
1932
  async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
1845
1933
  const readAndParseBlob = async (id) => (0, driver_utils_1.readAndParse)(this.storage, id);
@@ -1884,8 +1972,43 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1884
1972
  return maybeSnapshot;
1885
1973
  });
1886
1974
  }
1975
+ notifyAttaching(snapshot) {
1976
+ var _a;
1977
+ if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
1978
+ this.baseSnapshotBlobs = serializedSnapshotStorage_1.SerializedSnapshotStorage.serializeTreeWithBlobContents(snapshot);
1979
+ }
1980
+ }
1981
+ async getSnapshotBlobs() {
1982
+ var _a;
1983
+ if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) ||
1984
+ this.attachState !== container_definitions_1.AttachState.Attached || this.context.pendingLocalState) {
1985
+ return;
1986
+ }
1987
+ (0, common_utils_1.assert)(!!this.context.baseSnapshot, 0x2e5 /* "Must have a base snapshot" */);
1988
+ this.baseSnapshotBlobs = await serializedSnapshotStorage_1.SerializedSnapshotStorage.serializeTree(this.context.baseSnapshot, this.storage);
1989
+ }
1887
1990
  getPendingLocalState() {
1888
- return this.pendingStateManager.getLocalState();
1991
+ var _a;
1992
+ if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad)) {
1993
+ throw new container_utils_1.UsageError("can't get state when offline load disabled");
1994
+ }
1995
+ const previousPendingState = this.context.pendingLocalState;
1996
+ if (previousPendingState) {
1997
+ return {
1998
+ pending: this.pendingStateManager.getLocalState(),
1999
+ snapshotBlobs: previousPendingState.snapshotBlobs,
2000
+ baseSnapshot: previousPendingState.baseSnapshot,
2001
+ savedOps: this.savedOps,
2002
+ };
2003
+ }
2004
+ (0, common_utils_1.assert)(!!this.context.baseSnapshot, 0x2e6 /* "Must have a base snapshot" */);
2005
+ (0, common_utils_1.assert)(!!this.baseSnapshotBlobs, 0x2e7 /* "Must serialize base snapshot blobs before getting runtime state" */);
2006
+ return {
2007
+ pending: this.pendingStateManager.getLocalState(),
2008
+ snapshotBlobs: this.baseSnapshotBlobs,
2009
+ baseSnapshot: this.context.baseSnapshot,
2010
+ savedOps: this.savedOps,
2011
+ };
1889
2012
  }
1890
2013
  /**
1891
2014
  * * Forms a function that will request a Summarizer.
@@ -1913,6 +2036,15 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
1913
2036
  return summarizer;
1914
2037
  };
1915
2038
  }
2039
+ async processSavedOps(state) {
2040
+ for (const op of state.savedOps) {
2041
+ this.process(op, false);
2042
+ await this.pendingStateManager.applyStashedOpsAt(op.sequenceNumber);
2043
+ }
2044
+ // we may not have seen every sequence number (because of system ops) so apply everything once we
2045
+ // don't have any more saved ops
2046
+ await this.pendingStateManager.applyStashedOpsAt();
2047
+ }
1916
2048
  }
1917
2049
  exports.ContainerRuntime = ContainerRuntime;
1918
2050
  /**