@fluidframework/container-runtime 0.57.0-51086 → 0.58.0-55561
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.
- package/dist/batchTracker.d.ts +26 -0
- package/dist/batchTracker.d.ts.map +1 -0
- package/dist/batchTracker.js +59 -0
- package/dist/batchTracker.js.map +1 -0
- package/dist/containerRuntime.d.ts +12 -7
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +125 -55
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts +1 -36
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +5 -27
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +5 -7
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +12 -7
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +1 -1
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +3 -3
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +25 -11
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +100 -57
- package/dist/garbageCollection.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +1 -6
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runningSummarizer.d.ts +1 -1
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +1 -1
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/summarizer.d.ts +3 -4
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +8 -9
- package/dist/summarizer.js.map +1 -1
- package/dist/summaryGenerator.d.ts +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +1 -1
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts +2 -6
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +4 -10
- package/dist/summaryManager.js.map +1 -1
- package/lib/batchTracker.d.ts +26 -0
- package/lib/batchTracker.d.ts.map +1 -0
- package/lib/batchTracker.js +54 -0
- package/lib/batchTracker.js.map +1 -0
- package/lib/containerRuntime.d.ts +12 -7
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +126 -56
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts +1 -36
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +4 -26
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +5 -7
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +13 -8
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +1 -1
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +3 -3
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +25 -11
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +98 -55
- package/lib/garbageCollection.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +1 -6
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runningSummarizer.d.ts +1 -1
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +1 -1
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/summarizer.d.ts +3 -4
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +8 -9
- package/lib/summarizer.js.map +1 -1
- package/lib/summaryGenerator.d.ts +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +1 -1
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts +2 -6
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +5 -11
- package/lib/summaryManager.js.map +1 -1
- package/package.json +12 -12
- package/src/batchTracker.ts +80 -0
- package/src/containerRuntime.ts +180 -63
- package/src/dataStore.ts +6 -42
- package/src/dataStoreContext.ts +17 -15
- package/src/dataStores.ts +9 -4
- package/src/garbageCollection.ts +125 -67
- package/src/index.ts +0 -1
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +4 -8
- package/src/runningSummarizer.ts +3 -3
- package/src/summarizer.ts +8 -8
- package/src/summaryGenerator.ts +2 -2
- package/src/summaryManager.ts +5 -20
package/dist/containerRuntime.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/*!
|
|
3
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
3
|
exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.ScheduleManager = exports.unpackRuntimeMessage = exports.isRuntimeMessage = exports.RuntimeMessage = exports.RuntimeHeaders = exports.ContainerMessageType = void 0;
|
|
8
4
|
const container_definitions_1 = require("@fluidframework/container-definitions");
|
|
@@ -33,6 +29,7 @@ const throttler_1 = require("./throttler");
|
|
|
33
29
|
const runWhileConnectedCoordinator_1 = require("./runWhileConnectedCoordinator");
|
|
34
30
|
const garbageCollection_1 = require("./garbageCollection");
|
|
35
31
|
const dataStore_1 = require("./dataStore");
|
|
32
|
+
const batchTracker_1 = require("./batchTracker");
|
|
36
33
|
var ContainerMessageType;
|
|
37
34
|
(function (ContainerMessageType) {
|
|
38
35
|
// An op to be delivered to store
|
|
@@ -75,9 +72,16 @@ var RuntimeHeaders;
|
|
|
75
72
|
/** True if the request is coming from an IFluidHandle. */
|
|
76
73
|
RuntimeHeaders["viaHandle"] = "viaHandle";
|
|
77
74
|
})(RuntimeHeaders = exports.RuntimeHeaders || (exports.RuntimeHeaders = {}));
|
|
78
|
-
// Local storage key to set the default flush mode to TurnBased
|
|
79
|
-
const turnBasedFlushModeKey = "Fluid.ContainerRuntime.FlushModeTurnBased";
|
|
80
75
|
const useDataStoreAliasingKey = "Fluid.ContainerRuntime.UseDataStoreAliasing";
|
|
76
|
+
const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
|
|
77
|
+
// Feature gate for the max op size. If the value is negative, chunking is enabled
|
|
78
|
+
// and all ops over 16k would be chunked. If the value is positive, all ops with
|
|
79
|
+
// a size strictly larger will be rejected and the container closed with an error.
|
|
80
|
+
const maxOpSizeInBytesKey = "Fluid.ContainerRuntime.MaxOpSizeInBytes";
|
|
81
|
+
// By default, we should reject any op larger than 768KB,
|
|
82
|
+
// in order to account for some extra overhead from serialization
|
|
83
|
+
// to not reach the 1MB limits in socket.io and Kafka.
|
|
84
|
+
const defaultMaxOpSizeInBytes = 768000;
|
|
81
85
|
var RuntimeMessage;
|
|
82
86
|
(function (RuntimeMessage) {
|
|
83
87
|
RuntimeMessage["FluidDataStoreOp"] = "component";
|
|
@@ -355,7 +359,7 @@ exports.getDeviceSpec = getDeviceSpec;
|
|
|
355
359
|
*/
|
|
356
360
|
class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
357
361
|
constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, requestHandler, _storage) {
|
|
358
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
362
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
359
363
|
super();
|
|
360
364
|
this.context = context;
|
|
361
365
|
this.registry = registry;
|
|
@@ -364,13 +368,15 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
364
368
|
this.logger = logger;
|
|
365
369
|
this.requestHandler = requestHandler;
|
|
366
370
|
this._storage = _storage;
|
|
371
|
+
this.defaultMaxConsecutiveReconnects = 15;
|
|
367
372
|
this._orderSequentiallyCalls = 0;
|
|
373
|
+
this._flushMode = runtime_definitions_1.FlushMode.TurnBased;
|
|
368
374
|
this.needsFlush = false;
|
|
369
375
|
this.flushTrigger = false;
|
|
370
376
|
this.paused = false;
|
|
377
|
+
this.consecutiveReconnects = 0;
|
|
371
378
|
this._disposed = false;
|
|
372
379
|
this.emitDirtyDocumentEvent = true;
|
|
373
|
-
this.summarizerWarning = (warning) => this.mc.logger.sendTelemetryEvent({ eventName: "summarizerWarning" }, warning);
|
|
374
380
|
/**
|
|
375
381
|
* Used to apply stashed ops at their reference sequence number.
|
|
376
382
|
* Normal op processing is synchronous, but applying stashed ops is async since the
|
|
@@ -420,7 +426,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
420
426
|
throw new container_utils_1.UsageError(`Can't summarize, disableSummaries: ${this.summariesDisabled}`);
|
|
421
427
|
}
|
|
422
428
|
};
|
|
423
|
-
this.
|
|
429
|
+
this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
|
|
424
430
|
// If this is an existing container, we get values from metadata.
|
|
425
431
|
// otherwise, we initialize them.
|
|
426
432
|
if (existing) {
|
|
@@ -442,25 +448,17 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
442
448
|
this.chunkMap = new Map(chunks);
|
|
443
449
|
this.handleContext = new containerHandleContext_1.ContainerFluidHandleContext("", this);
|
|
444
450
|
this.mc = telemetry_utils_1.loggerToMonitoringContext(telemetry_utils_1.ChildLogger.create(this.logger, "ContainerRuntime"));
|
|
445
|
-
this._flushMode =
|
|
446
|
-
((_b = this.mc.config.getBoolean(turnBasedFlushModeKey)) !== null && _b !== void 0 ? _b : false) ? runtime_definitions_1.FlushMode.TurnBased : runtime_definitions_1.FlushMode.Immediate;
|
|
447
451
|
this._aliasingEnabled =
|
|
448
|
-
((
|
|
449
|
-
((
|
|
452
|
+
((_b = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _b !== void 0 ? _b : false) ||
|
|
453
|
+
((_c = runtimeOptions.useDataStoreAliasing) !== null && _c !== void 0 ? _c : false);
|
|
454
|
+
this._maxOpSizeInBytes = ((_d = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _d !== void 0 ? _d : defaultMaxOpSizeInBytes);
|
|
455
|
+
this.maxConsecutiveReconnects = (_e = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _e !== void 0 ? _e : this.defaultMaxConsecutiveReconnects;
|
|
456
|
+
this.garbageCollector = garbageCollection_1.GarbageCollector.create(this, this.runtimeOptions.gcOptions, (unusedRoutes) => this.dataStores.deleteUnusedRoutes(unusedRoutes), (nodePath) => this.dataStores.getNodePackagePath(nodePath),
|
|
450
457
|
/**
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
* We use the timestamp of the last op for current timestamp. However, there can be cases where
|
|
454
|
-
* we don't have an op (on demand summaries for instance). In those cases, we will use the timestamp
|
|
455
|
-
* of this client's connection.
|
|
458
|
+
* Returns the timestamp of the last message seen by this client. This is used by garbage collector as
|
|
459
|
+
* the current reference timestamp for tracking unreferenced objects.
|
|
456
460
|
*/
|
|
457
|
-
|
|
458
|
-
var _a, _b, _c;
|
|
459
|
-
const client = this.clientId !== undefined ? this.getAudience().getMember(this.clientId) : undefined;
|
|
460
|
-
const timestamp = client === null || client === void 0 ? void 0 : client.timestamp;
|
|
461
|
-
return (_c = (_b = (_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.timestamp) !== null && _b !== void 0 ? _b : timestamp) !== null && _c !== void 0 ? _c : Date.now();
|
|
462
|
-
};
|
|
463
|
-
this.garbageCollector = garbageCollection_1.GarbageCollector.create(this, this.runtimeOptions.gcOptions, (unusedRoutes) => this.dataStores.deleteUnusedRoutes(unusedRoutes), (nodePath) => this.dataStores.getNodePackagePath(nodePath), getCurrentTimestamp, this.closeFn, context.baseSnapshot, async (id) => driver_utils_1.readAndParse(this.storage, id), this.mc.logger, existing, metadata);
|
|
461
|
+
() => { var _a, _b, _c; return (_b = (_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.timestamp) !== null && _b !== void 0 ? _b : (_c = this.messageAtLastSummary) === null || _c === void 0 ? void 0 : _c.timestamp; }, () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; }, context.baseSnapshot, async (id) => driver_utils_1.readAndParse(this.storage, id), this.mc.logger, existing, metadata);
|
|
464
462
|
const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
|
|
465
463
|
this.summarizerNode = runtime_utils_1.createRootSummarizerNodeWithGC(telemetry_utils_1.ChildLogger.create(this.logger, "SummarizerNode"),
|
|
466
464
|
// Summarize function to call when summarize is called. Summarizer node always tracks summary state.
|
|
@@ -481,7 +479,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
481
479
|
if (this.context.baseSnapshot) {
|
|
482
480
|
this.summarizerNode.loadBaseSummaryWithoutDifferential(this.context.baseSnapshot);
|
|
483
481
|
}
|
|
484
|
-
this.dataStores = new dataStores_1.DataStores(dataStores_1.getSummaryForDatastores(context.baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getBaseGCDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getBaseGCDetailsFn), (id) => this.summarizerNode.deleteChild(id), this.mc.logger, async () => this.garbageCollector.getDataStoreBaseGCDetails(), (
|
|
482
|
+
this.dataStores = new dataStores_1.DataStores(dataStores_1.getSummaryForDatastores(context.baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getBaseGCDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getBaseGCDetailsFn), (id) => this.summarizerNode.deleteChild(id), this.mc.logger, async () => this.garbageCollector.getDataStoreBaseGCDetails(), (path, timestampMs, packagePath) => this.garbageCollector.nodeUpdated(path, "Changed", timestampMs, packagePath), new Map(dataStoreAliasMap), this.garbageCollector.writeDataAtRoot);
|
|
485
483
|
this.blobManager = new blobManager_1.BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId) => this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId }), this, this.logger);
|
|
486
484
|
this.scheduleManager = new ScheduleManager(context.deltaManager, this, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
|
|
487
485
|
this.deltaSender = this.deltaManager;
|
|
@@ -492,7 +490,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
492
490
|
this.summaryCollection = new summaryCollection_1.SummaryCollection(this.deltaManager, this.logger);
|
|
493
491
|
const { attachState, pendingLocalState } = this.context;
|
|
494
492
|
this.dirtyContainer = attachState !== container_definitions_1.AttachState.Attached
|
|
495
|
-
|| ((
|
|
493
|
+
|| ((_f = pendingLocalState) === null || _f === void 0 ? void 0 : _f.pendingStates.length) > 0;
|
|
496
494
|
this.context.updateDirtyContainerState(this.dirtyContainer);
|
|
497
495
|
// Map the deprecated generateSummaries flag to disableSummaries.
|
|
498
496
|
if (this.runtimeOptions.summaryOptions.generateSummaries === false) {
|
|
@@ -505,8 +503,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
505
503
|
const orderedClientLogger = telemetry_utils_1.ChildLogger.create(this.logger, "OrderedClientElection");
|
|
506
504
|
const orderedClientCollection = new orderedClientElection_1.OrderedClientCollection(orderedClientLogger, this.context.deltaManager, this.context.quorum);
|
|
507
505
|
const orderedClientElectionForSummarizer = new orderedClientElection_1.OrderedClientElection(orderedClientLogger, orderedClientCollection, electedSummarizerData !== null && electedSummarizerData !== void 0 ? electedSummarizerData : this.context.deltaManager.lastSequenceNumber, summarizerClientElection_1.SummarizerClientElection.isClientEligible);
|
|
508
|
-
const summarizerClientElectionEnabled = (
|
|
509
|
-
const maxOpsSinceLastSummary = (
|
|
506
|
+
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;
|
|
507
|
+
const maxOpsSinceLastSummary = (_j = this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary) !== null && _j !== void 0 ? _j : 7000;
|
|
510
508
|
this.summarizerClientElection = new summarizerClientElection_1.SummarizerClientElection(orderedClientLogger, this.summaryCollection, orderedClientElectionForSummarizer, maxOpsSinceLastSummary, summarizerClientElectionEnabled);
|
|
511
509
|
if (this.context.clientDetails.type === summarizerClientElection_1.summarizerClientType) {
|
|
512
510
|
this._summarizer = new summarizer_1.Summarizer("/_summarizer", this /* ISummarizerRuntime */, () => this.summaryConfiguration, this /* ISummarizerInternalsProvider */, this.handleContext, this.summaryCollection, async (runtime) => runWhileConnectedCoordinator_1.RunWhileConnectedCoordinator.create(runtime));
|
|
@@ -538,7 +536,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
538
536
|
throttler_1.formExponentialFn({ coefficient: 20, initialDelay: 0 })), {
|
|
539
537
|
initialDelayMs: this.runtimeOptions.summaryOptions.initialSummarizerDelayMs,
|
|
540
538
|
}, this.runtimeOptions.summaryOptions.summarizerOptions);
|
|
541
|
-
this.summaryManager.on("summarizerWarning", this.summarizerWarning);
|
|
542
539
|
this.summaryManager.start();
|
|
543
540
|
}
|
|
544
541
|
}
|
|
@@ -569,6 +566,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
569
566
|
// logging container load stats
|
|
570
567
|
this.logger.sendTelemetryEvent(Object.assign(Object.assign(Object.assign({ eventName: "ContainerLoadStats" }, this.createContainerMetadata), this.dataStores.containerLoadStats), { summaryCount: this.summaryCount, summaryFormatVersion: metadata === null || metadata === void 0 ? void 0 : metadata.summaryFormatVersion, disableIsolatedChannels: metadata === null || metadata === void 0 ? void 0 : metadata.disableIsolatedChannels, gcVersion: metadata === null || metadata === void 0 ? void 0 : metadata.gcFeature }));
|
|
571
568
|
connectionTelemetry_1.ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
|
|
569
|
+
batchTracker_1.BindBatchTracker(this, this.logger);
|
|
572
570
|
}
|
|
573
571
|
get IContainerRuntime() { return this; }
|
|
574
572
|
get IFluidRouter() { return this; }
|
|
@@ -584,7 +582,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
584
582
|
var _a, _b, _c;
|
|
585
583
|
// If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
|
|
586
584
|
// back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
|
|
587
|
-
const
|
|
585
|
+
const backCompatContext = context;
|
|
586
|
+
const passLogger = (_a = backCompatContext.taggedLogger) !== null && _a !== void 0 ? _a : new telemetry_utils_1.TaggedLoggerAdapter(backCompatContext.logger);
|
|
588
587
|
const logger = telemetry_utils_1.ChildLogger.create(passLogger, undefined, {
|
|
589
588
|
all: {
|
|
590
589
|
runtimeVersion: packageVersion_1.pkgVersion,
|
|
@@ -645,7 +644,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
645
644
|
// Unless bypass is explicitly set, then take action when sequence numbers mismatch.
|
|
646
645
|
if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
|
|
647
646
|
// "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
|
|
648
|
-
const error = new container_utils_1.DataCorruptionError("
|
|
647
|
+
const error = new container_utils_1.DataCorruptionError("Summary metadata mismatch", { runtimeSequenceNumber, protocolSequenceNumber });
|
|
649
648
|
if (loadSequenceNumberVerification === "log") {
|
|
650
649
|
logger.sendErrorEvent({ eventName: "SequenceNumberMismatch" }, error);
|
|
651
650
|
}
|
|
@@ -747,7 +746,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
747
746
|
attachState: this.attachState,
|
|
748
747
|
}, error);
|
|
749
748
|
if (this.summaryManager !== undefined) {
|
|
750
|
-
this.summaryManager.off("summarizerWarning", this.summarizerWarning);
|
|
751
749
|
this.summaryManager.dispose();
|
|
752
750
|
}
|
|
753
751
|
this.garbageCollector.dispose();
|
|
@@ -853,17 +851,15 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
853
851
|
}
|
|
854
852
|
}
|
|
855
853
|
const dataStoreChannel = await dataStoreContext.realize();
|
|
856
|
-
|
|
857
|
-
// that the package path is available.
|
|
858
|
-
this.garbageCollector.nodeUpdated(`/${id}`, "Loaded", dataStoreContext.packagePath, request === null || request === void 0 ? void 0 : request.headers);
|
|
854
|
+
this.garbageCollector.nodeUpdated(`/${id}`, "Loaded", undefined /* timestampMs */, dataStoreContext.packagePath, request === null || request === void 0 ? void 0 : request.headers);
|
|
859
855
|
return dataStoreChannel;
|
|
860
856
|
}
|
|
861
857
|
formMetadata() {
|
|
862
858
|
var _a;
|
|
863
859
|
return Object.assign(Object.assign({}, this.createContainerMetadata), { summaryCount: this.summaryCount, summaryFormatVersion: 1, disableIsolatedChannels: this.disableIsolatedChannels || undefined, gcFeature: this.garbageCollector.gcSummaryFeatureVersion,
|
|
864
|
-
// The last message processed at the time of summary. If there are no messages,
|
|
865
|
-
//
|
|
866
|
-
message: (_a = summaryFormat_1.extractSummaryMetadataMessage(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.
|
|
860
|
+
// The last message processed at the time of summary. If there are no new messages, use the message from the
|
|
861
|
+
// last summary.
|
|
862
|
+
message: (_a = summaryFormat_1.extractSummaryMetadataMessage(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.messageAtLastSummary, sessionExpiryTimeoutMs: this.garbageCollector.sessionExpiryTimeoutMs });
|
|
867
863
|
}
|
|
868
864
|
addContainerStateToSummary(summaryTree) {
|
|
869
865
|
var _a;
|
|
@@ -894,6 +890,37 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
894
890
|
}
|
|
895
891
|
}
|
|
896
892
|
}
|
|
893
|
+
// Track how many times the container tries to reconnect with pending messages.
|
|
894
|
+
// This happens when the connection state is changed and we reset the counter
|
|
895
|
+
// when we are able to process a local op or when there are no pending messages.
|
|
896
|
+
// If this counter reaches a max, it's a good indicator that the container
|
|
897
|
+
// is not making progress and it is stuck in a retry loop.
|
|
898
|
+
shouldContinueReconnecting() {
|
|
899
|
+
if (this.maxConsecutiveReconnects <= 0) {
|
|
900
|
+
// Feature disabled, we never stop reconnecting
|
|
901
|
+
return true;
|
|
902
|
+
}
|
|
903
|
+
if (!this.pendingStateManager.hasPendingMessages()) {
|
|
904
|
+
// If there are no pending messages, we can always reconnect
|
|
905
|
+
this.resetReconnectCount();
|
|
906
|
+
return true;
|
|
907
|
+
}
|
|
908
|
+
this.consecutiveReconnects++;
|
|
909
|
+
if (this.consecutiveReconnects === Math.floor(this.maxConsecutiveReconnects / 2)) {
|
|
910
|
+
// If we're halfway through the max reconnects, send an event in order
|
|
911
|
+
// to better identify false positives, if any. If the rate of this event
|
|
912
|
+
// matches Container Close count below, we can safely cut down
|
|
913
|
+
// maxConsecutiveReconnects to half.
|
|
914
|
+
this.mc.logger.sendTelemetryEvent({
|
|
915
|
+
eventName: "ReconnectsWithNoProgress",
|
|
916
|
+
attempts: this.consecutiveReconnects,
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
return this.consecutiveReconnects < this.maxConsecutiveReconnects;
|
|
920
|
+
}
|
|
921
|
+
resetReconnectCount() {
|
|
922
|
+
this.consecutiveReconnects = 0;
|
|
923
|
+
}
|
|
897
924
|
replayPendingStates() {
|
|
898
925
|
// We need to be able to send ops to replay states
|
|
899
926
|
if (!this.canSendOps()) {
|
|
@@ -946,6 +973,11 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
946
973
|
if (changeOfState) {
|
|
947
974
|
this.deltaManager.off("op", this.onOp);
|
|
948
975
|
this.context.pendingLocalState = undefined;
|
|
976
|
+
if (!this.shouldContinueReconnecting()) {
|
|
977
|
+
this.closeFn(new container_utils_1.GenericError("Runtime detected too many reconnects with no progress syncing local ops", undefined, // error
|
|
978
|
+
{ attempts: this.consecutiveReconnects }));
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
949
981
|
this.replayPendingStates();
|
|
950
982
|
}
|
|
951
983
|
this.dataStores.setConnectionState(connected, clientId);
|
|
@@ -998,6 +1030,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
998
1030
|
}
|
|
999
1031
|
this.emit("op", message);
|
|
1000
1032
|
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
1033
|
+
if (local) {
|
|
1034
|
+
// If we have processed a local op, this means that the container is
|
|
1035
|
+
// making progress and we can reset the counter for how many times
|
|
1036
|
+
// we have consecutively replayed the pending states
|
|
1037
|
+
this.resetReconnectCount();
|
|
1038
|
+
}
|
|
1001
1039
|
}
|
|
1002
1040
|
catch (e) {
|
|
1003
1041
|
this.scheduleManager.afterOpProcessing(e, message);
|
|
@@ -1082,7 +1120,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1082
1120
|
callback();
|
|
1083
1121
|
}
|
|
1084
1122
|
catch (error) {
|
|
1085
|
-
this.closeFn(new container_utils_1.GenericError("
|
|
1123
|
+
this.closeFn(new container_utils_1.GenericError("orderSequentially callback exception", error));
|
|
1086
1124
|
throw error; // throw the original error for the consumer of the runtime
|
|
1087
1125
|
}
|
|
1088
1126
|
finally {
|
|
@@ -1125,7 +1163,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1125
1163
|
const dataStore = await this._createDataStore(pkg, false /* isRoot */, internalId, props);
|
|
1126
1164
|
const aliasedDataStore = dataStore_1.channelToDataStore(dataStore, internalId, this, this.dataStores, this.mc.logger);
|
|
1127
1165
|
const result = await aliasedDataStore.trySetAlias(alias);
|
|
1128
|
-
if (result !==
|
|
1166
|
+
if (result !== "Success") {
|
|
1129
1167
|
throw new container_utils_1.GenericError("dataStoreAliasFailure", undefined /* error */, {
|
|
1130
1168
|
alias: {
|
|
1131
1169
|
value: alias,
|
|
@@ -1157,7 +1195,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1157
1195
|
if (isRoot) {
|
|
1158
1196
|
fluidDataStore.bindToContext();
|
|
1159
1197
|
}
|
|
1160
|
-
return fluidDataStore;
|
|
1198
|
+
return dataStore_1.channelToDataStore(fluidDataStore, id, this, this.dataStores, this.mc.logger);
|
|
1161
1199
|
}
|
|
1162
1200
|
async _createDataStoreWithProps(pkg, props, id = uuid_1.v4(), isRoot = false) {
|
|
1163
1201
|
return this._aliasingEnabled === true && isRoot ?
|
|
@@ -1339,7 +1377,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1339
1377
|
* @param options - options controlling how the summary is generated or submitted
|
|
1340
1378
|
*/
|
|
1341
1379
|
async submitSummary(options) {
|
|
1342
|
-
var _a, _b;
|
|
1380
|
+
var _a, _b, _c;
|
|
1343
1381
|
const { fullTree, refreshLatestAck, summaryLogger } = options;
|
|
1344
1382
|
if (refreshLatestAck) {
|
|
1345
1383
|
const latestSummaryRefSeq = await this.refreshLatestSummaryAckFromServer(telemetry_utils_1.ChildLogger.create(summaryLogger, undefined, { all: { safeSummary: true } }));
|
|
@@ -1357,6 +1395,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1357
1395
|
await this.deltaManager.inbound.pause();
|
|
1358
1396
|
const summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
|
|
1359
1397
|
const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
|
|
1398
|
+
// We should be here is we haven't processed be here. If we are of if the last message's sequence number
|
|
1399
|
+
// doesn't match the last processed sequence number, log an error.
|
|
1400
|
+
if (summaryRefSeqNum !== ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber)) {
|
|
1401
|
+
summaryLogger.sendErrorEvent({
|
|
1402
|
+
eventName: "LastSequenceMismatch",
|
|
1403
|
+
message,
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1360
1406
|
this.summarizerNode.startSummary(summaryRefSeqNum, summaryLogger);
|
|
1361
1407
|
// Helper function to check whether we should still continue between each async step.
|
|
1362
1408
|
const checkContinue = () => {
|
|
@@ -1413,13 +1459,15 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1413
1459
|
return { stage: "base", referenceSequenceNumber: summaryRefSeqNum, error };
|
|
1414
1460
|
}
|
|
1415
1461
|
const { summary: summaryTree, stats: partialStats } = summarizeResult;
|
|
1462
|
+
// Now that we have generated the summary, update the message at last summary to the last message processed.
|
|
1463
|
+
this.messageAtLastSummary = this.deltaManager.lastMessage;
|
|
1416
1464
|
// Counting dataStores and handles
|
|
1417
1465
|
// Because handles are unchanged dataStores in the current logic,
|
|
1418
1466
|
// summarized dataStore count is total dataStore count minus handle count
|
|
1419
1467
|
const dataStoreTree = this.disableIsolatedChannels ? summaryTree : summaryTree.tree[runtime_definitions_1.channelsTreeName];
|
|
1420
1468
|
common_utils_1.assert(dataStoreTree.type === protocol_definitions_1.SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
|
|
1421
1469
|
const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === protocol_definitions_1.SummaryType.Handle).length;
|
|
1422
|
-
const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (
|
|
1470
|
+
const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (_b = summarizeResult.gcStats) === null || _b === void 0 ? void 0 : _b.updatedDataStoreCount }, partialStats);
|
|
1423
1471
|
const generateSummaryData = {
|
|
1424
1472
|
referenceSequenceNumber: summaryRefSeqNum,
|
|
1425
1473
|
summaryTree,
|
|
@@ -1435,7 +1483,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1435
1483
|
const summaryContext = lastAck === undefined
|
|
1436
1484
|
? {
|
|
1437
1485
|
proposalHandle: undefined,
|
|
1438
|
-
ackHandle: (
|
|
1486
|
+
ackHandle: (_c = this.context.getLoadedFromVersion()) === null || _c === void 0 ? void 0 : _c.id,
|
|
1439
1487
|
referenceSequenceNumber: summaryRefSeqNum,
|
|
1440
1488
|
}
|
|
1441
1489
|
: {
|
|
@@ -1566,16 +1614,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1566
1614
|
});
|
|
1567
1615
|
}
|
|
1568
1616
|
}
|
|
1569
|
-
|
|
1570
|
-
// there will be a lot of escape characters that can make it up to 2x bigger!
|
|
1571
|
-
// This is Ok, because DeltaManager.shouldSplit() will have 2 * maxMessageSize limit
|
|
1572
|
-
if (!serializedContent || serializedContent.length <= maxOpSize) {
|
|
1573
|
-
clientSequenceNumber = this.submitRuntimeMessage(type, content,
|
|
1574
|
-
/* batch: */ this._flushMode === runtime_definitions_1.FlushMode.TurnBased, opMetadataInternal);
|
|
1575
|
-
}
|
|
1576
|
-
else {
|
|
1577
|
-
clientSequenceNumber = this.submitChunkedMessage(type, serializedContent, maxOpSize);
|
|
1578
|
-
}
|
|
1617
|
+
clientSequenceNumber = this.submitMaybeChunkedMessages(type, content, serializedContent, maxOpSize, this._flushMode === runtime_definitions_1.FlushMode.TurnBased, opMetadataInternal);
|
|
1579
1618
|
}
|
|
1580
1619
|
// Let the PendingStateManager know that a message was submitted.
|
|
1581
1620
|
this.pendingStateManager.onSubmitMessage(type, clientSequenceNumber, this.deltaManager.lastSequenceNumber, content, localOpMetadata, opMetadataInternal);
|
|
@@ -1583,6 +1622,35 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1583
1622
|
this.updateDocumentDirtyState(true);
|
|
1584
1623
|
}
|
|
1585
1624
|
}
|
|
1625
|
+
submitMaybeChunkedMessages(type, content, serializedContent, serverMaxOpSize, batch, opMetadataInternal = undefined) {
|
|
1626
|
+
if (this._maxOpSizeInBytes >= 0) {
|
|
1627
|
+
// Chunking disabled
|
|
1628
|
+
if (!serializedContent || serializedContent.length <= this._maxOpSizeInBytes) {
|
|
1629
|
+
return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
|
|
1630
|
+
}
|
|
1631
|
+
// When chunking is disabled, we ignore the server max message size
|
|
1632
|
+
// and if the content length is larger than the client configured message size
|
|
1633
|
+
// instead of splitting the content, we will fail by explicitly close the container
|
|
1634
|
+
this.closeFn(new container_utils_1.GenericError("OpTooLarge",
|
|
1635
|
+
/* error */ undefined, {
|
|
1636
|
+
length: {
|
|
1637
|
+
value: serializedContent.length,
|
|
1638
|
+
tag: telemetry_utils_1.TelemetryDataTag.PackageData,
|
|
1639
|
+
},
|
|
1640
|
+
limit: {
|
|
1641
|
+
value: this._maxOpSizeInBytes,
|
|
1642
|
+
tag: telemetry_utils_1.TelemetryDataTag.PackageData,
|
|
1643
|
+
},
|
|
1644
|
+
}));
|
|
1645
|
+
return -1;
|
|
1646
|
+
}
|
|
1647
|
+
// Chunking enabled, fallback on the server's max message size
|
|
1648
|
+
// and split the content accordingly
|
|
1649
|
+
if (!serializedContent || serializedContent.length <= serverMaxOpSize) {
|
|
1650
|
+
return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
|
|
1651
|
+
}
|
|
1652
|
+
return this.submitChunkedMessage(type, serializedContent, serverMaxOpSize);
|
|
1653
|
+
}
|
|
1586
1654
|
submitChunkedMessage(type, content, maxOpSize) {
|
|
1587
1655
|
const contentLength = content.length;
|
|
1588
1656
|
const chunkN = Math.floor((contentLength - 1) / maxOpSize) + 1;
|
|
@@ -1661,6 +1729,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1661
1729
|
const readAndParseBlob = async (id) => driver_utils_1.readAndParse(this.storage, id);
|
|
1662
1730
|
const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, async () => this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
|
|
1663
1731
|
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
1732
|
+
ackHandle,
|
|
1733
|
+
summaryRefSeq,
|
|
1664
1734
|
fetchLatest: false,
|
|
1665
1735
|
}), readAndParseBlob, summaryLogger);
|
|
1666
1736
|
// Notify the garbage collector so it can update its latest summary state.
|