@fluidframework/container-runtime 0.59.4001 → 1.1.0-75972

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/.eslintrc.js +1 -1
  2. package/dist/blobManager.d.ts +2 -2
  3. package/dist/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager.js +12 -11
  5. package/dist/blobManager.js.map +1 -1
  6. package/dist/connectionTelemetry.d.ts +19 -0
  7. package/dist/connectionTelemetry.d.ts.map +1 -1
  8. package/dist/connectionTelemetry.js +23 -23
  9. package/dist/connectionTelemetry.js.map +1 -1
  10. package/dist/containerRuntime.d.ts +137 -29
  11. package/dist/containerRuntime.d.ts.map +1 -1
  12. package/dist/containerRuntime.js +338 -118
  13. package/dist/containerRuntime.js.map +1 -1
  14. package/dist/dataStore.d.ts.map +1 -1
  15. package/dist/dataStore.js +14 -3
  16. package/dist/dataStore.js.map +1 -1
  17. package/dist/dataStoreContext.d.ts +4 -2
  18. package/dist/dataStoreContext.d.ts.map +1 -1
  19. package/dist/dataStoreContext.js +16 -5
  20. package/dist/dataStoreContext.js.map +1 -1
  21. package/dist/dataStoreRegistry.d.ts +0 -4
  22. package/dist/dataStoreRegistry.d.ts.map +1 -1
  23. package/dist/dataStoreRegistry.js +12 -1
  24. package/dist/dataStoreRegistry.js.map +1 -1
  25. package/dist/dataStores.d.ts +4 -3
  26. package/dist/dataStores.d.ts.map +1 -1
  27. package/dist/dataStores.js +13 -7
  28. package/dist/dataStores.js.map +1 -1
  29. package/dist/garbageCollection.d.ts +23 -27
  30. package/dist/garbageCollection.d.ts.map +1 -1
  31. package/dist/garbageCollection.js +44 -119
  32. package/dist/garbageCollection.js.map +1 -1
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +2 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/orderedClientElection.js +0 -4
  38. package/dist/orderedClientElection.js.map +1 -1
  39. package/dist/packageVersion.d.ts +1 -1
  40. package/dist/packageVersion.d.ts.map +1 -1
  41. package/dist/packageVersion.js +1 -1
  42. package/dist/packageVersion.js.map +1 -1
  43. package/dist/pendingStateManager.d.ts +30 -29
  44. package/dist/pendingStateManager.d.ts.map +1 -1
  45. package/dist/pendingStateManager.js +72 -109
  46. package/dist/pendingStateManager.js.map +1 -1
  47. package/dist/runningSummarizer.d.ts +4 -3
  48. package/dist/runningSummarizer.d.ts.map +1 -1
  49. package/dist/runningSummarizer.js +11 -6
  50. package/dist/runningSummarizer.js.map +1 -1
  51. package/dist/serializedSnapshotStorage.d.ts +58 -0
  52. package/dist/serializedSnapshotStorage.d.ts.map +1 -0
  53. package/dist/serializedSnapshotStorage.js +108 -0
  54. package/dist/serializedSnapshotStorage.js.map +1 -0
  55. package/dist/summarizer.d.ts +11 -4
  56. package/dist/summarizer.d.ts.map +1 -1
  57. package/dist/summarizer.js +18 -9
  58. package/dist/summarizer.js.map +1 -1
  59. package/dist/summarizerHeuristics.d.ts +5 -3
  60. package/dist/summarizerHeuristics.d.ts.map +1 -1
  61. package/dist/summarizerHeuristics.js +10 -3
  62. package/dist/summarizerHeuristics.js.map +1 -1
  63. package/dist/summarizerTypes.d.ts +4 -2
  64. package/dist/summarizerTypes.d.ts.map +1 -1
  65. package/dist/summarizerTypes.js.map +1 -1
  66. package/dist/summaryManager.d.ts +3 -3
  67. package/dist/summaryManager.d.ts.map +1 -1
  68. package/dist/summaryManager.js +7 -7
  69. package/dist/summaryManager.js.map +1 -1
  70. package/garbageCollection.md +9 -1
  71. package/lib/blobManager.d.ts +2 -2
  72. package/lib/blobManager.d.ts.map +1 -1
  73. package/lib/blobManager.js +12 -11
  74. package/lib/blobManager.js.map +1 -1
  75. package/lib/connectionTelemetry.d.ts +19 -0
  76. package/lib/connectionTelemetry.d.ts.map +1 -1
  77. package/lib/connectionTelemetry.js +23 -23
  78. package/lib/connectionTelemetry.js.map +1 -1
  79. package/lib/containerRuntime.d.ts +137 -29
  80. package/lib/containerRuntime.d.ts.map +1 -1
  81. package/lib/containerRuntime.js +341 -121
  82. package/lib/containerRuntime.js.map +1 -1
  83. package/lib/dataStore.d.ts.map +1 -1
  84. package/lib/dataStore.js +15 -4
  85. package/lib/dataStore.js.map +1 -1
  86. package/lib/dataStoreContext.d.ts +4 -2
  87. package/lib/dataStoreContext.d.ts.map +1 -1
  88. package/lib/dataStoreContext.js +16 -5
  89. package/lib/dataStoreContext.js.map +1 -1
  90. package/lib/dataStoreRegistry.d.ts +0 -4
  91. package/lib/dataStoreRegistry.d.ts.map +1 -1
  92. package/lib/dataStoreRegistry.js +12 -1
  93. package/lib/dataStoreRegistry.js.map +1 -1
  94. package/lib/dataStores.d.ts +4 -3
  95. package/lib/dataStores.d.ts.map +1 -1
  96. package/lib/dataStores.js +14 -8
  97. package/lib/dataStores.js.map +1 -1
  98. package/lib/garbageCollection.d.ts +23 -27
  99. package/lib/garbageCollection.d.ts.map +1 -1
  100. package/lib/garbageCollection.js +43 -117
  101. package/lib/garbageCollection.js.map +1 -1
  102. package/lib/index.d.ts +2 -2
  103. package/lib/index.d.ts.map +1 -1
  104. package/lib/index.js +1 -1
  105. package/lib/index.js.map +1 -1
  106. package/lib/orderedClientElection.js +0 -4
  107. package/lib/orderedClientElection.js.map +1 -1
  108. package/lib/packageVersion.d.ts +1 -1
  109. package/lib/packageVersion.d.ts.map +1 -1
  110. package/lib/packageVersion.js +1 -1
  111. package/lib/packageVersion.js.map +1 -1
  112. package/lib/pendingStateManager.d.ts +30 -29
  113. package/lib/pendingStateManager.d.ts.map +1 -1
  114. package/lib/pendingStateManager.js +72 -109
  115. package/lib/pendingStateManager.js.map +1 -1
  116. package/lib/runningSummarizer.d.ts +4 -3
  117. package/lib/runningSummarizer.d.ts.map +1 -1
  118. package/lib/runningSummarizer.js +11 -6
  119. package/lib/runningSummarizer.js.map +1 -1
  120. package/lib/serializedSnapshotStorage.d.ts +58 -0
  121. package/lib/serializedSnapshotStorage.d.ts.map +1 -0
  122. package/lib/serializedSnapshotStorage.js +104 -0
  123. package/lib/serializedSnapshotStorage.js.map +1 -0
  124. package/lib/summarizer.d.ts +11 -4
  125. package/lib/summarizer.d.ts.map +1 -1
  126. package/lib/summarizer.js +18 -9
  127. package/lib/summarizer.js.map +1 -1
  128. package/lib/summarizerHeuristics.d.ts +5 -3
  129. package/lib/summarizerHeuristics.d.ts.map +1 -1
  130. package/lib/summarizerHeuristics.js +10 -3
  131. package/lib/summarizerHeuristics.js.map +1 -1
  132. package/lib/summarizerTypes.d.ts +4 -2
  133. package/lib/summarizerTypes.d.ts.map +1 -1
  134. package/lib/summarizerTypes.js.map +1 -1
  135. package/lib/summaryManager.d.ts +3 -3
  136. package/lib/summaryManager.d.ts.map +1 -1
  137. package/lib/summaryManager.js +7 -7
  138. package/lib/summaryManager.js.map +1 -1
  139. package/package.json +19 -32
  140. package/src/blobManager.ts +29 -15
  141. package/src/connectionTelemetry.ts +60 -39
  142. package/src/containerRuntime.ts +502 -156
  143. package/src/dataStore.ts +21 -4
  144. package/src/dataStoreContext.ts +27 -5
  145. package/src/dataStoreRegistry.ts +8 -1
  146. package/src/dataStores.ts +21 -8
  147. package/src/garbageCollection.ts +81 -166
  148. package/src/index.ts +7 -1
  149. package/src/orderedClientElection.ts +1 -1
  150. package/src/packageVersion.ts +1 -1
  151. package/src/pendingStateManager.ts +104 -123
  152. package/src/runningSummarizer.ts +20 -10
  153. package/src/serializedSnapshotStorage.ts +146 -0
  154. package/src/summarizer.ts +20 -16
  155. package/src/summarizerHeuristics.ts +21 -5
  156. package/src/summarizerTypes.ts +4 -2
  157. package/src/summaryManager.ts +5 -6
@@ -1,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
- import { DataCorruptionError, GenericError, UsageError, extractSafePropertiesFromMessage, } from "@fluidframework/container-utils";
6
+ import { DataCorruptionError, DataProcessingError, 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";
@@ -14,7 +14,7 @@ import { FluidDataStoreRegistry } from "./dataStoreRegistry";
14
14
  import { Summarizer } from "./summarizer";
15
15
  import { SummaryManager } from "./summaryManager";
16
16
  import { DeltaScheduler } from "./deltaScheduler";
17
- import { ReportOpPerfTelemetry, latencyThreshold } from "./connectionTelemetry";
17
+ import { ReportOpPerfTelemetry, latencyThreshold, } from "./connectionTelemetry";
18
18
  import { PendingStateManager } from "./pendingStateManager";
19
19
  import { pkgVersion } from "./packageVersion";
20
20
  import { BlobManager } from "./blobManager";
@@ -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.
@@ -203,7 +203,7 @@ class ScheduleManagerCore {
203
203
  }
204
204
  resumeQueue(startBatch, messageEndBatch) {
205
205
  const endBatch = messageEndBatch.sequenceNumber;
206
- const duration = performance.now() - this.timePaused;
206
+ const duration = this.localPaused ? (performance.now() - this.timePaused) : undefined;
207
207
  this.batchCount++;
208
208
  if (this.batchCount % 1000 === 1) {
209
209
  this.logger.sendTelemetryEvent({
@@ -222,7 +222,7 @@ class ScheduleManagerCore {
222
222
  }
223
223
  this.localPaused = false;
224
224
  // Random round number - we want to know when batch waiting paused op processing.
225
- if (duration > latencyThreshold) {
225
+ if (duration !== undefined && duration > latencyThreshold) {
226
226
  this.logger.sendErrorEvent({
227
227
  eventName: "MaxBatchWaitTimeExceeded",
228
228
  duration,
@@ -372,43 +372,32 @@ 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
- });
395
+ this.defaultTelemetrySignalSampleCount = 100;
396
+ this._perfSignalData = {
397
+ signalsLost: 0,
398
+ signalSequenceNumber: 0,
399
+ signalTimestamp: 0,
400
+ trackingSignalSequenceNumber: undefined,
412
401
  };
413
402
  this.summarizeOnDemand = (...args) => {
414
403
  if (this.clientDetails.type === summarizerClientType) {
@@ -440,27 +429,45 @@ export class ContainerRuntime extends TypedEventEmitter {
440
429
  };
441
430
  this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
442
431
  // Default to false (enabled).
443
- 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;
444
433
  this._connected = this.context.connected;
445
434
  this.chunkMap = new Map(chunks);
446
435
  this.handleContext = new ContainerFluidHandleContext("", this);
447
436
  this.mc = loggerToMonitoringContext(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();
448
442
  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);
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);
452
446
  this.maxConsecutiveReconnects =
453
- (_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;
454
448
  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);
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 = GarbageCollector.create({
452
+ runtime: this,
453
+ gcOptions: this.runtimeOptions.gcOptions,
454
+ baseSnapshot,
455
+ baseLogger: this.mc.logger,
456
+ existing,
457
+ metadata,
458
+ isSummarizerClient: this.context.clientDetails.type === summarizerClientType,
459
+ getNodePackagePath: (nodePath) => this.getGCNodePackagePath(nodePath),
460
+ getLastSummaryTimestampMs: () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; },
461
+ readAndParseBlob: async (id) => readAndParse(this.storage, id),
462
+ });
456
463
  const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
457
464
  this.summarizerNode = createRootSummarizerNodeWithGC(ChildLogger.create(this.logger, "SummarizerNode"),
458
465
  // Summarize function to call when summarize is called. Summarizer node always tracks summary state.
459
- async (fullTree, trackState) => this.summarizeInternal(fullTree, trackState),
466
+ async (fullTree, trackState, telemetryContext) => this.summarizeInternal(fullTree, trackState, telemetryContext),
460
467
  // Latest change sequence number, no changes since summary applied yet
461
468
  loadedFromSequenceNumber,
462
469
  // Summary reference sequence number, undefined if no summary yet
463
- context.baseSnapshot ? loadedFromSequenceNumber : undefined, {
470
+ baseSnapshot ? loadedFromSequenceNumber : undefined, {
464
471
  // Must set to false to prevent sending summary handle which would be pointing to
465
472
  // a summary with an older protocol state.
466
473
  canReuseHandle: false,
@@ -470,26 +477,31 @@ export class ContainerRuntime extends TypedEventEmitter {
470
477
  // If GC should not run, let the summarizer node know so that it does not track GC state.
471
478
  gcDisabled: !this.garbageCollector.shouldRunGC,
472
479
  });
473
- if (this.context.baseSnapshot) {
474
- this.summarizerNode.loadBaseSummaryWithoutDifferential(this.context.baseSnapshot);
480
+ if (baseSnapshot) {
481
+ this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
475
482
  }
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);
483
+ 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
484
  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
485
  this.scheduleManager = new ScheduleManager(context.deltaManager, this, ChildLogger.create(this.logger, "ScheduleManager"));
479
486
  this.deltaSender = this.deltaManager;
480
- this.pendingStateManager = new PendingStateManager(this, async (type, content) => this.applyStashedOp(type, content), this._flushMode, context.pendingLocalState);
487
+ this.pendingStateManager = new PendingStateManager({
488
+ applyStashedOp: this.applyStashedOp.bind(this),
489
+ clientId: () => this.clientId,
490
+ close: this.closeFn,
491
+ connected: () => this.connected,
492
+ flush: this.flush.bind(this),
493
+ flushMode: () => this.flushMode,
494
+ reSubmit: this.reSubmit.bind(this),
495
+ rollback: this.rollback.bind(this),
496
+ setFlushMode: (mode) => this.setFlushMode(mode),
497
+ }, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
481
498
  this.context.quorum.on("removeMember", (clientId) => {
482
499
  this.clearPartialChunks(clientId);
483
500
  });
484
501
  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;
502
+ this.dirtyContainer = this.context.attachState !== AttachState.Attached
503
+ || this.pendingStateManager.hasPendingMessages();
488
504
  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
505
  if (this.summariesDisabled) {
494
506
  this.mc.logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
495
507
  }
@@ -497,9 +509,7 @@ export class ContainerRuntime extends TypedEventEmitter {
497
509
  const orderedClientLogger = ChildLogger.create(this.logger, "OrderedClientElection");
498
510
  const orderedClientCollection = new OrderedClientCollection(orderedClientLogger, this.context.deltaManager, this.context.quorum);
499
511
  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);
512
+ this.summarizerClientElection = new SummarizerClientElection(orderedClientLogger, this.summaryCollection, orderedClientElectionForSummarizer, this.maxOpsSinceLastSummary, this.summarizerClientElectionEnabled);
503
513
  if (this.context.clientDetails.type === summarizerClientType) {
504
514
  this._summarizer = new Summarizer("/_summarizer", this /* ISummarizerRuntime */, () => this.summaryConfiguration, this /* ISummarizerInternalsProvider */, this.handleContext, this.summaryCollection, async (runtime) => RunWhileConnectedCoordinator.create(runtime));
505
515
  }
@@ -507,7 +517,7 @@ export class ContainerRuntime extends TypedEventEmitter {
507
517
  // Only create a SummaryManager and SummarizerClientElection
508
518
  // if summaries are enabled and we are not the summarizer client.
509
519
  const defaultAction = () => {
510
- if (this.summaryCollection.opsSinceLastAck > maxOpsSinceLastSummary) {
520
+ if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
511
521
  this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
512
522
  // unregister default to no log on every op after falling behind
513
523
  // and register summary ack handler to re-register this handler
@@ -528,8 +538,8 @@ export class ContainerRuntime extends TypedEventEmitter {
528
538
  30 * 1000, // 30 sec max delay
529
539
  // throttling function increases exponentially (0ms, 40ms, 80ms, 160ms, etc)
530
540
  formExponentialFn({ coefficient: 20, initialDelay: 0 })), {
531
- initialDelayMs: this.runtimeOptions.summaryOptions.initialSummarizerDelayMs,
532
- }, this.runtimeOptions.summaryOptions.summarizerOptions);
541
+ initialDelayMs: this.initialSummarizerDelayMs,
542
+ }, this.heuristicsDisabled);
533
543
  this.summaryManager.start();
534
544
  }
535
545
  }
@@ -552,9 +562,6 @@ export class ContainerRuntime extends TypedEventEmitter {
552
562
  assert(!readonly || !this.connected, 0x125 /* "Unsafe to transition to read-only state!" */);
553
563
  this.replayPendingStates();
554
564
  });
555
- if (context.pendingLocalState !== undefined) {
556
- this.deltaManager.on("op", this.onOp);
557
- }
558
565
  // logging hardware telemetry
559
566
  logger.sendTelemetryEvent(Object.assign({ eventName: "DeviceSpec" }, getDeviceSpec()));
560
567
  let loadSummaryNumber;
@@ -567,7 +574,7 @@ export class ContainerRuntime extends TypedEventEmitter {
567
574
  };
568
575
  // back-compat 0.59.3000 - Older document may either write summaryCount or not write it at all. If it does
569
576
  // 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;
577
+ 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
578
  }
572
579
  else {
573
580
  this.createContainerMetadata = {
@@ -603,13 +610,16 @@ export class ContainerRuntime extends TypedEventEmitter {
603
610
  runtimeVersion: pkgVersion,
604
611
  },
605
612
  });
606
- const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", useDataStoreAliasing = false, flushMode = defaultFlushMode, } = runtimeOptions;
607
- const storage = context.storage;
613
+ const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", useDataStoreAliasing = false, flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
614
+ const pendingRuntimeState = context.pendingLocalState;
615
+ const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
616
+ const storage = !pendingRuntimeState ?
617
+ context.storage :
618
+ new SerializedSnapshotStorage(() => { return context.storage; }, pendingRuntimeState.snapshotBlobs);
608
619
  const registry = new FluidDataStoreRegistry(registryEntries);
609
620
  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) {
621
+ const blobId = baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.blobs[blobName];
622
+ if (baseSnapshot && blobId) {
613
623
  // IContainerContext storage api return type still has undefined in 0.39 package version.
614
624
  // So once we release 0.40 container-defn package we can remove this check.
615
625
  assert(storage !== undefined, 0x1f5 /* "Attached state should have storage" */);
@@ -624,7 +634,7 @@ export class ContainerRuntime extends TypedEventEmitter {
624
634
  ]);
625
635
  const loadExisting = existing === true || context.existing === true;
626
636
  // 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) => {
637
+ const blobManagerSnapshot = await BlobManager.load(baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.trees[blobsTreeName], async (id) => {
628
638
  // IContainerContext storage api return type still has undefined in 0.39 package version.
629
639
  // So once we release 0.40 container-defn package we can remove this check.
630
640
  assert(storage !== undefined, 0x256 /* "storage undefined in attached container" */);
@@ -632,7 +642,8 @@ export class ContainerRuntime extends TypedEventEmitter {
632
642
  });
633
643
  // Verify summary runtime sequence number matches protocol sequence number.
634
644
  const runtimeSequenceNumber = (_c = metadata === null || metadata === void 0 ? void 0 : metadata.message) === null || _c === void 0 ? void 0 : _c.sequenceNumber;
635
- if (runtimeSequenceNumber !== undefined) {
645
+ // When we load with pending state, we reuse an old snapshot so we don't expect these numbers to match
646
+ if (!pendingRuntimeState && runtimeSequenceNumber !== undefined) {
636
647
  const protocolSequenceNumber = context.deltaManager.initialSequenceNumber;
637
648
  // Unless bypass is explicitly set, then take action when sequence numbers mismatch.
638
649
  if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
@@ -654,7 +665,14 @@ export class ContainerRuntime extends TypedEventEmitter {
654
665
  loadSequenceNumberVerification,
655
666
  useDataStoreAliasing,
656
667
  flushMode,
657
- }, containerScope, logger, loadExisting, blobManagerSnapshot, requestHandler);
668
+ enableOfflineLoad,
669
+ }, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
670
+ if (pendingRuntimeState) {
671
+ await runtime.processSavedOps(pendingRuntimeState);
672
+ // delete these once runtime has seen them to save space
673
+ pendingRuntimeState.savedOps = [];
674
+ }
675
+ await runtime.getSnapshotBlobs();
658
676
  return runtime;
659
677
  }
660
678
  get options() {
@@ -670,7 +688,7 @@ export class ContainerRuntime extends TypedEventEmitter {
670
688
  return this.context.deltaManager;
671
689
  }
672
690
  get storage() {
673
- return this.context.storage;
691
+ return this._storage;
674
692
  }
675
693
  get reSubmitFn() {
676
694
  // eslint-disable-next-line @typescript-eslint/unbound-method
@@ -702,19 +720,70 @@ export class ContainerRuntime extends TypedEventEmitter {
702
720
  var _a;
703
721
  return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
704
722
  }
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
723
  get disposed() { return this._disposed; }
710
724
  get summarizer() {
711
725
  assert(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
712
726
  return this._summarizer;
713
727
  }
714
- get summariesDisabled() {
728
+ isSummariesDisabled() {
729
+ // back-compat: disableSummaries was moved from ISummaryRuntimeOptions
730
+ // to ISummaryConfiguration in 0.60.
731
+ if (this.runtimeOptions.summaryOptions.disableSummaries === true) {
732
+ return true;
733
+ }
734
+ return this.summaryConfiguration.state === "disabled";
735
+ }
736
+ isHeuristicsDisabled() {
715
737
  var _a;
716
- return this.runtimeOptions.summaryOptions.disableSummaries === true ||
717
- ((_a = this.runtimeOptions.summaryOptions.summaryConfigOverrides) === null || _a === void 0 ? void 0 : _a.disableSummaries) === true;
738
+ // back-compat: disableHeuristics was moved from ISummarizerOptions
739
+ // to ISummaryConfiguration in 0.60.
740
+ if (((_a = this.runtimeOptions.summaryOptions.summarizerOptions) === null || _a === void 0 ? void 0 : _a.disableHeuristics) === true) {
741
+ return true;
742
+ }
743
+ return this.summaryConfiguration.state === "disableHeuristics";
744
+ }
745
+ isSummarizerClientElectionEnabled() {
746
+ var _a;
747
+ if (this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) {
748
+ return (_a = this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) !== null && _a !== void 0 ? _a : true;
749
+ }
750
+ // back-compat: summarizerClientElection was moved from ISummaryRuntimeOptions
751
+ // to ISummaryConfiguration in 0.60.
752
+ if (this.runtimeOptions.summaryOptions.summarizerClientElection === true) {
753
+ return true;
754
+ }
755
+ if (this.summaryConfiguration.state !== "disabled") {
756
+ return this.summaryConfiguration.summarizerClientElection === true;
757
+ }
758
+ else {
759
+ return false;
760
+ }
761
+ }
762
+ getMaxOpsSinceLastSummary() {
763
+ // back-compat: maxOpsSinceLastSummary was moved from ISummaryRuntimeOptions
764
+ // to ISummaryConfiguration in 0.60.
765
+ if (this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary !== undefined) {
766
+ return this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary;
767
+ }
768
+ if (this.summaryConfiguration.state !== "disabled") {
769
+ return this.summaryConfiguration.maxOpsSinceLastSummary;
770
+ }
771
+ else {
772
+ return 0;
773
+ }
774
+ }
775
+ getInitialSummarizerDelayMs() {
776
+ // back-compat: initialSummarizerDelayMs was moved from ISummaryRuntimeOptions
777
+ // to ISummaryConfiguration in 0.60.
778
+ if (this.runtimeOptions.summaryOptions.initialSummarizerDelayMs !== undefined) {
779
+ return this.runtimeOptions.summaryOptions.initialSummarizerDelayMs;
780
+ }
781
+ if (this.summaryConfiguration.state !== "disabled") {
782
+ return this.summaryConfiguration.initialSummarizerDelayMs;
783
+ }
784
+ else {
785
+ return 0;
786
+ }
718
787
  }
719
788
  dispose(error) {
720
789
  var _a;
@@ -813,12 +882,17 @@ export class ContainerRuntime extends TypedEventEmitter {
813
882
  return exceptionToResponse(error);
814
883
  }
815
884
  }
885
+ internalId(maybeAlias) {
886
+ var _a;
887
+ return (_a = this.dataStores.aliases().get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
888
+ }
816
889
  async getDataStoreFromRequest(id, request) {
817
890
  var _a, _b, _c;
818
891
  const wait = typeof ((_a = request.headers) === null || _a === void 0 ? void 0 : _a[RuntimeHeaders.wait]) === "boolean"
819
892
  ? (_b = request.headers) === null || _b === void 0 ? void 0 : _b[RuntimeHeaders.wait]
820
893
  : true;
821
- const dataStoreContext = await this.dataStores.getDataStore(id, wait);
894
+ const internalId = this.internalId(id);
895
+ const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
822
896
  /**
823
897
  * If GC should run and this an external app request with "externalRequest" header, we need to return
824
898
  * an error if the data store being requested is marked as unreferenced as per the data store's base
@@ -855,7 +929,7 @@ export class ContainerRuntime extends TypedEventEmitter {
855
929
  message: (_a = extractSummaryMetadataMessage(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.messageAtLastSummary });
856
930
  addBlobToSummary(summaryTree, metadataBlobName, JSON.stringify(metadata));
857
931
  }
858
- addContainerStateToSummary(summaryTree, fullTree, trackState) {
932
+ addContainerStateToSummary(summaryTree, fullTree, trackState, telemetryContext) {
859
933
  var _a;
860
934
  this.addMetadataToSummary(summaryTree);
861
935
  if (this.chunkMap.size > 0) {
@@ -877,7 +951,7 @@ export class ContainerRuntime extends TypedEventEmitter {
877
951
  addTreeToSummary(summaryTree, blobsTreeName, blobManagerSummary);
878
952
  }
879
953
  if (this.garbageCollector.writeDataAtRoot) {
880
- const gcSummary = this.garbageCollector.summarize(fullTree, trackState);
954
+ const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
881
955
  if (gcSummary !== undefined) {
882
956
  addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
883
957
  }
@@ -898,7 +972,6 @@ export class ContainerRuntime extends TypedEventEmitter {
898
972
  this.resetReconnectCount();
899
973
  return true;
900
974
  }
901
- this.consecutiveReconnects++;
902
975
  if (this.consecutiveReconnects === Math.floor(this.maxConsecutiveReconnects / 2)) {
903
976
  // If we're halfway through the max reconnects, send an event in order
904
977
  // to better identify false positives, if any. If the rate of this event
@@ -907,6 +980,7 @@ export class ContainerRuntime extends TypedEventEmitter {
907
980
  this.mc.logger.sendTelemetryEvent({
908
981
  eventName: "ReconnectsWithNoProgress",
909
982
  attempts: this.consecutiveReconnects,
983
+ pendingMessages: this.pendingStateManager.pendingMessagesCount,
910
984
  });
911
985
  }
912
986
  return this.consecutiveReconnects < this.maxConsecutiveReconnects;
@@ -962,29 +1036,42 @@ export class ContainerRuntime extends TypedEventEmitter {
962
1036
  this.verifyNotClosed();
963
1037
  // There might be no change of state due to Container calling this API after loading runtime.
964
1038
  const changeOfState = this._connected !== connected;
1039
+ const reconnection = changeOfState && connected;
965
1040
  this._connected = connected;
966
- if (changeOfState) {
967
- this.deltaManager.off("op", this.onOp);
968
- this.context.pendingLocalState = undefined;
1041
+ if (!connected) {
1042
+ this._perfSignalData.signalsLost = 0;
1043
+ this._perfSignalData.signalTimestamp = 0;
1044
+ this._perfSignalData.trackingSignalSequenceNumber = undefined;
1045
+ }
1046
+ if (reconnection) {
1047
+ this.consecutiveReconnects++;
969
1048
  if (!this.shouldContinueReconnecting()) {
970
- this.closeFn(new GenericError(
1049
+ this.closeFn(
971
1050
  // pre-0.58 error message: MaxReconnectsWithNoProgress
972
- "Runtime detected too many reconnects with no progress syncing local ops", undefined, // error
973
- { attempts: this.consecutiveReconnects }));
1051
+ DataProcessingError.create("Runtime detected too many reconnects with no progress syncing local ops", "setConnectionState", undefined, {
1052
+ dataLoss: 1,
1053
+ attempts: this.consecutiveReconnects,
1054
+ pendingMessages: this.pendingStateManager.pendingMessagesCount,
1055
+ }));
974
1056
  return;
975
1057
  }
1058
+ }
1059
+ if (changeOfState) {
976
1060
  this.replayPendingStates();
977
1061
  }
978
1062
  this.dataStores.setConnectionState(connected, clientId);
979
1063
  raiseConnectedEvent(this.mc.logger, this, connected, clientId);
980
1064
  }
981
1065
  process(messageArg, local) {
982
- var _a;
1066
+ var _a, _b;
983
1067
  this.verifyNotClosed();
984
1068
  // If it's not message for runtime, bail out right away.
985
1069
  if (!isRuntimeMessage(messageArg)) {
986
1070
  return;
987
1071
  }
1072
+ if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
1073
+ this.savedOps.push(messageArg);
1074
+ }
988
1075
  // Do shallow copy of message, as methods below will modify it.
989
1076
  // There might be multiple container instances receiving same message
990
1077
  // We do not need to make deep copy, as each layer will just replace message.content itself,
@@ -999,8 +1086,14 @@ export class ContainerRuntime extends TypedEventEmitter {
999
1086
  // Chunk processing must come first given that we will transform the message to the unchunked version
1000
1087
  // once all pieces are available
1001
1088
  message = this.processRemoteChunkedMessage(message);
1002
- // Call the PendingStateManager to process messages.
1003
- const { localAck, localOpMetadata } = this.pendingStateManager.processMessage(message, local);
1089
+ let localOpMetadata;
1090
+ if (local) {
1091
+ // Call the PendingStateManager to process local messages.
1092
+ // Do not process local chunked ops until all pieces are available.
1093
+ if (message.type !== ContainerMessageType.ChunkedOp) {
1094
+ localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
1095
+ }
1096
+ }
1004
1097
  // If there are no more pending messages after processing a local message,
1005
1098
  // the document is no longer dirty.
1006
1099
  if (!this.pendingStateManager.hasPendingMessages()) {
@@ -1008,17 +1101,16 @@ export class ContainerRuntime extends TypedEventEmitter {
1008
1101
  }
1009
1102
  switch (message.type) {
1010
1103
  case ContainerMessageType.Attach:
1011
- this.dataStores.processAttachMessage(message, local || localAck);
1104
+ this.dataStores.processAttachMessage(message, local);
1012
1105
  break;
1013
1106
  case ContainerMessageType.Alias:
1014
1107
  this.processAliasMessage(message, localOpMetadata, local);
1015
1108
  break;
1016
1109
  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);
1110
+ this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
1019
1111
  break;
1020
1112
  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" */);
1113
+ 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
1114
  this.blobManager.processBlobAttachOp(message.metadata.blobId, local);
1023
1115
  break;
1024
1116
  default:
@@ -1040,6 +1132,20 @@ export class ContainerRuntime extends TypedEventEmitter {
1040
1132
  processAliasMessage(message, localOpMetadata, local) {
1041
1133
  this.dataStores.processAliasMessage(message, localOpMetadata, local);
1042
1134
  }
1135
+ /**
1136
+ * Emits the Signal event and update the perf signal data.
1137
+ * @param clientSignalSequenceNumber - is the client signal sequence number to be uploaded.
1138
+ */
1139
+ sendSignalTelemetryEvent(clientSignalSequenceNumber) {
1140
+ const duration = Date.now() - this._perfSignalData.signalTimestamp;
1141
+ this.logger.sendPerformanceEvent({
1142
+ eventName: "SignalLatency",
1143
+ duration,
1144
+ signalsLost: this._perfSignalData.signalsLost,
1145
+ });
1146
+ this._perfSignalData.signalsLost = 0;
1147
+ this._perfSignalData.signalTimestamp = 0;
1148
+ }
1043
1149
  processSignal(message, local) {
1044
1150
  const envelope = message.content;
1045
1151
  const transformed = {
@@ -1047,6 +1153,26 @@ export class ContainerRuntime extends TypedEventEmitter {
1047
1153
  content: envelope.contents.content,
1048
1154
  type: envelope.contents.type,
1049
1155
  };
1156
+ // Only collect signal telemetry for messages sent by the current client.
1157
+ if (message.clientId === this.clientId && this.connected) {
1158
+ // Check to see if the signal was lost.
1159
+ if (this._perfSignalData.trackingSignalSequenceNumber !== undefined &&
1160
+ envelope.clientSignalSequenceNumber > this._perfSignalData.trackingSignalSequenceNumber) {
1161
+ this._perfSignalData.signalsLost++;
1162
+ this._perfSignalData.trackingSignalSequenceNumber = undefined;
1163
+ this.logger.sendErrorEvent({
1164
+ eventName: "SignalLost",
1165
+ type: envelope.contents.type,
1166
+ signalsLost: this._perfSignalData.signalsLost,
1167
+ trackingSequenceNumber: this._perfSignalData.trackingSignalSequenceNumber,
1168
+ clientSignalSequenceNumber: envelope.clientSignalSequenceNumber,
1169
+ });
1170
+ }
1171
+ else if (envelope.clientSignalSequenceNumber === this._perfSignalData.trackingSignalSequenceNumber) {
1172
+ this.sendSignalTelemetryEvent(envelope.clientSignalSequenceNumber);
1173
+ this._perfSignalData.trackingSignalSequenceNumber = undefined;
1174
+ }
1175
+ }
1050
1176
  if (envelope.address === undefined) {
1051
1177
  // No address indicates a container signal message.
1052
1178
  this.emit("signal", transformed, local);
@@ -1055,7 +1181,8 @@ export class ContainerRuntime extends TypedEventEmitter {
1055
1181
  this.dataStores.processSignal(envelope.address, transformed, local);
1056
1182
  }
1057
1183
  async getRootDataStore(id, wait = true) {
1058
- const context = await this.dataStores.getDataStore(id, wait);
1184
+ const internalId = this.internalId(id);
1185
+ const context = await this.dataStores.getDataStore(internalId, wait);
1059
1186
  assert(await context.isRoot(), 0x12b /* "did not get root data store" */);
1060
1187
  return context.realize();
1061
1188
  }
@@ -1110,18 +1237,32 @@ export class ContainerRuntime extends TypedEventEmitter {
1110
1237
  }
1111
1238
  const savedFlushMode = this.flushMode;
1112
1239
  this.setFlushMode(FlushMode.TurnBased);
1113
- this.trackOrderSequentiallyCalls(callback);
1114
- this.flush();
1115
- this.setFlushMode(savedFlushMode);
1240
+ try {
1241
+ this.trackOrderSequentiallyCalls(callback);
1242
+ this.flush();
1243
+ }
1244
+ finally {
1245
+ this.setFlushMode(savedFlushMode);
1246
+ }
1116
1247
  }
1117
1248
  trackOrderSequentiallyCalls(callback) {
1249
+ let checkpoint;
1250
+ if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
1251
+ checkpoint = this.pendingStateManager.checkpoint();
1252
+ }
1118
1253
  try {
1119
1254
  this._orderSequentiallyCalls++;
1120
1255
  callback();
1121
1256
  }
1122
1257
  catch (error) {
1123
- // pre-0.58 error message: orderSequentiallyCallbackException
1124
- this.closeFn(new GenericError("orderSequentially callback exception", error));
1258
+ if (checkpoint) {
1259
+ // This will throw and close the container if rollback fails
1260
+ checkpoint.rollback();
1261
+ }
1262
+ else {
1263
+ // pre-0.58 error message: orderSequentiallyCallbackException
1264
+ this.closeFn(new GenericError("orderSequentially callback exception", error));
1265
+ }
1125
1266
  throw error; // throw the original error for the consumer of the runtime
1126
1267
  }
1127
1268
  finally {
@@ -1150,7 +1291,13 @@ export class ContainerRuntime extends TypedEventEmitter {
1150
1291
  }
1151
1292
  return fluidDataStore;
1152
1293
  }
1294
+ /**
1295
+ * @deprecated - will be removed in an upcoming release. See #9660.
1296
+ */
1153
1297
  async createRootDataStore(pkg, rootDataStoreId) {
1298
+ if (rootDataStoreId.includes("/")) {
1299
+ throw new UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
1300
+ }
1154
1301
  return this._aliasingEnabled === true ?
1155
1302
  this.createAndAliasDataStore(pkg, rootDataStoreId) :
1156
1303
  this.createRootDataStoreLegacy(pkg, rootDataStoreId);
@@ -1187,6 +1334,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1187
1334
  return aliasedDataStore;
1188
1335
  }
1189
1336
  createDetachedRootDataStore(pkg, rootDataStoreId) {
1337
+ if (rootDataStoreId.includes("/")) {
1338
+ throw new UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
1339
+ }
1190
1340
  return this.dataStores.createDetachedDataStoreCore(pkg, true, rootDataStoreId);
1191
1341
  }
1192
1342
  createDetachedDataStore(pkg) {
@@ -1260,6 +1410,21 @@ export class ContainerRuntime extends TypedEventEmitter {
1260
1410
  }
1261
1411
  return true;
1262
1412
  }
1413
+ createNewSignalEnvelope(address, type, content) {
1414
+ const newSequenceNumber = ++this._perfSignalData.signalSequenceNumber;
1415
+ const newEnvelope = {
1416
+ address,
1417
+ clientSignalSequenceNumber: newSequenceNumber,
1418
+ contents: { type, content },
1419
+ };
1420
+ // We should not track any signals in case we already have a tracking number.
1421
+ if (newSequenceNumber % this.defaultTelemetrySignalSampleCount === 1 &&
1422
+ this._perfSignalData.trackingSignalSequenceNumber === undefined) {
1423
+ this._perfSignalData.signalTimestamp = Date.now();
1424
+ this._perfSignalData.trackingSignalSequenceNumber = newSequenceNumber;
1425
+ }
1426
+ return newEnvelope;
1427
+ }
1263
1428
  /**
1264
1429
  * Submits the signal to be sent to other clients.
1265
1430
  * @param type - Type of the signal.
@@ -1267,11 +1432,11 @@ export class ContainerRuntime extends TypedEventEmitter {
1267
1432
  */
1268
1433
  submitSignal(type, content) {
1269
1434
  this.verifyNotClosed();
1270
- const envelope = { address: undefined, contents: { type, content } };
1435
+ const envelope = this.createNewSignalEnvelope(undefined /* address */, type, content);
1271
1436
  return this.context.submitSignalFn(envelope);
1272
1437
  }
1273
1438
  submitDataStoreSignal(address, type, content) {
1274
- const envelope = { address, contents: { type, content } };
1439
+ const envelope = this.createNewSignalEnvelope(address, type, content);
1275
1440
  return this.context.submitSignalFn(envelope);
1276
1441
  }
1277
1442
  setAttachState(attachState) {
@@ -1293,17 +1458,18 @@ export class ContainerRuntime extends TypedEventEmitter {
1293
1458
  * @param blobRedirectTable - A table passed during the attach process. While detached, blob upload is supported
1294
1459
  * using IDs generated locally. After attach, these IDs cannot be used, so this table maps the old local IDs to the
1295
1460
  * new storage IDs so requests can be redirected.
1461
+ * @param telemetryContext - summary data passed through the layers for telemetry purposes
1296
1462
  */
1297
- createSummary(blobRedirectTable) {
1463
+ createSummary(blobRedirectTable, telemetryContext) {
1298
1464
  if (blobRedirectTable) {
1299
1465
  this.blobManager.setRedirectTable(blobRedirectTable);
1300
1466
  }
1301
- const summarizeResult = this.dataStores.createSummary();
1467
+ const summarizeResult = this.dataStores.createSummary(telemetryContext);
1302
1468
  if (!this.disableIsolatedChannels) {
1303
1469
  // Wrap data store summaries in .channels subtree.
1304
1470
  wrapSummaryInChannelsTree(summarizeResult);
1305
1471
  }
1306
- this.addContainerStateToSummary(summarizeResult, true /* fullTree */, false /* trackState */);
1472
+ this.addContainerStateToSummary(summarizeResult, true /* fullTree */, false /* trackState */, telemetryContext);
1307
1473
  return summarizeResult.summary;
1308
1474
  }
1309
1475
  async getAbsoluteUrl(relativeUrl) {
@@ -1315,15 +1481,15 @@ export class ContainerRuntime extends TypedEventEmitter {
1315
1481
  }
1316
1482
  return this.context.getAbsoluteUrl(relativeUrl);
1317
1483
  }
1318
- async summarizeInternal(fullTree, trackState) {
1319
- const summarizeResult = await this.dataStores.summarize(fullTree, trackState);
1484
+ async summarizeInternal(fullTree, trackState, telemetryContext) {
1485
+ const summarizeResult = await this.dataStores.summarize(fullTree, trackState, telemetryContext);
1320
1486
  let pathPartsForChildren;
1321
1487
  if (!this.disableIsolatedChannels) {
1322
1488
  // Wrap data store summaries in .channels subtree.
1323
1489
  wrapSummaryInChannelsTree(summarizeResult);
1324
1490
  pathPartsForChildren = [channelsTreeName];
1325
1491
  }
1326
- this.addContainerStateToSummary(summarizeResult, fullTree, trackState);
1492
+ this.addContainerStateToSummary(summarizeResult, fullTree, trackState, telemetryContext);
1327
1493
  return Object.assign(Object.assign({}, summarizeResult), { id: "", pathPartsForChildren });
1328
1494
  }
1329
1495
  /**
@@ -1336,7 +1502,9 @@ export class ContainerRuntime extends TypedEventEmitter {
1336
1502
  if (runGC) {
1337
1503
  gcStats = await this.collectGarbage({ logger: summaryLogger, runSweep, fullGC });
1338
1504
  }
1339
- const { stats, summary } = await this.summarizerNode.summarize(fullTree, trackState);
1505
+ const telemetryContext = new TelemetryContext();
1506
+ const { stats, summary } = await this.summarizerNode.summarize(fullTree, trackState, telemetryContext);
1507
+ this.logger.sendTelemetryEvent({ eventName: "SummarizeTelemetry", details: telemetryContext.serialize() });
1340
1508
  assert(summary.type === SummaryType.Tree, 0x12f /* "Container Runtime's summarize should always return a tree" */);
1341
1509
  return { stats, summary, gcStats };
1342
1510
  }
@@ -1700,9 +1868,6 @@ export class ContainerRuntime extends TypedEventEmitter {
1700
1868
  }
1701
1869
  submit(type, content, localOpMetadata = undefined, opMetadata = undefined) {
1702
1870
  this.verifyNotClosed();
1703
- if (this.context.pendingLocalState !== undefined) {
1704
- this.closeFn(new GenericError("containerRuntimeSubmitWithPendingLocalState"));
1705
- }
1706
1871
  // There should be no ops in detached container state!
1707
1872
  assert(this.attachState !== AttachState.Detached, 0x132 /* "sending ops in detached container" */);
1708
1873
  let clientSequenceNumber = -1;
@@ -1833,6 +1998,17 @@ export class ContainerRuntime extends TypedEventEmitter {
1833
1998
  unreachableCase(type, `Unknown ContainerMessageType: ${type}`);
1834
1999
  }
1835
2000
  }
2001
+ rollback(type, content, localOpMetadata) {
2002
+ switch (type) {
2003
+ case ContainerMessageType.FluidDataStoreOp:
2004
+ // For operations, call rollbackDataStoreOp which will find the right store
2005
+ // and trigger rollback on it.
2006
+ this.dataStores.rollbackDataStoreOp(content, localOpMetadata);
2007
+ break;
2008
+ default:
2009
+ throw new Error(`Can't rollback ${type}`);
2010
+ }
2011
+ }
1836
2012
  /** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
1837
2013
  async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
1838
2014
  const readAndParseBlob = async (id) => readAndParse(this.storage, id);
@@ -1877,8 +2053,43 @@ export class ContainerRuntime extends TypedEventEmitter {
1877
2053
  return maybeSnapshot;
1878
2054
  });
1879
2055
  }
2056
+ notifyAttaching(snapshot) {
2057
+ var _a;
2058
+ if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
2059
+ this.baseSnapshotBlobs = SerializedSnapshotStorage.serializeTreeWithBlobContents(snapshot);
2060
+ }
2061
+ }
2062
+ async getSnapshotBlobs() {
2063
+ var _a;
2064
+ if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) ||
2065
+ this.attachState !== AttachState.Attached || this.context.pendingLocalState) {
2066
+ return;
2067
+ }
2068
+ assert(!!this.context.baseSnapshot, 0x2e5 /* "Must have a base snapshot" */);
2069
+ this.baseSnapshotBlobs = await SerializedSnapshotStorage.serializeTree(this.context.baseSnapshot, this.storage);
2070
+ }
1880
2071
  getPendingLocalState() {
1881
- return this.pendingStateManager.getLocalState();
2072
+ var _a;
2073
+ if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad)) {
2074
+ throw new UsageError("can't get state when offline load disabled");
2075
+ }
2076
+ const previousPendingState = this.context.pendingLocalState;
2077
+ if (previousPendingState) {
2078
+ return {
2079
+ pending: this.pendingStateManager.getLocalState(),
2080
+ snapshotBlobs: previousPendingState.snapshotBlobs,
2081
+ baseSnapshot: previousPendingState.baseSnapshot,
2082
+ savedOps: this.savedOps,
2083
+ };
2084
+ }
2085
+ assert(!!this.context.baseSnapshot, 0x2e6 /* "Must have a base snapshot" */);
2086
+ assert(!!this.baseSnapshotBlobs, 0x2e7 /* "Must serialize base snapshot blobs before getting runtime state" */);
2087
+ return {
2088
+ pending: this.pendingStateManager.getLocalState(),
2089
+ snapshotBlobs: this.baseSnapshotBlobs,
2090
+ baseSnapshot: this.context.baseSnapshot,
2091
+ savedOps: this.savedOps,
2092
+ };
1882
2093
  }
1883
2094
  /**
1884
2095
  * * Forms a function that will request a Summarizer.
@@ -1906,6 +2117,15 @@ export class ContainerRuntime extends TypedEventEmitter {
1906
2117
  return summarizer;
1907
2118
  };
1908
2119
  }
2120
+ async processSavedOps(state) {
2121
+ for (const op of state.savedOps) {
2122
+ this.process(op, false);
2123
+ await this.pendingStateManager.applyStashedOpsAt(op.sequenceNumber);
2124
+ }
2125
+ // we may not have seen every sequence number (because of system ops) so apply everything once we
2126
+ // don't have any more saved ops
2127
+ await this.pendingStateManager.applyStashedOpsAt();
2128
+ }
1909
2129
  }
1910
2130
  /**
1911
2131
  * Wait for a specific sequence number. Promise should resolve when we reach that number,