@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,12 +1,12 @@
1
1
  import { AttachState, LoaderHeader, } from "@fluidframework/container-definitions";
2
2
  import { assert, Trace, TypedEventEmitter, unreachableCase, performance, } from "@fluidframework/common-utils";
3
- import { ChildLogger, raiseConnectedEvent, PerformanceEvent, normalizeError, TaggedLoggerAdapter, loggerToMonitoringContext, TelemetryDataTag, } from "@fluidframework/telemetry-utils";
3
+ import { ChildLogger, raiseConnectedEvent, PerformanceEvent, TaggedLoggerAdapter, loggerToMonitoringContext, TelemetryDataTag, } from "@fluidframework/telemetry-utils";
4
4
  import { DriverHeader } from "@fluidframework/driver-definitions";
5
5
  import { readAndParse } from "@fluidframework/driver-utils";
6
6
  import { DataCorruptionError, GenericError, UsageError, extractSafePropertiesFromMessage, } from "@fluidframework/container-utils";
7
7
  import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
8
8
  import { FlushMode, channelsTreeName, } from "@fluidframework/runtime-definitions";
9
- import { addBlobToSummary, addTreeToSummary, createRootSummarizerNodeWithGC, RequestParser, create404Response, exceptionToResponse, requestFluidObject, responseToException, seqFromTree, calculateStats, addSummarizeResultToSummary, } from "@fluidframework/runtime-utils";
9
+ import { addBlobToSummary, addSummarizeResultToSummary, addTreeToSummary, createRootSummarizerNodeWithGC, RequestParser, create404Response, exceptionToResponse, requestFluidObject, responseToException, seqFromTree, calculateStats, TelemetryContext, } from "@fluidframework/runtime-utils";
10
10
  import { GCDataBuilder, trimLeadingAndTrailingSlashes } from "@fluidframework/garbage-collector";
11
11
  import { v4 as uuid } from "uuid";
12
12
  import { ContainerFluidHandleContext } from "./containerHandleContext";
@@ -28,6 +28,7 @@ import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
28
28
  import { GarbageCollector, GCNodeType, gcTreeKey, } from "./garbageCollection";
29
29
  import { channelToDataStore, isDataStoreAliasMessage, } from "./dataStore";
30
30
  import { BindBatchTracker } from "./batchTracker";
31
+ import { SerializedSnapshotStorage } from "./serializedSnapshotStorage";
31
32
  import { OpTracker } from "./opTelemetry";
32
33
  export var ContainerMessageType;
33
34
  (function (ContainerMessageType) {
@@ -44,17 +45,16 @@ export var ContainerMessageType;
44
45
  // Sets the alias of a root data store
45
46
  ContainerMessageType["Alias"] = "alias";
46
47
  })(ContainerMessageType || (ContainerMessageType = {}));
47
- // Consider idle 5s of no activity. And snapshot if a minute has gone by with no snapshot.
48
- const IdleDetectionTime = 5000;
49
- const DefaultSummaryConfiguration = {
50
- idleTime: IdleDetectionTime * 3,
51
- maxTime: IdleDetectionTime * 12,
52
- // Summarize if 1000 ops received since last snapshot.
53
- maxOps: 1000,
54
- // Wait 10 minutes for summary ack
55
- // this is less than maxSummarizeAckWaitTime
56
- // the min of the two will be chosen
57
- maxAckWaitTime: 600000,
48
+ export const DefaultSummaryConfiguration = {
49
+ state: "enabled",
50
+ idleTime: 5000 * 3,
51
+ maxTime: 5000 * 12,
52
+ maxOps: 100,
53
+ minOpsForLastSummaryAttempt: 10,
54
+ maxAckWaitTime: 6 * 10 * 1000,
55
+ maxOpsSinceLastSummary: 7000,
56
+ initialSummarizerDelayMs: 5000,
57
+ summarizerClientElection: false,
58
58
  };
59
59
  /**
60
60
  * Accepted header keys for requests coming to the runtime.
@@ -372,44 +372,26 @@ export function getDeviceSpec() {
372
372
  * It will define the store level mappings.
373
373
  */
374
374
  export class ContainerRuntime extends TypedEventEmitter {
375
- constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, requestHandler) {
376
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
375
+ constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
376
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
377
+ if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
377
378
  super();
378
379
  this.context = context;
379
380
  this.registry = registry;
380
381
  this.runtimeOptions = runtimeOptions;
381
382
  this.containerScope = containerScope;
382
383
  this.logger = logger;
384
+ this._storage = _storage;
383
385
  this.requestHandler = requestHandler;
386
+ this.summaryConfiguration = summaryConfiguration;
384
387
  this.defaultMaxConsecutiveReconnects = 15;
385
388
  this._orderSequentiallyCalls = 0;
386
389
  this.needsFlush = false;
387
390
  this.flushTrigger = false;
388
- this.paused = false;
391
+ this.savedOps = [];
389
392
  this.consecutiveReconnects = 0;
390
393
  this._disposed = false;
391
394
  this.emitDirtyDocumentEvent = true;
392
- /**
393
- * Used to apply stashed ops at their reference sequence number.
394
- * Normal op processing is synchronous, but applying stashed ops is async since the
395
- * data store may not be loaded yet, so we pause DeltaManager between ops.
396
- * It's also important that we see each op so we know all stashed ops have
397
- * been applied by "connected" event, but process() doesn't see system ops,
398
- * so we listen directly from DeltaManager instead.
399
- */
400
- this.onOp = (op) => {
401
- assert(!this.paused, 0x128 /* "Container should not already be paused before applying stashed ops" */);
402
- this.paused = true;
403
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
404
- this.context.deltaManager.inbound.pause();
405
- const stashP = this.pendingStateManager.applyStashedOpsAt(op.sequenceNumber);
406
- stashP.then(() => {
407
- this.paused = false;
408
- this.context.deltaManager.inbound.resume();
409
- }, (error) => {
410
- this.closeFn(normalizeError(error));
411
- });
412
- };
413
395
  this.summarizeOnDemand = (...args) => {
414
396
  if (this.clientDetails.type === summarizerClientType) {
415
397
  return this.summarizer.summarizeOnDemand(...args);
@@ -440,27 +422,34 @@ export class ContainerRuntime extends TypedEventEmitter {
440
422
  };
441
423
  this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
442
424
  // Default to false (enabled).
443
- this.disableIsolatedChannels = (_a = this.runtimeOptions.summaryOptions.disableIsolatedChannels) !== null && _a !== void 0 ? _a : false;
425
+ this.disableIsolatedChannels = (_b = this.runtimeOptions.summaryOptions.disableIsolatedChannels) !== null && _b !== void 0 ? _b : false;
444
426
  this._connected = this.context.connected;
445
427
  this.chunkMap = new Map(chunks);
446
428
  this.handleContext = new ContainerFluidHandleContext("", this);
447
429
  this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "ContainerRuntime"));
430
+ this.summariesDisabled = this.isSummariesDisabled();
431
+ this.heuristicsDisabled = this.isHeuristicsDisabled();
432
+ this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
433
+ this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
434
+ this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
448
435
  this._aliasingEnabled =
449
- ((_b = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _b !== void 0 ? _b : false) ||
450
- ((_c = runtimeOptions.useDataStoreAliasing) !== null && _c !== void 0 ? _c : false);
451
- this._maxOpSizeInBytes = ((_d = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _d !== void 0 ? _d : defaultMaxOpSizeInBytes);
436
+ ((_c = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _c !== void 0 ? _c : false) ||
437
+ ((_d = runtimeOptions.useDataStoreAliasing) !== null && _d !== void 0 ? _d : false);
438
+ this._maxOpSizeInBytes = ((_e = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _e !== void 0 ? _e : defaultMaxOpSizeInBytes);
452
439
  this.maxConsecutiveReconnects =
453
- (_e = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _e !== void 0 ? _e : this.defaultMaxConsecutiveReconnects;
440
+ (_f = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _f !== void 0 ? _f : this.defaultMaxConsecutiveReconnects;
454
441
  this._flushMode = runtimeOptions.flushMode;
455
- this.garbageCollector = 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) => readAndParse(this.storage, id), this.mc.logger, existing, metadata, this.context.clientDetails.type === summarizerClientType);
442
+ const pendingRuntimeState = context.pendingLocalState;
443
+ const baseSnapshot = (_g = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _g !== void 0 ? _g : context.baseSnapshot;
444
+ this.garbageCollector = 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) => readAndParse(this.storage, id), this.mc.logger, existing, metadata, this.context.clientDetails.type === summarizerClientType);
456
445
  const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
457
446
  this.summarizerNode = createRootSummarizerNodeWithGC(ChildLogger.create(this.logger, "SummarizerNode"),
458
447
  // Summarize function to call when summarize is called. Summarizer node always tracks summary state.
459
- async (fullTree, trackState) => this.summarizeInternal(fullTree, trackState),
448
+ async (fullTree, trackState, telemetryContext) => this.summarizeInternal(fullTree, trackState, telemetryContext),
460
449
  // Latest change sequence number, no changes since summary applied yet
461
450
  loadedFromSequenceNumber,
462
451
  // Summary reference sequence number, undefined if no summary yet
463
- context.baseSnapshot ? loadedFromSequenceNumber : undefined, {
452
+ baseSnapshot ? loadedFromSequenceNumber : undefined, {
464
453
  // Must set to false to prevent sending summary handle which would be pointing to
465
454
  // a summary with an older protocol state.
466
455
  canReuseHandle: false,
@@ -470,26 +459,31 @@ export class ContainerRuntime extends TypedEventEmitter {
470
459
  // If GC should not run, let the summarizer node know so that it does not track GC state.
471
460
  gcDisabled: !this.garbageCollector.shouldRunGC,
472
461
  });
473
- if (this.context.baseSnapshot) {
474
- this.summarizerNode.loadBaseSummaryWithoutDifferential(this.context.baseSnapshot);
462
+ if (baseSnapshot) {
463
+ this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
475
464
  }
476
- this.dataStores = new DataStores(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);
465
+ this.dataStores = new DataStores(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);
477
466
  this.blobManager = new BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId) => this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId }), (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, this.logger);
478
467
  this.scheduleManager = new ScheduleManager(context.deltaManager, this, ChildLogger.create(this.logger, "ScheduleManager"));
479
468
  this.deltaSender = this.deltaManager;
480
- this.pendingStateManager = new PendingStateManager(this, async (type, content) => this.applyStashedOp(type, content), this._flushMode, context.pendingLocalState);
469
+ this.pendingStateManager = new PendingStateManager({
470
+ applyStashedOp: this.applyStashedOp.bind(this),
471
+ clientId: () => this.clientId,
472
+ close: this.closeFn,
473
+ connected: () => this.connected,
474
+ flush: this.flush.bind(this),
475
+ flushMode: () => this.flushMode,
476
+ reSubmit: this.reSubmit.bind(this),
477
+ rollback: this.rollback.bind(this),
478
+ setFlushMode: (mode) => this.setFlushMode(mode),
479
+ }, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
481
480
  this.context.quorum.on("removeMember", (clientId) => {
482
481
  this.clearPartialChunks(clientId);
483
482
  });
484
483
  this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
485
- const { attachState, pendingLocalState } = this.context;
486
- this.dirtyContainer = attachState !== AttachState.Attached
487
- || ((_f = pendingLocalState) === null || _f === void 0 ? void 0 : _f.pendingStates.length) > 0;
484
+ this.dirtyContainer = this.context.attachState !== AttachState.Attached
485
+ || this.pendingStateManager.hasPendingMessages();
488
486
  this.context.updateDirtyContainerState(this.dirtyContainer);
489
- // Map the deprecated generateSummaries flag to disableSummaries.
490
- if (this.runtimeOptions.summaryOptions.generateSummaries === false) {
491
- this.runtimeOptions.summaryOptions.disableSummaries = true;
492
- }
493
487
  if (this.summariesDisabled) {
494
488
  this.mc.logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
495
489
  }
@@ -497,9 +491,7 @@ export class ContainerRuntime extends TypedEventEmitter {
497
491
  const orderedClientLogger = ChildLogger.create(this.logger, "OrderedClientElection");
498
492
  const orderedClientCollection = new OrderedClientCollection(orderedClientLogger, this.context.deltaManager, this.context.quorum);
499
493
  const orderedClientElectionForSummarizer = new OrderedClientElection(orderedClientLogger, orderedClientCollection, electedSummarizerData !== null && electedSummarizerData !== void 0 ? electedSummarizerData : this.context.deltaManager.lastSequenceNumber, SummarizerClientElection.isClientEligible);
500
- 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;
501
- const maxOpsSinceLastSummary = (_j = this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary) !== null && _j !== void 0 ? _j : 7000;
502
- this.summarizerClientElection = new SummarizerClientElection(orderedClientLogger, this.summaryCollection, orderedClientElectionForSummarizer, maxOpsSinceLastSummary, summarizerClientElectionEnabled);
494
+ this.summarizerClientElection = new SummarizerClientElection(orderedClientLogger, this.summaryCollection, orderedClientElectionForSummarizer, this.maxOpsSinceLastSummary, this.summarizerClientElectionEnabled);
503
495
  if (this.context.clientDetails.type === summarizerClientType) {
504
496
  this._summarizer = new Summarizer("/_summarizer", this /* ISummarizerRuntime */, () => this.summaryConfiguration, this /* ISummarizerInternalsProvider */, this.handleContext, this.summaryCollection, async (runtime) => RunWhileConnectedCoordinator.create(runtime));
505
497
  }
@@ -507,7 +499,7 @@ export class ContainerRuntime extends TypedEventEmitter {
507
499
  // Only create a SummaryManager and SummarizerClientElection
508
500
  // if summaries are enabled and we are not the summarizer client.
509
501
  const defaultAction = () => {
510
- if (this.summaryCollection.opsSinceLastAck > maxOpsSinceLastSummary) {
502
+ if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
511
503
  this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
512
504
  // unregister default to no log on every op after falling behind
513
505
  // and register summary ack handler to re-register this handler
@@ -528,8 +520,8 @@ export class ContainerRuntime extends TypedEventEmitter {
528
520
  30 * 1000, // 30 sec max delay
529
521
  // throttling function increases exponentially (0ms, 40ms, 80ms, 160ms, etc)
530
522
  formExponentialFn({ coefficient: 20, initialDelay: 0 })), {
531
- initialDelayMs: this.runtimeOptions.summaryOptions.initialSummarizerDelayMs,
532
- }, this.runtimeOptions.summaryOptions.summarizerOptions);
523
+ initialDelayMs: this.initialSummarizerDelayMs,
524
+ }, this.heuristicsDisabled);
533
525
  this.summaryManager.start();
534
526
  }
535
527
  }
@@ -552,9 +544,6 @@ export class ContainerRuntime extends TypedEventEmitter {
552
544
  assert(!readonly || !this.connected, 0x125 /* "Unsafe to transition to read-only state!" */);
553
545
  this.replayPendingStates();
554
546
  });
555
- if (context.pendingLocalState !== undefined) {
556
- this.deltaManager.on("op", this.onOp);
557
- }
558
547
  // logging hardware telemetry
559
548
  logger.sendTelemetryEvent(Object.assign({ eventName: "DeviceSpec" }, getDeviceSpec()));
560
549
  let loadSummaryNumber;
@@ -567,7 +556,7 @@ export class ContainerRuntime extends TypedEventEmitter {
567
556
  };
568
557
  // back-compat 0.59.3000 - Older document may either write summaryCount or not write it at all. If it does
569
558
  // not write it, initialize summaryNumber to 0.
570
- 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;
559
+ 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;
571
560
  }
572
561
  else {
573
562
  this.createContainerMetadata = {
@@ -603,13 +592,16 @@ export class ContainerRuntime extends TypedEventEmitter {
603
592
  runtimeVersion: pkgVersion,
604
593
  },
605
594
  });
606
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", useDataStoreAliasing = false, flushMode = defaultFlushMode, } = runtimeOptions;
607
- const storage = context.storage;
595
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", useDataStoreAliasing = false, flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
596
+ const pendingRuntimeState = context.pendingLocalState;
597
+ const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
598
+ const storage = !pendingRuntimeState ?
599
+ context.storage :
600
+ new SerializedSnapshotStorage(() => { return context.storage; }, pendingRuntimeState.snapshotBlobs);
608
601
  const registry = new FluidDataStoreRegistry(registryEntries);
609
602
  const tryFetchBlob = async (blobName) => {
610
- var _a;
611
- const blobId = (_a = context.baseSnapshot) === null || _a === void 0 ? void 0 : _a.blobs[blobName];
612
- if (context.baseSnapshot && blobId) {
603
+ const blobId = baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.blobs[blobName];
604
+ if (baseSnapshot && blobId) {
613
605
  // IContainerContext storage api return type still has undefined in 0.39 package version.
614
606
  // So once we release 0.40 container-defn package we can remove this check.
615
607
  assert(storage !== undefined, 0x1f5 /* "Attached state should have storage" */);
@@ -624,7 +616,7 @@ export class ContainerRuntime extends TypedEventEmitter {
624
616
  ]);
625
617
  const loadExisting = existing === true || context.existing === true;
626
618
  // read snapshot blobs needed for BlobManager to load
627
- const blobManagerSnapshot = await BlobManager.load((_b = context.baseSnapshot) === null || _b === void 0 ? void 0 : _b.trees[blobsTreeName], async (id) => {
619
+ const blobManagerSnapshot = await BlobManager.load(baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[blobsTreeName], async (id) => {
628
620
  // IContainerContext storage api return type still has undefined in 0.39 package version.
629
621
  // So once we release 0.40 container-defn package we can remove this check.
630
622
  assert(storage !== undefined, 0x256 /* "storage undefined in attached container" */);
@@ -654,7 +646,14 @@ export class ContainerRuntime extends TypedEventEmitter {
654
646
  loadSequenceNumberVerification,
655
647
  useDataStoreAliasing,
656
648
  flushMode,
657
- }, containerScope, logger, loadExisting, blobManagerSnapshot, requestHandler);
649
+ enableOfflineLoad,
650
+ }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
651
+ if (pendingRuntimeState) {
652
+ await runtime.processSavedOps(pendingRuntimeState);
653
+ // delete these once runtime has seen them to save space
654
+ pendingRuntimeState.savedOps = [];
655
+ }
656
+ await runtime.getSnapshotBlobs();
658
657
  return runtime;
659
658
  }
660
659
  get options() {
@@ -670,7 +669,7 @@ export class ContainerRuntime extends TypedEventEmitter {
670
669
  return this.context.deltaManager;
671
670
  }
672
671
  get storage() {
673
- return this.context.storage;
672
+ return this._storage;
674
673
  }
675
674
  get reSubmitFn() {
676
675
  // eslint-disable-next-line @typescript-eslint/unbound-method
@@ -702,19 +701,70 @@ export class ContainerRuntime extends TypedEventEmitter {
702
701
  var _a;
703
702
  return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
704
703
  }
705
- get summaryConfiguration() {
706
- var _a;
707
- return Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_a = this.runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides);
708
- }
709
704
  get disposed() { return this._disposed; }
710
705
  get summarizer() {
711
706
  assert(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
712
707
  return this._summarizer;
713
708
  }
714
- get summariesDisabled() {
709
+ isSummariesDisabled() {
710
+ // back-compat: disableSummaries was moved from ISummaryRuntimeOptions
711
+ // to ISummaryConfiguration in 0.60.
712
+ if (this.runtimeOptions.summaryOptions.disableSummaries === true) {
713
+ return true;
714
+ }
715
+ return this.summaryConfiguration.state === "disabled";
716
+ }
717
+ isHeuristicsDisabled() {
715
718
  var _a;
716
- return this.runtimeOptions.summaryOptions.disableSummaries === true ||
717
- ((_a = this.runtimeOptions.summaryOptions.summaryConfigOverrides) === null || _a === void 0 ? void 0 : _a.disableSummaries) === true;
719
+ // back-compat: disableHeuristics was moved from ISummarizerOptions
720
+ // to ISummaryConfiguration in 0.60.
721
+ if (((_a = this.runtimeOptions.summaryOptions.summarizerOptions) === null || _a === void 0 ? void 0 : _a.disableHeuristics) === true) {
722
+ return true;
723
+ }
724
+ return this.summaryConfiguration.state === "disableHeuristics";
725
+ }
726
+ isSummarizerClientElectionEnabled() {
727
+ var _a;
728
+ if (this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) {
729
+ return (_a = this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) !== null && _a !== void 0 ? _a : true;
730
+ }
731
+ // back-compat: summarizerClientElection was moved from ISummaryRuntimeOptions
732
+ // to ISummaryConfiguration in 0.60.
733
+ if (this.runtimeOptions.summaryOptions.summarizerClientElection === true) {
734
+ return true;
735
+ }
736
+ if (this.summaryConfiguration.state !== "disabled") {
737
+ return this.summaryConfiguration.summarizerClientElection === true;
738
+ }
739
+ else {
740
+ return false;
741
+ }
742
+ }
743
+ getMaxOpsSinceLastSummary() {
744
+ // back-compat: maxOpsSinceLastSummary was moved from ISummaryRuntimeOptions
745
+ // to ISummaryConfiguration in 0.60.
746
+ if (this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary !== undefined) {
747
+ return this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary;
748
+ }
749
+ if (this.summaryConfiguration.state !== "disabled") {
750
+ return this.summaryConfiguration.maxOpsSinceLastSummary;
751
+ }
752
+ else {
753
+ return 0;
754
+ }
755
+ }
756
+ getInitialSummarizerDelayMs() {
757
+ // back-compat: initialSummarizerDelayMs was moved from ISummaryRuntimeOptions
758
+ // to ISummaryConfiguration in 0.60.
759
+ if (this.runtimeOptions.summaryOptions.initialSummarizerDelayMs !== undefined) {
760
+ return this.runtimeOptions.summaryOptions.initialSummarizerDelayMs;
761
+ }
762
+ if (this.summaryConfiguration.state !== "disabled") {
763
+ return this.summaryConfiguration.initialSummarizerDelayMs;
764
+ }
765
+ else {
766
+ return 0;
767
+ }
718
768
  }
719
769
  dispose(error) {
720
770
  var _a;
@@ -855,7 +905,7 @@ export class ContainerRuntime extends TypedEventEmitter {
855
905
  message: (_a = extractSummaryMetadataMessage(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.messageAtLastSummary });
856
906
  addBlobToSummary(summaryTree, metadataBlobName, JSON.stringify(metadata));
857
907
  }
858
- addContainerStateToSummary(summaryTree, fullTree, trackState) {
908
+ addContainerStateToSummary(summaryTree, fullTree, trackState, telemetryContext) {
859
909
  var _a;
860
910
  this.addMetadataToSummary(summaryTree);
861
911
  if (this.chunkMap.size > 0) {
@@ -877,7 +927,7 @@ export class ContainerRuntime extends TypedEventEmitter {
877
927
  addTreeToSummary(summaryTree, blobsTreeName, blobManagerSummary);
878
928
  }
879
929
  if (this.garbageCollector.writeDataAtRoot) {
880
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState);
930
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
881
931
  if (gcSummary !== undefined) {
882
932
  addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
883
933
  }
@@ -898,7 +948,6 @@ export class ContainerRuntime extends TypedEventEmitter {
898
948
  this.resetReconnectCount();
899
949
  return true;
900
950
  }
901
- this.consecutiveReconnects++;
902
951
  if (this.consecutiveReconnects === Math.floor(this.maxConsecutiveReconnects / 2)) {
903
952
  // If we're halfway through the max reconnects, send an event in order
904
953
  // to better identify false positives, if any. If the rate of this event
@@ -907,6 +956,7 @@ export class ContainerRuntime extends TypedEventEmitter {
907
956
  this.mc.logger.sendTelemetryEvent({
908
957
  eventName: "ReconnectsWithNoProgress",
909
958
  attempts: this.consecutiveReconnects,
959
+ pendingMessages: this.pendingStateManager.pendingMessagesCount,
910
960
  });
911
961
  }
912
962
  return this.consecutiveReconnects < this.maxConsecutiveReconnects;
@@ -962,29 +1012,37 @@ export class ContainerRuntime extends TypedEventEmitter {
962
1012
  this.verifyNotClosed();
963
1013
  // There might be no change of state due to Container calling this API after loading runtime.
964
1014
  const changeOfState = this._connected !== connected;
1015
+ const reconnection = changeOfState && connected;
965
1016
  this._connected = connected;
966
- if (changeOfState) {
967
- this.deltaManager.off("op", this.onOp);
968
- this.context.pendingLocalState = undefined;
1017
+ if (reconnection) {
1018
+ this.consecutiveReconnects++;
969
1019
  if (!this.shouldContinueReconnecting()) {
970
1020
  this.closeFn(new GenericError(
971
1021
  // pre-0.58 error message: MaxReconnectsWithNoProgress
972
1022
  "Runtime detected too many reconnects with no progress syncing local ops", undefined, // error
973
- { attempts: this.consecutiveReconnects }));
1023
+ {
1024
+ attempts: this.consecutiveReconnects,
1025
+ pendingMessages: this.pendingStateManager.pendingMessagesCount,
1026
+ }));
974
1027
  return;
975
1028
  }
1029
+ }
1030
+ if (changeOfState) {
976
1031
  this.replayPendingStates();
977
1032
  }
978
1033
  this.dataStores.setConnectionState(connected, clientId);
979
1034
  raiseConnectedEvent(this.mc.logger, this, connected, clientId);
980
1035
  }
981
1036
  process(messageArg, local) {
982
- var _a;
1037
+ var _a, _b;
983
1038
  this.verifyNotClosed();
984
1039
  // If it's not message for runtime, bail out right away.
985
1040
  if (!isRuntimeMessage(messageArg)) {
986
1041
  return;
987
1042
  }
1043
+ if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
1044
+ this.savedOps.push(messageArg);
1045
+ }
988
1046
  // Do shallow copy of message, as methods below will modify it.
989
1047
  // There might be multiple container instances receiving same message
990
1048
  // We do not need to make deep copy, as each layer will just replace message.content itself,
@@ -999,8 +1057,14 @@ export class ContainerRuntime extends TypedEventEmitter {
999
1057
  // Chunk processing must come first given that we will transform the message to the unchunked version
1000
1058
  // once all pieces are available
1001
1059
  message = this.processRemoteChunkedMessage(message);
1002
- // Call the PendingStateManager to process messages.
1003
- const { localAck, localOpMetadata } = this.pendingStateManager.processMessage(message, local);
1060
+ let localOpMetadata;
1061
+ if (local) {
1062
+ // Call the PendingStateManager to process local messages.
1063
+ // Do not process local chunked ops until all pieces are available.
1064
+ if (message.type !== ContainerMessageType.ChunkedOp) {
1065
+ localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
1066
+ }
1067
+ }
1004
1068
  // If there are no more pending messages after processing a local message,
1005
1069
  // the document is no longer dirty.
1006
1070
  if (!this.pendingStateManager.hasPendingMessages()) {
@@ -1008,17 +1072,16 @@ export class ContainerRuntime extends TypedEventEmitter {
1008
1072
  }
1009
1073
  switch (message.type) {
1010
1074
  case ContainerMessageType.Attach:
1011
- this.dataStores.processAttachMessage(message, local || localAck);
1075
+ this.dataStores.processAttachMessage(message, local);
1012
1076
  break;
1013
1077
  case ContainerMessageType.Alias:
1014
1078
  this.processAliasMessage(message, localOpMetadata, local);
1015
1079
  break;
1016
1080
  case ContainerMessageType.FluidDataStoreOp:
1017
- // if localAck === true, treat this as a local op because it's one we sent on a previous container
1018
- this.dataStores.processFluidDataStoreOp(message, local || localAck, localOpMetadata);
1081
+ this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
1019
1082
  break;
1020
1083
  case ContainerMessageType.BlobAttach:
1021
- 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" */);
1084
+ 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" */);
1022
1085
  this.blobManager.processBlobAttachOp(message.metadata.blobId, local);
1023
1086
  break;
1024
1087
  default:
@@ -1110,18 +1173,32 @@ export class ContainerRuntime extends TypedEventEmitter {
1110
1173
  }
1111
1174
  const savedFlushMode = this.flushMode;
1112
1175
  this.setFlushMode(FlushMode.TurnBased);
1113
- this.trackOrderSequentiallyCalls(callback);
1114
- this.flush();
1115
- this.setFlushMode(savedFlushMode);
1176
+ try {
1177
+ this.trackOrderSequentiallyCalls(callback);
1178
+ this.flush();
1179
+ }
1180
+ finally {
1181
+ this.setFlushMode(savedFlushMode);
1182
+ }
1116
1183
  }
1117
1184
  trackOrderSequentiallyCalls(callback) {
1185
+ let checkpoint;
1186
+ if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1187
+ checkpoint = this.pendingStateManager.checkpoint();
1188
+ }
1118
1189
  try {
1119
1190
  this._orderSequentiallyCalls++;
1120
1191
  callback();
1121
1192
  }
1122
1193
  catch (error) {
1123
- // pre-0.58 error message: orderSequentiallyCallbackException
1124
- this.closeFn(new GenericError("orderSequentially callback exception", error));
1194
+ if (checkpoint) {
1195
+ // This will throw and close the container if rollback fails
1196
+ checkpoint.rollback();
1197
+ }
1198
+ else {
1199
+ // pre-0.58 error message: orderSequentiallyCallbackException
1200
+ this.closeFn(new GenericError("orderSequentially callback exception", error));
1201
+ }
1125
1202
  throw error; // throw the original error for the consumer of the runtime
1126
1203
  }
1127
1204
  finally {
@@ -1293,17 +1370,18 @@ export class ContainerRuntime extends TypedEventEmitter {
1293
1370
  * @param blobRedirectTable - A table passed during the attach process. While detached, blob upload is supported
1294
1371
  * using IDs generated locally. After attach, these IDs cannot be used, so this table maps the old local IDs to the
1295
1372
  * new storage IDs so requests can be redirected.
1373
+ * @param telemetryContext - summary data passed through the layers for telemetry purposes
1296
1374
  */
1297
- createSummary(blobRedirectTable) {
1375
+ createSummary(blobRedirectTable, telemetryContext) {
1298
1376
  if (blobRedirectTable) {
1299
1377
  this.blobManager.setRedirectTable(blobRedirectTable);
1300
1378
  }
1301
- const summarizeResult = this.dataStores.createSummary();
1379
+ const summarizeResult = this.dataStores.createSummary(telemetryContext);
1302
1380
  if (!this.disableIsolatedChannels) {
1303
1381
  // Wrap data store summaries in .channels subtree.
1304
1382
  wrapSummaryInChannelsTree(summarizeResult);
1305
1383
  }
1306
- this.addContainerStateToSummary(summarizeResult, true /* fullTree */, false /* trackState */);
1384
+ this.addContainerStateToSummary(summarizeResult, true /* fullTree */, false /* trackState */, telemetryContext);
1307
1385
  return summarizeResult.summary;
1308
1386
  }
1309
1387
  async getAbsoluteUrl(relativeUrl) {
@@ -1315,15 +1393,15 @@ export class ContainerRuntime extends TypedEventEmitter {
1315
1393
  }
1316
1394
  return this.context.getAbsoluteUrl(relativeUrl);
1317
1395
  }
1318
- async summarizeInternal(fullTree, trackState) {
1319
- const summarizeResult = await this.dataStores.summarize(fullTree, trackState);
1396
+ async summarizeInternal(fullTree, trackState, telemetryContext) {
1397
+ const summarizeResult = await this.dataStores.summarize(fullTree, trackState, telemetryContext);
1320
1398
  let pathPartsForChildren;
1321
1399
  if (!this.disableIsolatedChannels) {
1322
1400
  // Wrap data store summaries in .channels subtree.
1323
1401
  wrapSummaryInChannelsTree(summarizeResult);
1324
1402
  pathPartsForChildren = [channelsTreeName];
1325
1403
  }
1326
- this.addContainerStateToSummary(summarizeResult, fullTree, trackState);
1404
+ this.addContainerStateToSummary(summarizeResult, fullTree, trackState, telemetryContext);
1327
1405
  return Object.assign(Object.assign({}, summarizeResult), { id: "", pathPartsForChildren });
1328
1406
  }
1329
1407
  /**
@@ -1336,7 +1414,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1336
1414
  if (runGC) {
1337
1415
  gcStats = await this.collectGarbage({ logger: summaryLogger, runSweep, fullGC });
1338
1416
  }
1339
- const { stats, summary } = await this.summarizerNode.summarize(fullTree, trackState);
1417
+ const telemetryContext = new TelemetryContext();
1418
+ const { stats, summary } = await this.summarizerNode.summarize(fullTree, trackState, telemetryContext);
1419
+ this.logger.sendTelemetryEvent({ eventName: "SummarizeTelemetry", details: telemetryContext.serialize() });
1340
1420
  assert(summary.type === SummaryType.Tree, 0x12f /* "Container Runtime's summarize should always return a tree" */);
1341
1421
  return { stats, summary, gcStats };
1342
1422
  }
@@ -1700,9 +1780,6 @@ export class ContainerRuntime extends TypedEventEmitter {
1700
1780
  }
1701
1781
  submit(type, content, localOpMetadata = undefined, opMetadata = undefined) {
1702
1782
  this.verifyNotClosed();
1703
- if (this.context.pendingLocalState !== undefined) {
1704
- this.closeFn(new GenericError("containerRuntimeSubmitWithPendingLocalState"));
1705
- }
1706
1783
  // There should be no ops in detached container state!
1707
1784
  assert(this.attachState !== AttachState.Detached, 0x132 /* "sending ops in detached container" */);
1708
1785
  let clientSequenceNumber = -1;
@@ -1833,6 +1910,17 @@ export class ContainerRuntime extends TypedEventEmitter {
1833
1910
  unreachableCase(type, `Unknown ContainerMessageType: ${type}`);
1834
1911
  }
1835
1912
  }
1913
+ rollback(type, content, localOpMetadata) {
1914
+ switch (type) {
1915
+ case ContainerMessageType.FluidDataStoreOp:
1916
+ // For operations, call rollbackDataStoreOp which will find the right store
1917
+ // and trigger rollback on it.
1918
+ this.dataStores.rollbackDataStoreOp(content, localOpMetadata);
1919
+ break;
1920
+ default:
1921
+ throw new Error(`Can't rollback ${type}`);
1922
+ }
1923
+ }
1836
1924
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
1837
1925
  async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
1838
1926
  const readAndParseBlob = async (id) => readAndParse(this.storage, id);
@@ -1877,8 +1965,43 @@ export class ContainerRuntime extends TypedEventEmitter {
1877
1965
  return maybeSnapshot;
1878
1966
  });
1879
1967
  }
1968
+ notifyAttaching(snapshot) {
1969
+ var _a;
1970
+ if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
1971
+ this.baseSnapshotBlobs = SerializedSnapshotStorage.serializeTreeWithBlobContents(snapshot);
1972
+ }
1973
+ }
1974
+ async getSnapshotBlobs() {
1975
+ var _a;
1976
+ if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) ||
1977
+ this.attachState !== AttachState.Attached || this.context.pendingLocalState) {
1978
+ return;
1979
+ }
1980
+ assert(!!this.context.baseSnapshot, 0x2e5 /* "Must have a base snapshot" */);
1981
+ this.baseSnapshotBlobs = await SerializedSnapshotStorage.serializeTree(this.context.baseSnapshot, this.storage);
1982
+ }
1880
1983
  getPendingLocalState() {
1881
- return this.pendingStateManager.getLocalState();
1984
+ var _a;
1985
+ if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad)) {
1986
+ throw new UsageError("can't get state when offline load disabled");
1987
+ }
1988
+ const previousPendingState = this.context.pendingLocalState;
1989
+ if (previousPendingState) {
1990
+ return {
1991
+ pending: this.pendingStateManager.getLocalState(),
1992
+ snapshotBlobs: previousPendingState.snapshotBlobs,
1993
+ baseSnapshot: previousPendingState.baseSnapshot,
1994
+ savedOps: this.savedOps,
1995
+ };
1996
+ }
1997
+ assert(!!this.context.baseSnapshot, 0x2e6 /* "Must have a base snapshot" */);
1998
+ assert(!!this.baseSnapshotBlobs, 0x2e7 /* "Must serialize base snapshot blobs before getting runtime state" */);
1999
+ return {
2000
+ pending: this.pendingStateManager.getLocalState(),
2001
+ snapshotBlobs: this.baseSnapshotBlobs,
2002
+ baseSnapshot: this.context.baseSnapshot,
2003
+ savedOps: this.savedOps,
2004
+ };
1882
2005
  }
1883
2006
  /**
1884
2007
  * * Forms a function that will request a Summarizer.
@@ -1906,6 +2029,15 @@ export class ContainerRuntime extends TypedEventEmitter {
1906
2029
  return summarizer;
1907
2030
  };
1908
2031
  }
2032
+ async processSavedOps(state) {
2033
+ for (const op of state.savedOps) {
2034
+ this.process(op, false);
2035
+ await this.pendingStateManager.applyStashedOpsAt(op.sequenceNumber);
2036
+ }
2037
+ // we may not have seen every sequence number (because of system ops) so apply everything once we
2038
+ // don't have any more saved ops
2039
+ await this.pendingStateManager.applyStashedOpsAt();
2040
+ }
1909
2041
  }
1910
2042
  /**
1911
2043
  * Wait for a specific sequence number. Promise should resolve when we reach that number,