@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.
- package/.eslintrc.js +1 -1
- package/dist/blobManager.d.ts +2 -2
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +12 -11
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.d.ts +19 -0
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +23 -23
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +137 -29
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +338 -118
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +14 -3
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +4 -2
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +16 -5
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreRegistry.d.ts +0 -4
- package/dist/dataStoreRegistry.d.ts.map +1 -1
- package/dist/dataStoreRegistry.js +12 -1
- package/dist/dataStoreRegistry.js.map +1 -1
- package/dist/dataStores.d.ts +4 -3
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +13 -7
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +23 -27
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +44 -119
- package/dist/garbageCollection.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/orderedClientElection.js +0 -4
- package/dist/orderedClientElection.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +30 -29
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +72 -109
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runningSummarizer.d.ts +4 -3
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +11 -6
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/serializedSnapshotStorage.d.ts +58 -0
- package/dist/serializedSnapshotStorage.d.ts.map +1 -0
- package/dist/serializedSnapshotStorage.js +108 -0
- package/dist/serializedSnapshotStorage.js.map +1 -0
- package/dist/summarizer.d.ts +11 -4
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +18 -9
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerHeuristics.d.ts +5 -3
- package/dist/summarizerHeuristics.d.ts.map +1 -1
- package/dist/summarizerHeuristics.js +10 -3
- package/dist/summarizerHeuristics.js.map +1 -1
- package/dist/summarizerTypes.d.ts +4 -2
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryManager.d.ts +3 -3
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +7 -7
- package/dist/summaryManager.js.map +1 -1
- package/garbageCollection.md +9 -1
- package/lib/blobManager.d.ts +2 -2
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +12 -11
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts +19 -0
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +23 -23
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +137 -29
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +341 -121
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +15 -4
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +4 -2
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +16 -5
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreRegistry.d.ts +0 -4
- package/lib/dataStoreRegistry.d.ts.map +1 -1
- package/lib/dataStoreRegistry.js +12 -1
- package/lib/dataStoreRegistry.js.map +1 -1
- package/lib/dataStores.d.ts +4 -3
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +14 -8
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +23 -27
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +43 -117
- package/lib/garbageCollection.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/orderedClientElection.js +0 -4
- package/lib/orderedClientElection.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +30 -29
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +72 -109
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runningSummarizer.d.ts +4 -3
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +11 -6
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/serializedSnapshotStorage.d.ts +58 -0
- package/lib/serializedSnapshotStorage.d.ts.map +1 -0
- package/lib/serializedSnapshotStorage.js +104 -0
- package/lib/serializedSnapshotStorage.js.map +1 -0
- package/lib/summarizer.d.ts +11 -4
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +18 -9
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerHeuristics.d.ts +5 -3
- package/lib/summarizerHeuristics.d.ts.map +1 -1
- package/lib/summarizerHeuristics.js +10 -3
- package/lib/summarizerHeuristics.js.map +1 -1
- package/lib/summarizerTypes.d.ts +4 -2
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryManager.d.ts +3 -3
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +7 -7
- package/lib/summaryManager.js.map +1 -1
- package/package.json +19 -32
- package/src/blobManager.ts +29 -15
- package/src/connectionTelemetry.ts +60 -39
- package/src/containerRuntime.ts +502 -156
- package/src/dataStore.ts +21 -4
- package/src/dataStoreContext.ts +27 -5
- package/src/dataStoreRegistry.ts +8 -1
- package/src/dataStores.ts +21 -8
- package/src/garbageCollection.ts +81 -166
- package/src/index.ts +7 -1
- package/src/orderedClientElection.ts +1 -1
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +104 -123
- package/src/runningSummarizer.ts +20 -10
- package/src/serializedSnapshotStorage.ts +146 -0
- package/src/summarizer.ts +20 -16
- package/src/summarizerHeuristics.ts +21 -5
- package/src/summarizerTypes.ts +4 -2
- package/src/summaryManager.ts +5 -6
package/lib/containerRuntime.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
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.
|
|
391
|
+
this.savedOps = [];
|
|
389
392
|
this.consecutiveReconnects = 0;
|
|
390
393
|
this._disposed = false;
|
|
391
394
|
this.emitDirtyDocumentEvent = true;
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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 = (
|
|
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
|
-
((
|
|
450
|
-
((
|
|
451
|
-
this._maxOpSizeInBytes = ((
|
|
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
|
-
(
|
|
447
|
+
(_f = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _f !== void 0 ? _f : this.defaultMaxConsecutiveReconnects;
|
|
454
448
|
this._flushMode = runtimeOptions.flushMode;
|
|
455
|
-
|
|
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
|
-
|
|
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 (
|
|
474
|
-
this.summarizerNode.loadBaseSummaryWithoutDifferential(
|
|
480
|
+
if (baseSnapshot) {
|
|
481
|
+
this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
|
|
475
482
|
}
|
|
476
|
-
this.dataStores = new DataStores(getSummaryForDatastores(
|
|
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(
|
|
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
|
-
|
|
486
|
-
|
|
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
|
-
|
|
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.
|
|
532
|
-
}, this.
|
|
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 = (
|
|
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
|
|
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
|
-
|
|
611
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
717
|
-
|
|
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
|
|
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 (
|
|
967
|
-
this.
|
|
968
|
-
this.
|
|
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(
|
|
1049
|
+
this.closeFn(
|
|
971
1050
|
// pre-0.58 error message: MaxReconnectsWithNoProgress
|
|
972
|
-
"Runtime detected too many reconnects with no progress syncing local ops", undefined,
|
|
973
|
-
|
|
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
|
-
|
|
1003
|
-
|
|
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
|
|
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
|
-
|
|
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((
|
|
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
|
|
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
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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
|
-
|
|
1124
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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,
|