@fluidframework/container-runtime 2.0.0-dev.2.2.0.111723 → 2.0.0-dev.3.1.0.125672
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 +21 -10
- package/.mocharc.js +2 -2
- package/api-extractor.json +2 -2
- package/dist/batchTracker.d.ts +1 -2
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js +2 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +62 -28
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +256 -102
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +11 -9
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerHandleContext.d.ts.map +1 -1
- package/dist/containerHandleContext.js +3 -1
- package/dist/containerHandleContext.js.map +1 -1
- package/dist/containerRuntime.d.ts +110 -78
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +336 -331
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +11 -9
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +2 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +40 -23
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreContexts.d.ts.map +1 -1
- package/dist/dataStoreContexts.js +7 -3
- package/dist/dataStoreContexts.js.map +1 -1
- package/dist/dataStoreRegistry.d.ts.map +1 -1
- package/dist/dataStoreRegistry.js +3 -1
- package/dist/dataStoreRegistry.js.map +1 -1
- package/dist/dataStores.d.ts +12 -9
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +69 -46
- package/dist/dataStores.js.map +1 -1
- package/dist/deltaScheduler.d.ts.map +1 -1
- package/dist/deltaScheduler.js +8 -3
- package/dist/deltaScheduler.js.map +1 -1
- package/dist/garbageCollection.d.ts +57 -42
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +371 -239
- package/dist/garbageCollection.js.map +1 -1
- package/dist/garbageCollectionConstants.d.ts +23 -0
- package/dist/garbageCollectionConstants.d.ts.map +1 -0
- package/dist/garbageCollectionConstants.js +36 -0
- package/dist/garbageCollectionConstants.js.map +1 -0
- package/dist/garbageCollectionHelpers.d.ts +15 -0
- package/dist/garbageCollectionHelpers.d.ts.map +1 -0
- package/dist/garbageCollectionHelpers.js +27 -0
- package/dist/garbageCollectionHelpers.js.map +1 -0
- package/dist/gcSweepReadyUsageDetection.d.ts +5 -5
- package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -1
- package/dist/gcSweepReadyUsageDetection.js +15 -11
- package/dist/gcSweepReadyUsageDetection.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +42 -0
- package/dist/opLifecycle/batchManager.d.ts.map +1 -0
- package/dist/opLifecycle/batchManager.js +124 -0
- package/dist/opLifecycle/batchManager.js.map +1 -0
- package/dist/opLifecycle/definitions.d.ts +64 -0
- package/dist/opLifecycle/definitions.d.ts.map +1 -0
- package/dist/opLifecycle/definitions.js +7 -0
- package/dist/opLifecycle/definitions.js.map +1 -0
- package/dist/opLifecycle/index.d.ts +12 -0
- package/dist/opLifecycle/index.d.ts.map +1 -0
- package/dist/opLifecycle/index.js +22 -0
- package/dist/opLifecycle/index.js.map +1 -0
- package/dist/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +3 -3
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -0
- package/dist/opLifecycle/opCompressor.js +67 -0
- package/dist/opLifecycle/opCompressor.js.map +1 -0
- package/dist/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +2 -1
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -0
- package/dist/{opDecompressor.js → opLifecycle/opDecompressor.js} +37 -21
- package/dist/opLifecycle/opDecompressor.js.map +1 -0
- package/dist/opLifecycle/opSplitter.d.ts +49 -0
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -0
- package/dist/opLifecycle/opSplitter.js +173 -0
- package/dist/opLifecycle/opSplitter.js.map +1 -0
- package/dist/opLifecycle/outbox.d.ts +52 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -0
- package/dist/opLifecycle/outbox.js +164 -0
- package/dist/opLifecycle/outbox.js.map +1 -0
- package/dist/opLifecycle/remoteMessageProcessor.d.ts +26 -0
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
- package/dist/opLifecycle/remoteMessageProcessor.js +96 -0
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -0
- package/dist/opProperties.d.ts.map +1 -1
- package/dist/opProperties.js +1 -3
- package/dist/opProperties.js.map +1 -1
- package/dist/orderedClientElection.d.ts.map +1 -1
- package/dist/orderedClientElection.js +10 -4
- package/dist/orderedClientElection.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 +4 -13
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +134 -161
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runWhileConnectedCoordinator.d.ts.map +1 -1
- package/dist/runWhileConnectedCoordinator.js.map +1 -1
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +34 -22
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/scheduleManager.d.ts +0 -1
- package/dist/scheduleManager.d.ts.map +1 -1
- package/dist/scheduleManager.js +11 -21
- package/dist/scheduleManager.js.map +1 -1
- package/dist/serializedSnapshotStorage.d.ts.map +1 -1
- package/dist/serializedSnapshotStorage.js +3 -1
- package/dist/serializedSnapshotStorage.js.map +1 -1
- package/dist/summarizer.d.ts +2 -3
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +39 -18
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerClientElection.d.ts +1 -2
- package/dist/summarizerClientElection.d.ts.map +1 -1
- package/dist/summarizerClientElection.js +3 -30
- package/dist/summarizerClientElection.js.map +1 -1
- package/dist/summarizerHandle.d.ts.map +1 -1
- package/dist/summarizerHandle.js.map +1 -1
- package/dist/summarizerHeuristics.d.ts.map +1 -1
- package/dist/summarizerHeuristics.js +6 -9
- package/dist/summarizerHeuristics.js.map +1 -1
- package/dist/summarizerTypes.d.ts +22 -25
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryCollection.d.ts.map +1 -1
- package/dist/summaryCollection.js +18 -8
- package/dist/summaryCollection.js.map +1 -1
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js +18 -11
- package/dist/summaryFormat.js.map +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +32 -14
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +21 -9
- package/dist/summaryManager.js.map +1 -1
- package/dist/throttler.d.ts +2 -2
- package/dist/throttler.d.ts.map +1 -1
- package/dist/throttler.js +4 -4
- package/dist/throttler.js.map +1 -1
- package/garbageCollection.md +15 -2
- package/lib/batchTracker.d.ts +1 -2
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js +2 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +62 -28
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +259 -105
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +11 -9
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerHandleContext.d.ts.map +1 -1
- package/lib/containerHandleContext.js +3 -1
- package/lib/containerHandleContext.js.map +1 -1
- package/lib/containerRuntime.d.ts +110 -78
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +340 -334
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +11 -9
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +2 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +41 -24
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreContexts.d.ts.map +1 -1
- package/lib/dataStoreContexts.js +7 -3
- package/lib/dataStoreContexts.js.map +1 -1
- package/lib/dataStoreRegistry.d.ts.map +1 -1
- package/lib/dataStoreRegistry.js +3 -1
- package/lib/dataStoreRegistry.js.map +1 -1
- package/lib/dataStores.d.ts +12 -9
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +75 -52
- package/lib/dataStores.js.map +1 -1
- package/lib/deltaScheduler.d.ts.map +1 -1
- package/lib/deltaScheduler.js +9 -4
- package/lib/deltaScheduler.js.map +1 -1
- package/lib/garbageCollection.d.ts +57 -42
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +364 -232
- package/lib/garbageCollection.js.map +1 -1
- package/lib/garbageCollectionConstants.d.ts +23 -0
- package/lib/garbageCollectionConstants.d.ts.map +1 -0
- package/lib/garbageCollectionConstants.js +33 -0
- package/lib/garbageCollectionConstants.js.map +1 -0
- package/lib/garbageCollectionHelpers.d.ts +15 -0
- package/lib/garbageCollectionHelpers.d.ts.map +1 -0
- package/lib/garbageCollectionHelpers.js +23 -0
- package/lib/garbageCollectionHelpers.js.map +1 -0
- package/lib/gcSweepReadyUsageDetection.d.ts +5 -5
- package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -1
- package/lib/gcSweepReadyUsageDetection.js +15 -11
- package/lib/gcSweepReadyUsageDetection.js.map +1 -1
- package/lib/index.d.ts +4 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -3
- package/lib/index.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +42 -0
- package/lib/opLifecycle/batchManager.d.ts.map +1 -0
- package/lib/opLifecycle/batchManager.js +120 -0
- package/lib/opLifecycle/batchManager.js.map +1 -0
- package/lib/opLifecycle/definitions.d.ts +64 -0
- package/lib/opLifecycle/definitions.d.ts.map +1 -0
- package/lib/opLifecycle/definitions.js +6 -0
- package/lib/opLifecycle/definitions.js.map +1 -0
- package/lib/opLifecycle/index.d.ts +12 -0
- package/lib/opLifecycle/index.d.ts.map +1 -0
- package/lib/opLifecycle/index.js +11 -0
- package/lib/opLifecycle/index.js.map +1 -0
- package/lib/{opCompressor.d.ts → opLifecycle/opCompressor.d.ts} +3 -3
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -0
- package/lib/opLifecycle/opCompressor.js +63 -0
- package/lib/opLifecycle/opCompressor.js.map +1 -0
- package/lib/{opDecompressor.d.ts → opLifecycle/opDecompressor.d.ts} +2 -1
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -0
- package/lib/{opDecompressor.js → opLifecycle/opDecompressor.js} +37 -21
- package/lib/opLifecycle/opDecompressor.js.map +1 -0
- package/lib/opLifecycle/opSplitter.d.ts +49 -0
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -0
- package/lib/opLifecycle/opSplitter.js +168 -0
- package/lib/opLifecycle/opSplitter.js.map +1 -0
- package/lib/opLifecycle/outbox.d.ts +52 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -0
- package/lib/opLifecycle/outbox.js +160 -0
- package/lib/opLifecycle/outbox.js.map +1 -0
- package/lib/opLifecycle/remoteMessageProcessor.d.ts +26 -0
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -0
- package/lib/opLifecycle/remoteMessageProcessor.js +91 -0
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -0
- package/lib/opProperties.d.ts.map +1 -1
- package/lib/opProperties.js +1 -3
- package/lib/opProperties.js.map +1 -1
- package/lib/orderedClientElection.d.ts.map +1 -1
- package/lib/orderedClientElection.js +10 -4
- package/lib/orderedClientElection.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 +4 -13
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +134 -161
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runWhileConnectedCoordinator.d.ts.map +1 -1
- package/lib/runWhileConnectedCoordinator.js.map +1 -1
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +35 -23
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/scheduleManager.d.ts +0 -1
- package/lib/scheduleManager.d.ts.map +1 -1
- package/lib/scheduleManager.js +11 -21
- package/lib/scheduleManager.js.map +1 -1
- package/lib/serializedSnapshotStorage.d.ts.map +1 -1
- package/lib/serializedSnapshotStorage.js +3 -1
- package/lib/serializedSnapshotStorage.js.map +1 -1
- package/lib/summarizer.d.ts +2 -3
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +39 -18
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerClientElection.d.ts +1 -2
- package/lib/summarizerClientElection.d.ts.map +1 -1
- package/lib/summarizerClientElection.js +3 -30
- package/lib/summarizerClientElection.js.map +1 -1
- package/lib/summarizerHandle.d.ts.map +1 -1
- package/lib/summarizerHandle.js.map +1 -1
- package/lib/summarizerHeuristics.d.ts.map +1 -1
- package/lib/summarizerHeuristics.js +6 -9
- package/lib/summarizerHeuristics.js.map +1 -1
- package/lib/summarizerTypes.d.ts +22 -25
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryCollection.d.ts.map +1 -1
- package/lib/summaryCollection.js +18 -8
- package/lib/summaryCollection.js.map +1 -1
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js +20 -13
- package/lib/summaryFormat.js.map +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +32 -14
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +21 -9
- package/lib/summaryManager.js.map +1 -1
- package/lib/throttler.d.ts +2 -2
- package/lib/throttler.d.ts.map +1 -1
- package/lib/throttler.js +4 -4
- package/lib/throttler.js.map +1 -1
- package/package.json +28 -38
- package/prettier.config.cjs +1 -1
- package/src/batchTracker.ts +55 -50
- package/src/blobManager.ts +802 -541
- package/src/connectionTelemetry.ts +280 -249
- package/src/containerHandleContext.ts +27 -29
- package/src/containerRuntime.ts +3125 -2982
- package/src/dataStore.ts +172 -159
- package/src/dataStoreContext.ts +1049 -992
- package/src/dataStoreContexts.ts +178 -161
- package/src/dataStoreRegistry.ts +25 -20
- package/src/dataStores.ts +785 -711
- package/src/deltaScheduler.ts +158 -150
- package/src/garbageCollection.ts +1797 -1558
- package/src/garbageCollectionConstants.ts +38 -0
- package/src/garbageCollectionHelpers.ts +37 -0
- package/src/gcSweepReadyUsageDetection.ts +90 -84
- package/src/index.ts +68 -69
- package/src/opLifecycle/batchManager.ts +167 -0
- package/src/opLifecycle/definitions.ts +70 -0
- package/src/opLifecycle/index.ts +18 -0
- package/src/opLifecycle/opCompressor.ts +82 -0
- package/src/opLifecycle/opDecompressor.ts +124 -0
- package/src/opLifecycle/opSplitter.ts +238 -0
- package/src/opLifecycle/outbox.ts +228 -0
- package/src/opLifecycle/remoteMessageProcessor.ts +106 -0
- package/src/opProperties.ts +11 -9
- package/src/orderedClientElection.ts +489 -457
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +379 -381
- package/src/runWhileConnectedCoordinator.ts +78 -71
- package/src/runningSummarizer.ts +619 -582
- package/src/scheduleManager.ts +299 -280
- package/src/serializedSnapshotStorage.ts +116 -111
- package/src/summarizer.ts +417 -381
- package/src/summarizerClientElection.ts +107 -129
- package/src/summarizerHandle.ts +11 -9
- package/src/summarizerHeuristics.ts +183 -186
- package/src/summarizerTypes.ts +344 -333
- package/src/summaryCollection.ts +378 -349
- package/src/summaryFormat.ts +146 -127
- package/src/summaryGenerator.ts +464 -406
- package/src/summaryManager.ts +377 -348
- package/src/throttler.ts +131 -122
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +9 -13
- package/dist/batchManager.d.ts +0 -42
- package/dist/batchManager.d.ts.map +0 -1
- package/dist/batchManager.js +0 -83
- package/dist/batchManager.js.map +0 -1
- package/dist/opCompressor.d.ts.map +0 -1
- package/dist/opCompressor.js +0 -50
- package/dist/opCompressor.js.map +0 -1
- package/dist/opDecompressor.d.ts.map +0 -1
- package/dist/opDecompressor.js.map +0 -1
- package/lib/batchManager.d.ts +0 -42
- package/lib/batchManager.d.ts.map +0 -1
- package/lib/batchManager.js +0 -79
- package/lib/batchManager.js.map +0 -1
- package/lib/opCompressor.d.ts.map +0 -1
- package/lib/opCompressor.js +0 -46
- package/lib/opCompressor.js.map +0 -1
- package/lib/opDecompressor.d.ts.map +0 -1
- package/lib/opDecompressor.js.map +0 -1
- package/src/batchManager.ts +0 -108
- package/src/opCompressor.ts +0 -59
- package/src/opDecompressor.ts +0 -82
package/lib/containerRuntime.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { AttachState, LoaderHeader, } from "@fluidframework/container-definitions";
|
|
2
|
-
import { assert, Trace, TypedEventEmitter, unreachableCase
|
|
2
|
+
import { assert, Trace, TypedEventEmitter, unreachableCase } from "@fluidframework/common-utils";
|
|
3
3
|
import { ChildLogger, raiseConnectedEvent, PerformanceEvent, TaggedLoggerAdapter, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
|
|
4
4
|
import { DriverHeader, FetchSource, } from "@fluidframework/driver-definitions";
|
|
5
5
|
import { readAndParse } from "@fluidframework/driver-utils";
|
|
6
6
|
import { DataCorruptionError, DataProcessingError, GenericError, UsageError, } from "@fluidframework/container-utils";
|
|
7
7
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
8
|
-
import { FlushMode, channelsTreeName, } from "@fluidframework/runtime-definitions";
|
|
8
|
+
import { FlushMode, gcTreeKey, channelsTreeName, } from "@fluidframework/runtime-definitions";
|
|
9
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";
|
|
@@ -13,24 +13,23 @@ import { ContainerFluidHandleContext } from "./containerHandleContext";
|
|
|
13
13
|
import { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
14
14
|
import { Summarizer } from "./summarizer";
|
|
15
15
|
import { SummaryManager } from "./summaryManager";
|
|
16
|
-
import { ReportOpPerfTelemetry
|
|
17
|
-
import { PendingStateManager
|
|
18
|
-
import { BatchManager } from "./batchManager";
|
|
16
|
+
import { ReportOpPerfTelemetry } from "./connectionTelemetry";
|
|
17
|
+
import { PendingStateManager } from "./pendingStateManager";
|
|
19
18
|
import { pkgVersion } from "./packageVersion";
|
|
20
19
|
import { BlobManager } from "./blobManager";
|
|
21
20
|
import { DataStores, getSummaryForDatastores } from "./dataStores";
|
|
22
21
|
import { aliasBlobName, blobsTreeName, chunksBlobName, electedSummarizerBlobName, extractSummaryMetadataMessage, metadataBlobName, wrapSummaryInChannelsTree, } from "./summaryFormat";
|
|
23
22
|
import { SummaryCollection } from "./summaryCollection";
|
|
24
|
-
import { OrderedClientCollection, OrderedClientElection } from "./orderedClientElection";
|
|
23
|
+
import { OrderedClientCollection, OrderedClientElection, } from "./orderedClientElection";
|
|
25
24
|
import { SummarizerClientElection, summarizerClientType } from "./summarizerClientElection";
|
|
26
25
|
import { formExponentialFn, Throttler } from "./throttler";
|
|
27
26
|
import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
|
|
28
|
-
import { GarbageCollector, GCNodeType,
|
|
29
|
-
import { channelToDataStore, isDataStoreAliasMessage
|
|
27
|
+
import { GarbageCollector, GCNodeType, } from "./garbageCollection";
|
|
28
|
+
import { channelToDataStore, isDataStoreAliasMessage } from "./dataStore";
|
|
30
29
|
import { BindBatchTracker } from "./batchTracker";
|
|
31
|
-
import { SerializedSnapshotStorage } from "./serializedSnapshotStorage";
|
|
30
|
+
import { SerializedSnapshotStorage, } from "./serializedSnapshotStorage";
|
|
32
31
|
import { ScheduleManager } from "./scheduleManager";
|
|
33
|
-
import { OpDecompressor } from "./
|
|
32
|
+
import { OpCompressor, OpDecompressor, Outbox, OpSplitter, RemoteMessageProcessor, } from "./opLifecycle";
|
|
34
33
|
export var ContainerMessageType;
|
|
35
34
|
(function (ContainerMessageType) {
|
|
36
35
|
// An op to be delivered to store
|
|
@@ -56,7 +55,6 @@ export const DefaultSummaryConfiguration = {
|
|
|
56
55
|
maxAckWaitTime: 10 * 60 * 1000,
|
|
57
56
|
maxOpsSinceLastSummary: 7000,
|
|
58
57
|
initialSummarizerDelayMs: 5 * 1000,
|
|
59
|
-
summarizerClientElection: false,
|
|
60
58
|
nonRuntimeOpWeight: 0.1,
|
|
61
59
|
runtimeOpWeight: 1.0,
|
|
62
60
|
nonRuntimeHeuristicThreshold: 20,
|
|
@@ -76,6 +74,17 @@ export var RuntimeHeaders;
|
|
|
76
74
|
/** True if the request is coming from an IFluidHandle. */
|
|
77
75
|
RuntimeHeaders["viaHandle"] = "viaHandle";
|
|
78
76
|
})(RuntimeHeaders || (RuntimeHeaders = {}));
|
|
77
|
+
/** True if a tombstoned object should be returned without erroring */
|
|
78
|
+
export const AllowTombstoneRequestHeaderKey = "allowTombstone"; // Belongs in the enum above, but avoiding the breaking change
|
|
79
|
+
/** Tombstone error responses will have this header set to true */
|
|
80
|
+
export const TombstoneResponseHeaderKey = "isTombstoned";
|
|
81
|
+
/** Default values for Runtime Headers */
|
|
82
|
+
export const defaultRuntimeHeaderData = {
|
|
83
|
+
wait: true,
|
|
84
|
+
externalRequest: false,
|
|
85
|
+
viaHandle: false,
|
|
86
|
+
allowTombstone: false,
|
|
87
|
+
};
|
|
79
88
|
/**
|
|
80
89
|
* Available compression algorithms for op compression.
|
|
81
90
|
*/
|
|
@@ -109,37 +118,6 @@ export var RuntimeMessage;
|
|
|
109
118
|
export function isRuntimeMessage(message) {
|
|
110
119
|
return Object.values(RuntimeMessage).includes(message.type);
|
|
111
120
|
}
|
|
112
|
-
/**
|
|
113
|
-
* Unpacks runtime messages
|
|
114
|
-
*
|
|
115
|
-
* @remarks This API makes no promises regarding backward-compatibility. This is internal API.
|
|
116
|
-
* @param message - message (as it observed in storage / service)
|
|
117
|
-
* @returns unpacked runtime message
|
|
118
|
-
*
|
|
119
|
-
* @internal
|
|
120
|
-
*/
|
|
121
|
-
export function unpackRuntimeMessage(message) {
|
|
122
|
-
if (message.type === MessageType.Operation) {
|
|
123
|
-
// legacy op format?
|
|
124
|
-
if (message.contents.address !== undefined && message.contents.type === undefined) {
|
|
125
|
-
message.type = ContainerMessageType.FluidDataStoreOp;
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
// new format
|
|
129
|
-
const innerContents = message.contents;
|
|
130
|
-
message.type = innerContents.type;
|
|
131
|
-
message.contents = innerContents.contents;
|
|
132
|
-
}
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
// Legacy format, but it's already "unpacked",
|
|
137
|
-
// i.e. message.type is actually ContainerMessageType.
|
|
138
|
-
// Or it's non-runtime message.
|
|
139
|
-
// Nothing to do in such case.
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
121
|
/**
|
|
144
122
|
* Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
|
|
145
123
|
* special-case for document dirty state. Ultimately we should have no special-cases from the
|
|
@@ -156,8 +134,7 @@ export function getDeviceSpec() {
|
|
|
156
134
|
};
|
|
157
135
|
}
|
|
158
136
|
}
|
|
159
|
-
catch (_a) {
|
|
160
|
-
}
|
|
137
|
+
catch (_a) { }
|
|
161
138
|
return {};
|
|
162
139
|
}
|
|
163
140
|
/**
|
|
@@ -165,6 +142,9 @@ export function getDeviceSpec() {
|
|
|
165
142
|
* It will define the store level mappings.
|
|
166
143
|
*/
|
|
167
144
|
export class ContainerRuntime extends TypedEventEmitter {
|
|
145
|
+
/**
|
|
146
|
+
* @internal
|
|
147
|
+
*/
|
|
168
148
|
constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
|
|
169
149
|
var _a, _b, _c, _d, _e, _f;
|
|
170
150
|
if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
|
|
@@ -177,12 +157,19 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
177
157
|
this._storage = _storage;
|
|
178
158
|
this.requestHandler = requestHandler;
|
|
179
159
|
this.summaryConfiguration = summaryConfiguration;
|
|
180
|
-
this.opDecompressor = new OpDecompressor();
|
|
181
160
|
this.defaultMaxConsecutiveReconnects = 7;
|
|
182
161
|
this._orderSequentiallyCalls = 0;
|
|
183
162
|
this.flushMicroTaskExists = false;
|
|
184
163
|
this.savedOps = [];
|
|
185
164
|
this.consecutiveReconnects = 0;
|
|
165
|
+
this.ensureNoDataModelChangesCalls = 0;
|
|
166
|
+
/**
|
|
167
|
+
* Tracks the number of detected reentrant ops to report,
|
|
168
|
+
* in order to self-throttle the telemetry events.
|
|
169
|
+
*
|
|
170
|
+
* This should be removed as part of ADO:2322
|
|
171
|
+
*/
|
|
172
|
+
this.opReentryCallsToReport = 5;
|
|
186
173
|
this._disposed = false;
|
|
187
174
|
this.emitDirtyDocumentEvent = true;
|
|
188
175
|
this.defaultTelemetrySignalSampleCount = 100;
|
|
@@ -242,38 +229,31 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
242
229
|
this.nextSummaryNumber = loadSummaryNumber + 1;
|
|
243
230
|
this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
|
|
244
231
|
this._connected = this.context.connected;
|
|
245
|
-
this.chunkMap = new Map(chunks);
|
|
246
|
-
this.handleContext = new ContainerFluidHandleContext("", this);
|
|
247
232
|
this.mc = loggerToMonitoringContext(ChildLogger.create(this.logger, "ContainerRuntime"));
|
|
233
|
+
const opSplitter = new OpSplitter(chunks, this.context.submitBatchFn, this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableCompressionChunking") === true
|
|
234
|
+
? Number.POSITIVE_INFINITY
|
|
235
|
+
: runtimeOptions.chunkSizeInBytes, runtimeOptions.maxBatchSizeInBytes, this.mc.logger);
|
|
236
|
+
this.remoteMessageProcessor = new RemoteMessageProcessor(opSplitter, new OpDecompressor());
|
|
237
|
+
this.handleContext = new ContainerFluidHandleContext("", this);
|
|
248
238
|
if (this.summaryConfiguration.state === "enabled") {
|
|
249
239
|
this.validateSummaryHeuristicConfiguration(this.summaryConfiguration);
|
|
250
240
|
}
|
|
241
|
+
this.enableOpReentryCheck =
|
|
242
|
+
runtimeOptions.enableOpReentryCheck === true &&
|
|
243
|
+
// Allow for a break-glass config to override the options
|
|
244
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableOpReentryCheck") !== true;
|
|
251
245
|
this.summariesDisabled = this.isSummariesDisabled();
|
|
252
246
|
this.heuristicsDisabled = this.isHeuristicsDisabled();
|
|
253
|
-
this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
|
|
254
247
|
this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
|
|
255
248
|
this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
|
|
256
249
|
this.maxConsecutiveReconnects =
|
|
257
250
|
(_c = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _c !== void 0 ? _c : this.defaultMaxConsecutiveReconnects;
|
|
258
251
|
this._flushMode = runtimeOptions.flushMode;
|
|
259
|
-
// Provide lower soft limit - we want to have some number of ops to get efficiency in compression
|
|
260
|
-
// & bandwidth usage, but at the same time we want to send these ops sooner, to reduce overall
|
|
261
|
-
// latency of processing a batch.
|
|
262
|
-
// So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
|
|
263
|
-
// payloads. That number represents final (compressed) bits (once compression is implemented).
|
|
264
|
-
this.pendingAttachBatch = new BatchManager(this.mc.logger, {
|
|
265
|
-
hardLimit: runtimeOptions.maxBatchSizeInBytes,
|
|
266
|
-
softLimit: 64 * 1024,
|
|
267
|
-
compressionOptions: runtimeOptions.compressionOptions
|
|
268
|
-
});
|
|
269
|
-
this.pendingBatch = new BatchManager(this.mc.logger, {
|
|
270
|
-
hardLimit: runtimeOptions.maxBatchSizeInBytes,
|
|
271
|
-
compressionOptions: runtimeOptions.compressionOptions
|
|
272
|
-
});
|
|
273
252
|
const pendingRuntimeState = context.pendingLocalState;
|
|
274
253
|
const baseSnapshot = (_d = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _d !== void 0 ? _d : context.baseSnapshot;
|
|
275
254
|
const maxSnapshotCacheDurationMs = (_f = (_e = this._storage) === null || _e === void 0 ? void 0 : _e.policies) === null || _f === void 0 ? void 0 : _f.maximumCacheDurationMs;
|
|
276
|
-
if (maxSnapshotCacheDurationMs !== undefined &&
|
|
255
|
+
if (maxSnapshotCacheDurationMs !== undefined &&
|
|
256
|
+
maxSnapshotCacheDurationMs > 5 * 24 * 60 * 60 * 1000) {
|
|
277
257
|
// This is a runtime enforcement of what's already explicit in the policy's type itself,
|
|
278
258
|
// which dictates the value is either undefined or exactly 5 days in ms.
|
|
279
259
|
// As long as the actual value is less than 5 days, the assumptions GC makes here are valid.
|
|
@@ -310,34 +290,59 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
310
290
|
throwOnFailure: true,
|
|
311
291
|
// If GC should not run, let the summarizer node know so that it does not track GC state.
|
|
312
292
|
gcDisabled: !this.garbageCollector.shouldRunGC,
|
|
313
|
-
}
|
|
293
|
+
},
|
|
294
|
+
// Function to get GC data if needed. This will always be called by the root summarizer node to get GC data.
|
|
295
|
+
async (fullGC) => this.getGCDataInternal(fullGC),
|
|
296
|
+
// Function to get the GC details from the base snapshot we loaded from.
|
|
297
|
+
async () => this.garbageCollector.getBaseGCDetails());
|
|
314
298
|
if (baseSnapshot) {
|
|
315
299
|
this.summarizerNode.updateBaseSummaryState(baseSnapshot);
|
|
316
300
|
}
|
|
317
301
|
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));
|
|
318
|
-
this.blobManager = new BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (
|
|
302
|
+
this.blobManager = new BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (localId, blobId) => {
|
|
319
303
|
if (!this.disposed) {
|
|
320
|
-
this.submit(ContainerMessageType.BlobAttach, undefined, undefined, {
|
|
304
|
+
this.submit(ContainerMessageType.BlobAttach, undefined, undefined, {
|
|
305
|
+
localId,
|
|
306
|
+
blobId,
|
|
307
|
+
});
|
|
321
308
|
}
|
|
322
|
-
}, (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pendingAttachmentBlobs);
|
|
309
|
+
}, (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), (fromPath, toPath) => this.garbageCollector.addedOutboundReference(fromPath, toPath), (blobPath) => this.garbageCollector.isNodeDeleted(blobPath), this, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pendingAttachmentBlobs, () => this.getCurrentReferenceTimestampMs());
|
|
323
310
|
this.scheduleManager = new ScheduleManager(context.deltaManager, this, () => this.clientId, ChildLogger.create(this.logger, "ScheduleManager"));
|
|
324
|
-
this.deltaSender = this.deltaManager;
|
|
325
311
|
this.pendingStateManager = new PendingStateManager({
|
|
326
312
|
applyStashedOp: this.applyStashedOp.bind(this),
|
|
327
313
|
clientId: () => this.clientId,
|
|
328
314
|
close: this.closeFn,
|
|
329
315
|
connected: () => this.connected,
|
|
330
|
-
flush: this.flush.bind(this),
|
|
331
316
|
reSubmit: this.reSubmit.bind(this),
|
|
332
317
|
rollback: this.rollback.bind(this),
|
|
333
318
|
orderSequentially: this.orderSequentially.bind(this),
|
|
334
319
|
}, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
|
|
320
|
+
const compressionOptions = this.mc.config.getBoolean("Fluid.ContainerRuntime.DisableCompression") === true
|
|
321
|
+
? {
|
|
322
|
+
minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
|
|
323
|
+
compressionAlgorithm: CompressionAlgorithms.lz4,
|
|
324
|
+
}
|
|
325
|
+
: runtimeOptions.compressionOptions;
|
|
326
|
+
this.outbox = new Outbox({
|
|
327
|
+
shouldSend: () => this.canSendOps(),
|
|
328
|
+
pendingStateManager: this.pendingStateManager,
|
|
329
|
+
containerContext: this.context,
|
|
330
|
+
compressor: new OpCompressor(this.mc.logger),
|
|
331
|
+
splitter: opSplitter,
|
|
332
|
+
config: {
|
|
333
|
+
compressionOptions,
|
|
334
|
+
maxBatchSizeInBytes: runtimeOptions.maxBatchSizeInBytes,
|
|
335
|
+
enableOpReentryCheck: this.enableOpReentryCheck,
|
|
336
|
+
},
|
|
337
|
+
logger: this.mc.logger,
|
|
338
|
+
});
|
|
335
339
|
this.context.quorum.on("removeMember", (clientId) => {
|
|
336
|
-
this.
|
|
340
|
+
this.remoteMessageProcessor.clearPartialMessagesFor(clientId);
|
|
337
341
|
});
|
|
338
342
|
this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
|
|
339
|
-
this.dirtyContainer =
|
|
340
|
-
|
|
343
|
+
this.dirtyContainer =
|
|
344
|
+
this.context.attachState !== AttachState.Attached ||
|
|
345
|
+
this.pendingStateManager.hasPendingMessages();
|
|
341
346
|
this.context.updateDirtyContainerState(this.dirtyContainer);
|
|
342
347
|
if (this.summariesDisabled) {
|
|
343
348
|
this.mc.logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
|
|
@@ -346,7 +351,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
346
351
|
const orderedClientLogger = ChildLogger.create(this.logger, "OrderedClientElection");
|
|
347
352
|
const orderedClientCollection = new OrderedClientCollection(orderedClientLogger, this.context.deltaManager, this.context.quorum);
|
|
348
353
|
const orderedClientElectionForSummarizer = new OrderedClientElection(orderedClientLogger, orderedClientCollection, electedSummarizerData !== null && electedSummarizerData !== void 0 ? electedSummarizerData : this.context.deltaManager.lastSequenceNumber, SummarizerClientElection.isClientEligible);
|
|
349
|
-
this.summarizerClientElection = new SummarizerClientElection(orderedClientLogger, this.summaryCollection, orderedClientElectionForSummarizer, this.maxOpsSinceLastSummary
|
|
354
|
+
this.summarizerClientElection = new SummarizerClientElection(orderedClientLogger, this.summaryCollection, orderedClientElectionForSummarizer, this.maxOpsSinceLastSummary);
|
|
350
355
|
if (this.context.clientDetails.type === summarizerClientType) {
|
|
351
356
|
this._summarizer = new Summarizer("/_summarizer", this /* ISummarizerRuntime */, () => this.summaryConfiguration, this /* ISummarizerInternalsProvider */, this.handleContext, this.summaryCollection, async (runtime) => RunWhileConnectedCoordinator.create(runtime));
|
|
352
357
|
}
|
|
@@ -405,18 +410,53 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
405
410
|
ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
|
|
406
411
|
BindBatchTracker(this, this.logger);
|
|
407
412
|
}
|
|
408
|
-
get IContainerRuntime() {
|
|
409
|
-
|
|
413
|
+
get IContainerRuntime() {
|
|
414
|
+
return this;
|
|
415
|
+
}
|
|
416
|
+
get IFluidRouter() {
|
|
417
|
+
return this;
|
|
418
|
+
}
|
|
410
419
|
/**
|
|
420
|
+
* @deprecated - use loadRuntime instead.
|
|
411
421
|
* Load the stores from a snapshot and returns the runtime.
|
|
412
422
|
* @param context - Context of the container.
|
|
413
423
|
* @param registryEntries - Mapping to the stores.
|
|
414
424
|
* @param requestHandler - Request handlers for the container runtime
|
|
415
425
|
* @param runtimeOptions - Additional options to be passed to the runtime
|
|
416
426
|
* @param existing - (optional) When loading from an existing snapshot. Precedes context.existing if provided
|
|
427
|
+
* @param containerRuntimeCtor - (optional) Constructor to use to create the ContainerRuntime instance. This
|
|
428
|
+
* allows mixin classes to leverage this method to define their own async initializer.
|
|
417
429
|
*/
|
|
418
|
-
static async load(context, registryEntries, requestHandler, runtimeOptions = {}, containerScope = context.scope, existing) {
|
|
419
|
-
|
|
430
|
+
static async load(context, registryEntries, requestHandler, runtimeOptions = {}, containerScope = context.scope, existing, containerRuntimeCtor = ContainerRuntime) {
|
|
431
|
+
let existingFlag = true;
|
|
432
|
+
if (!existing) {
|
|
433
|
+
existingFlag = false;
|
|
434
|
+
}
|
|
435
|
+
return this.loadRuntime({
|
|
436
|
+
context,
|
|
437
|
+
registryEntries,
|
|
438
|
+
existing: existingFlag,
|
|
439
|
+
requestHandler,
|
|
440
|
+
runtimeOptions,
|
|
441
|
+
containerScope,
|
|
442
|
+
containerRuntimeCtor,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Load the stores from a snapshot and returns the runtime.
|
|
447
|
+
* @param params - An object housing the runtime properties:
|
|
448
|
+
* - context - Context of the container.
|
|
449
|
+
* - registryEntries - Mapping to the stores.
|
|
450
|
+
* - existing - When loading from an existing snapshot
|
|
451
|
+
* - requestHandler - Request handlers for the container runtime
|
|
452
|
+
* - runtimeOptions - Additional options to be passed to the runtime
|
|
453
|
+
* - containerScope - runtime services provided with context
|
|
454
|
+
* - containerRuntimeCtor - Constructor to use to create the ContainerRuntime instance.
|
|
455
|
+
* This allows mixin classes to leverage this method to define their own async initializer.
|
|
456
|
+
*/
|
|
457
|
+
static async loadRuntime(params) {
|
|
458
|
+
var _a, _b, _c, _d;
|
|
459
|
+
const { context, registryEntries, existing, requestHandler, runtimeOptions = {}, containerScope = {}, containerRuntimeCtor = ContainerRuntime, } = params;
|
|
420
460
|
// If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
|
|
421
461
|
// back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
|
|
422
462
|
const backCompatContext = context;
|
|
@@ -426,13 +466,17 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
426
466
|
runtimeVersion: pkgVersion,
|
|
427
467
|
},
|
|
428
468
|
});
|
|
429
|
-
const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, compressionOptions = {
|
|
430
|
-
|
|
469
|
+
const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, compressionOptions = {
|
|
470
|
+
minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
|
|
471
|
+
compressionAlgorithm: CompressionAlgorithms.lz4,
|
|
472
|
+
}, maxBatchSizeInBytes = defaultMaxBatchSizeInBytes, chunkSizeInBytes = Number.POSITIVE_INFINITY, enableOpReentryCheck = false, } = runtimeOptions;
|
|
431
473
|
const pendingRuntimeState = context.pendingLocalState;
|
|
432
474
|
const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
|
|
433
|
-
const storage = !pendingRuntimeState
|
|
434
|
-
context.storage
|
|
435
|
-
new SerializedSnapshotStorage(() => {
|
|
475
|
+
const storage = !pendingRuntimeState
|
|
476
|
+
? context.storage
|
|
477
|
+
: new SerializedSnapshotStorage(() => {
|
|
478
|
+
return context.storage;
|
|
479
|
+
}, pendingRuntimeState.snapshotBlobs);
|
|
436
480
|
const registry = new FluidDataStoreRegistry(registryEntries);
|
|
437
481
|
const tryFetchBlob = async (blobName) => {
|
|
438
482
|
const blobId = baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.blobs[blobName];
|
|
@@ -463,7 +507,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
463
507
|
if (!pendingRuntimeState && runtimeSequenceNumber !== undefined) {
|
|
464
508
|
const protocolSequenceNumber = context.deltaManager.initialSequenceNumber;
|
|
465
509
|
// Unless bypass is explicitly set, then take action when sequence numbers mismatch.
|
|
466
|
-
if (loadSequenceNumberVerification !== "bypass" &&
|
|
510
|
+
if (loadSequenceNumberVerification !== "bypass" &&
|
|
511
|
+
runtimeSequenceNumber !== protocolSequenceNumber) {
|
|
467
512
|
// "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
|
|
468
513
|
const error = new DataCorruptionError(
|
|
469
514
|
// pre-0.58 error message: SummaryMetadataMismatch
|
|
@@ -472,11 +517,13 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
472
517
|
logger.sendErrorEvent({ eventName: "SequenceNumberMismatch" }, error);
|
|
473
518
|
}
|
|
474
519
|
else {
|
|
520
|
+
// Call both close and dispose as closeFn implementation will no longer dispose runtime in future
|
|
475
521
|
context.closeFn(error);
|
|
522
|
+
(_d = context.disposeFn) === null || _d === void 0 ? void 0 : _d.call(context, error);
|
|
476
523
|
}
|
|
477
524
|
}
|
|
478
525
|
}
|
|
479
|
-
const runtime = new
|
|
526
|
+
const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks !== null && chunks !== void 0 ? chunks : [], aliases !== null && aliases !== void 0 ? aliases : [], {
|
|
480
527
|
summaryOptions,
|
|
481
528
|
gcOptions,
|
|
482
529
|
loadSequenceNumberVerification,
|
|
@@ -484,6 +531,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
484
531
|
enableOfflineLoad,
|
|
485
532
|
compressionOptions,
|
|
486
533
|
maxBatchSizeInBytes,
|
|
534
|
+
chunkSizeInBytes,
|
|
535
|
+
enableOpReentryCheck,
|
|
487
536
|
}, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
|
|
488
537
|
if (pendingRuntimeState) {
|
|
489
538
|
await runtime.processSavedOps(pendingRuntimeState);
|
|
@@ -513,8 +562,18 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
513
562
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
514
563
|
return this.reSubmit;
|
|
515
564
|
}
|
|
565
|
+
get disposeFn() {
|
|
566
|
+
var _a;
|
|
567
|
+
// In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
|
|
568
|
+
return (_a = this.context.disposeFn) !== null && _a !== void 0 ? _a : this.context.closeFn;
|
|
569
|
+
}
|
|
516
570
|
get closeFn() {
|
|
517
|
-
|
|
571
|
+
// Also call disposeFn to retain functionality of runtime being disposed on close
|
|
572
|
+
return (error) => {
|
|
573
|
+
var _a, _b;
|
|
574
|
+
this.context.closeFn(error);
|
|
575
|
+
(_b = (_a = this.context).disposeFn) === null || _b === void 0 ? void 0 : _b.call(_a, error);
|
|
576
|
+
};
|
|
518
577
|
}
|
|
519
578
|
get flushMode() {
|
|
520
579
|
return this._flushMode;
|
|
@@ -531,6 +590,23 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
531
590
|
get IFluidHandleContext() {
|
|
532
591
|
return this.handleContext;
|
|
533
592
|
}
|
|
593
|
+
/**
|
|
594
|
+
* Invokes the given callback and expects that no ops are submitted
|
|
595
|
+
* until execution finishes. If an op is submitted, an error will be raised.
|
|
596
|
+
*
|
|
597
|
+
* Can be disabled by feature gate `Fluid.ContainerRuntime.DisableOpReentryCheck`
|
|
598
|
+
*
|
|
599
|
+
* @param callback - the callback to be invoked
|
|
600
|
+
*/
|
|
601
|
+
ensureNoDataModelChanges(callback) {
|
|
602
|
+
this.ensureNoDataModelChangesCalls++;
|
|
603
|
+
try {
|
|
604
|
+
return callback();
|
|
605
|
+
}
|
|
606
|
+
finally {
|
|
607
|
+
this.ensureNoDataModelChangesCalls--;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
534
610
|
get connected() {
|
|
535
611
|
return this._connected;
|
|
536
612
|
}
|
|
@@ -539,51 +615,20 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
539
615
|
var _a;
|
|
540
616
|
return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
|
|
541
617
|
}
|
|
542
|
-
get disposed() {
|
|
543
|
-
|
|
544
|
-
return this.pendingBatch.empty && this.pendingAttachBatch.empty;
|
|
618
|
+
get disposed() {
|
|
619
|
+
return this._disposed;
|
|
545
620
|
}
|
|
546
621
|
get summarizer() {
|
|
547
622
|
assert(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
|
|
548
623
|
return this._summarizer;
|
|
549
624
|
}
|
|
550
625
|
isSummariesDisabled() {
|
|
551
|
-
// back-compat: disableSummaries was moved from ISummaryRuntimeOptions
|
|
552
|
-
// to ISummaryConfiguration in 0.60.
|
|
553
|
-
if (this.runtimeOptions.summaryOptions.disableSummaries === true) {
|
|
554
|
-
return true;
|
|
555
|
-
}
|
|
556
626
|
return this.summaryConfiguration.state === "disabled";
|
|
557
627
|
}
|
|
558
628
|
isHeuristicsDisabled() {
|
|
559
|
-
var _a;
|
|
560
|
-
// back-compat: disableHeuristics was moved from ISummarizerOptions
|
|
561
|
-
// to ISummaryConfiguration in 0.60.
|
|
562
|
-
if (((_a = this.runtimeOptions.summaryOptions.summarizerOptions) === null || _a === void 0 ? void 0 : _a.disableHeuristics) === true) {
|
|
563
|
-
return true;
|
|
564
|
-
}
|
|
565
629
|
return this.summaryConfiguration.state === "disableHeuristics";
|
|
566
630
|
}
|
|
567
|
-
isSummarizerClientElectionEnabled() {
|
|
568
|
-
var _a;
|
|
569
|
-
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) {
|
|
570
|
-
return (_a = this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) !== null && _a !== void 0 ? _a : true;
|
|
571
|
-
}
|
|
572
|
-
// back-compat: summarizerClientElection was moved from ISummaryRuntimeOptions
|
|
573
|
-
// to ISummaryConfiguration in 0.60.
|
|
574
|
-
if (this.runtimeOptions.summaryOptions.summarizerClientElection === true) {
|
|
575
|
-
return true;
|
|
576
|
-
}
|
|
577
|
-
return this.summaryConfiguration.state !== "disabled"
|
|
578
|
-
? this.summaryConfiguration.summarizerClientElection === true
|
|
579
|
-
: false;
|
|
580
|
-
}
|
|
581
631
|
getMaxOpsSinceLastSummary() {
|
|
582
|
-
// back-compat: maxOpsSinceLastSummary was moved from ISummaryRuntimeOptions
|
|
583
|
-
// to ISummaryConfiguration in 0.60.
|
|
584
|
-
if (this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary !== undefined) {
|
|
585
|
-
return this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary;
|
|
586
|
-
}
|
|
587
632
|
return this.summaryConfiguration.state !== "disabled"
|
|
588
633
|
? this.summaryConfiguration.maxOpsSinceLastSummary
|
|
589
634
|
: 0;
|
|
@@ -682,7 +727,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
682
727
|
status: 200,
|
|
683
728
|
mimeType: "fluid/object",
|
|
684
729
|
value: blob,
|
|
685
|
-
}
|
|
730
|
+
}
|
|
731
|
+
: create404Response(request);
|
|
686
732
|
}
|
|
687
733
|
else if (requestParser.pathParts.length > 0) {
|
|
688
734
|
const dataStore = await this.getDataStoreFromRequest(id, request);
|
|
@@ -703,16 +749,20 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
703
749
|
return (_a = this.dataStores.aliases.get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
|
|
704
750
|
}
|
|
705
751
|
async getDataStoreFromRequest(id, request) {
|
|
706
|
-
var _a, _b, _c, _d
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
752
|
+
var _a, _b, _c, _d;
|
|
753
|
+
const headerData = {};
|
|
754
|
+
if (typeof ((_a = request.headers) === null || _a === void 0 ? void 0 : _a[RuntimeHeaders.wait]) === "boolean") {
|
|
755
|
+
headerData.wait = request.headers[RuntimeHeaders.wait];
|
|
756
|
+
}
|
|
757
|
+
if (typeof ((_b = request.headers) === null || _b === void 0 ? void 0 : _b[RuntimeHeaders.viaHandle]) === "boolean") {
|
|
758
|
+
headerData.viaHandle = request.headers[RuntimeHeaders.viaHandle];
|
|
759
|
+
}
|
|
760
|
+
if (typeof ((_c = request.headers) === null || _c === void 0 ? void 0 : _c[AllowTombstoneRequestHeaderKey]) === "boolean") {
|
|
761
|
+
headerData.allowTombstone = request.headers[AllowTombstoneRequestHeaderKey];
|
|
762
|
+
}
|
|
713
763
|
await this.dataStores.waitIfPendingAlias(id);
|
|
714
764
|
const internalId = this.internalId(id);
|
|
715
|
-
const dataStoreContext = await this.dataStores.getDataStore(internalId,
|
|
765
|
+
const dataStoreContext = await this.dataStores.getDataStore(internalId, headerData);
|
|
716
766
|
/**
|
|
717
767
|
* If GC should run and this an external app request with "externalRequest" header, we need to return
|
|
718
768
|
* an error if the data store being requested is marked as unreferenced as per the data store's base
|
|
@@ -721,7 +771,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
721
771
|
* This is a workaround to handle scenarios where a data store shared with an external app is deleted
|
|
722
772
|
* and marked as unreferenced by GC. Returning an error will fail to load the data store for the app.
|
|
723
773
|
*/
|
|
724
|
-
if (((
|
|
774
|
+
if (((_d = request.headers) === null || _d === void 0 ? void 0 : _d[RuntimeHeaders.externalRequest]) &&
|
|
775
|
+
this.garbageCollector.shouldRunGC) {
|
|
725
776
|
// The data store is referenced if used routes in the base summary has a route to self.
|
|
726
777
|
// Older documents may not have used routes in the summary. They are considered referenced.
|
|
727
778
|
const usedRoutes = (await dataStoreContext.getBaseGCDetails()).usedRoutes;
|
|
@@ -750,8 +801,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
750
801
|
addContainerStateToSummary(summaryTree, fullTree, trackState, telemetryContext) {
|
|
751
802
|
var _a;
|
|
752
803
|
this.addMetadataToSummary(summaryTree);
|
|
753
|
-
if (this.
|
|
754
|
-
const content = JSON.stringify([...this.
|
|
804
|
+
if (this.remoteMessageProcessor.partialMessages.size > 0) {
|
|
805
|
+
const content = JSON.stringify([...this.remoteMessageProcessor.partialMessages]);
|
|
755
806
|
addBlobToSummary(summaryTree, chunksBlobName, content);
|
|
756
807
|
}
|
|
757
808
|
const dataStoreAliases = this.dataStores.aliases;
|
|
@@ -895,9 +946,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
895
946
|
if (reconnection) {
|
|
896
947
|
this.consecutiveReconnects++;
|
|
897
948
|
if (!this.shouldContinueReconnecting()) {
|
|
898
|
-
this.closeFn(DataProcessingError.create(
|
|
899
|
-
// eslint-disable-next-line max-len
|
|
900
|
-
"Runtime detected too many reconnects with no progress syncing local ops. Batch of ops is likely too large (over 1Mb)", "setConnectionState", undefined, {
|
|
949
|
+
this.closeFn(DataProcessingError.create("Runtime detected too many reconnects with no progress syncing local ops.", "setConnectionState", undefined, {
|
|
901
950
|
dataLoss: 1,
|
|
902
951
|
attempts: this.consecutiveReconnects,
|
|
903
952
|
pendingMessages: this.pendingStateManager.pendingMessagesCount,
|
|
@@ -915,34 +964,23 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
915
964
|
process(messageArg, local) {
|
|
916
965
|
var _a;
|
|
917
966
|
this.verifyNotClosed();
|
|
918
|
-
// Do shallow copy of message, as methods below will modify it.
|
|
919
|
-
// There might be multiple container instances receiving same message
|
|
920
|
-
// We do not need to make deep copy, as each layer will just replace message.content itself,
|
|
921
|
-
// but would not modify contents details
|
|
922
|
-
let message = Object.assign({}, messageArg);
|
|
923
|
-
// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
|
|
924
|
-
// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
|
|
925
|
-
// Old ops may contain empty string (I assume noops).
|
|
926
|
-
if (typeof message.contents === "string" && message.contents !== "") {
|
|
927
|
-
message.contents = JSON.parse(message.contents);
|
|
928
|
-
}
|
|
929
|
-
message = this.opDecompressor.processMessage(message);
|
|
930
|
-
// Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
|
|
931
|
-
// This format was not shipped to production workflows.
|
|
932
|
-
const runtimeMessage = unpackRuntimeMessage(message);
|
|
933
967
|
if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
|
|
934
968
|
this.savedOps.push(messageArg);
|
|
935
969
|
}
|
|
970
|
+
// Whether or not the message is actually a runtime message.
|
|
971
|
+
// It may be a legacy runtime message (ie already unpacked and ContainerMessageType)
|
|
972
|
+
// or something different, like a system message.
|
|
973
|
+
const runtimeMessage = messageArg.type === MessageType.Operation;
|
|
974
|
+
// Do shallow copy of message, as the processing flow will modify it.
|
|
975
|
+
const messageCopy = Object.assign({}, messageArg);
|
|
976
|
+
const message = this.remoteMessageProcessor.process(messageCopy);
|
|
936
977
|
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
937
978
|
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
938
979
|
// messages once a batch has been fully processed.
|
|
939
980
|
this.scheduleManager.beforeOpProcessing(message);
|
|
940
981
|
try {
|
|
941
|
-
// Chunk processing must come first given that we will transform the message to the unchunked version
|
|
942
|
-
// once all pieces are available
|
|
943
|
-
message = this.processRemoteChunkedMessage(message);
|
|
944
982
|
let localOpMetadata;
|
|
945
|
-
if (local && runtimeMessage) {
|
|
983
|
+
if (local && runtimeMessage && message.type !== ContainerMessageType.ChunkedOp) {
|
|
946
984
|
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
947
985
|
}
|
|
948
986
|
// If there are no more pending messages after processing a local message,
|
|
@@ -1015,7 +1053,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1015
1053
|
if (message.clientId === this.clientId && this.connected) {
|
|
1016
1054
|
// Check to see if the signal was lost.
|
|
1017
1055
|
if (this._perfSignalData.trackingSignalSequenceNumber !== undefined &&
|
|
1018
|
-
envelope.clientSignalSequenceNumber >
|
|
1056
|
+
envelope.clientSignalSequenceNumber >
|
|
1057
|
+
this._perfSignalData.trackingSignalSequenceNumber) {
|
|
1019
1058
|
this._perfSignalData.signalsLost++;
|
|
1020
1059
|
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
1021
1060
|
this.logger.sendErrorEvent({
|
|
@@ -1026,7 +1065,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1026
1065
|
clientSignalSequenceNumber: envelope.clientSignalSequenceNumber,
|
|
1027
1066
|
});
|
|
1028
1067
|
}
|
|
1029
|
-
else if (envelope.clientSignalSequenceNumber ===
|
|
1068
|
+
else if (envelope.clientSignalSequenceNumber ===
|
|
1069
|
+
this._perfSignalData.trackingSignalSequenceNumber) {
|
|
1030
1070
|
this.sendSignalTelemetryEvent(envelope.clientSignalSequenceNumber);
|
|
1031
1071
|
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
1032
1072
|
}
|
|
@@ -1044,7 +1084,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1044
1084
|
async getRootDataStoreChannel(id, wait = true) {
|
|
1045
1085
|
await this.dataStores.waitIfPendingAlias(id);
|
|
1046
1086
|
const internalId = this.internalId(id);
|
|
1047
|
-
const context = await this.dataStores.getDataStore(internalId, wait
|
|
1087
|
+
const context = await this.dataStores.getDataStore(internalId, { wait });
|
|
1048
1088
|
assert(await context.isRoot(), 0x12b /* "did not get root data store" */);
|
|
1049
1089
|
return context.realize();
|
|
1050
1090
|
}
|
|
@@ -1054,74 +1094,21 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1054
1094
|
*/
|
|
1055
1095
|
flush() {
|
|
1056
1096
|
assert(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
|
|
1057
|
-
this.
|
|
1058
|
-
|
|
1059
|
-
assert(this.emptyBatch, 0x3cf /* reentrancy */);
|
|
1060
|
-
}
|
|
1061
|
-
flushBatch(batch) {
|
|
1062
|
-
var _a;
|
|
1063
|
-
const length = batch.length;
|
|
1064
|
-
if (length > 1) {
|
|
1065
|
-
batch[0].metadata = Object.assign(Object.assign({}, batch[0].metadata), { batch: true });
|
|
1066
|
-
batch[length - 1].metadata = Object.assign(Object.assign({}, batch[length - 1].metadata), { batch: false });
|
|
1067
|
-
// This assert fires for the following reason (there might be more cases like that):
|
|
1068
|
-
// AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
|
|
1069
|
-
// i.e. in the middle of op processing!
|
|
1070
|
-
// Sending ops while processing ops is not good idea - it's not defined when
|
|
1071
|
-
// referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
|
|
1072
|
-
// If we send ops in response to processing multiple ops, then we for sure hit this assert!
|
|
1073
|
-
// Tracked via ADO #1834
|
|
1074
|
-
// assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
|
|
1075
|
-
// "Batch should be generated synchronously, without processing ops in the middle!");
|
|
1076
|
-
}
|
|
1077
|
-
let clientSequenceNumber = -1;
|
|
1078
|
-
// Did we disconnect in the middle of turn-based batch?
|
|
1079
|
-
// If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
|
|
1080
|
-
if (this.canSendOps()) {
|
|
1081
|
-
if (this.context.submitBatchFn !== undefined) {
|
|
1082
|
-
const batchToSend = [];
|
|
1083
|
-
for (const message of batch) {
|
|
1084
|
-
batchToSend.push({ contents: message.contents, metadata: message.metadata });
|
|
1085
|
-
}
|
|
1086
|
-
// returns clientSequenceNumber of last message in a batch
|
|
1087
|
-
clientSequenceNumber = this.context.submitBatchFn(batchToSend);
|
|
1088
|
-
}
|
|
1089
|
-
else {
|
|
1090
|
-
// Legacy path - supporting old loader versions. Can be removed only when LTS moves above
|
|
1091
|
-
// version that has support for batches (submitBatchFn)
|
|
1092
|
-
for (const message of batch) {
|
|
1093
|
-
// Legacy path doesn't support compressed payloads and will submit uncompressed payload anyways
|
|
1094
|
-
if ((_a = message.metadata) === null || _a === void 0 ? void 0 : _a.compressed) {
|
|
1095
|
-
delete message.metadata.compressed;
|
|
1096
|
-
}
|
|
1097
|
-
clientSequenceNumber = this.context.submitFn(MessageType.Operation, message.deserializedContent, true, // batch
|
|
1098
|
-
message.metadata);
|
|
1099
|
-
}
|
|
1100
|
-
this.deltaSender.flush();
|
|
1101
|
-
}
|
|
1102
|
-
// Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
|
|
1103
|
-
clientSequenceNumber -= batch.length - 1;
|
|
1104
|
-
assert(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
|
|
1105
|
-
}
|
|
1106
|
-
// Let the PendingStateManager know that a message was submitted.
|
|
1107
|
-
// In future, need to shift toward keeping batch as a whole!
|
|
1108
|
-
for (const message of batch) {
|
|
1109
|
-
this.pendingStateManager.onSubmitMessage(message.deserializedContent.type, clientSequenceNumber, message.referenceSequenceNumber, message.deserializedContent.contents, message.localOpMetadata, message.metadata);
|
|
1110
|
-
clientSequenceNumber++;
|
|
1111
|
-
}
|
|
1112
|
-
this.pendingStateManager.onFlush();
|
|
1097
|
+
this.outbox.flush();
|
|
1098
|
+
assert(this.outbox.isEmpty, 0x3cf /* reentrancy */);
|
|
1113
1099
|
}
|
|
1114
1100
|
orderSequentially(callback) {
|
|
1115
1101
|
let checkpoint;
|
|
1102
|
+
let result;
|
|
1116
1103
|
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
|
|
1117
1104
|
// Note: we are not touching this.pendingAttachBatch here, for two reasons:
|
|
1118
1105
|
// 1. It would not help, as we flush attach ops as they become available.
|
|
1119
1106
|
// 2. There is no way to undo process of data store creation.
|
|
1120
|
-
checkpoint = this.
|
|
1107
|
+
checkpoint = this.outbox.checkpoint().mainBatch;
|
|
1121
1108
|
}
|
|
1122
1109
|
try {
|
|
1123
1110
|
this._orderSequentiallyCalls++;
|
|
1124
|
-
callback();
|
|
1111
|
+
result = callback();
|
|
1125
1112
|
}
|
|
1126
1113
|
catch (error) {
|
|
1127
1114
|
if (checkpoint) {
|
|
@@ -1146,9 +1133,11 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1146
1133
|
finally {
|
|
1147
1134
|
this._orderSequentiallyCalls--;
|
|
1148
1135
|
}
|
|
1149
|
-
|
|
1136
|
+
// We don't flush on TurnBased since we expect all messages in the same JS turn to be part of the same batch
|
|
1137
|
+
if (this.flushMode !== FlushMode.TurnBased && this._orderSequentiallyCalls === 0) {
|
|
1150
1138
|
this.flush();
|
|
1151
1139
|
}
|
|
1140
|
+
return result;
|
|
1152
1141
|
}
|
|
1153
1142
|
async createDataStore(pkg) {
|
|
1154
1143
|
const internalId = uuid();
|
|
@@ -1164,7 +1153,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1164
1153
|
return this.dataStores.createDetachedDataStoreCore(pkg, false);
|
|
1165
1154
|
}
|
|
1166
1155
|
async _createDataStoreWithProps(pkg, props, id = uuid()) {
|
|
1167
|
-
const fluidDataStore = await this.dataStores
|
|
1156
|
+
const fluidDataStore = await this.dataStores
|
|
1157
|
+
._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
|
|
1158
|
+
.realize();
|
|
1168
1159
|
return channelToDataStore(fluidDataStore, id, this, this.dataStores, this.mc.logger);
|
|
1169
1160
|
}
|
|
1170
1161
|
async _createDataStore(pkg, id = uuid(), props) {
|
|
@@ -1301,7 +1292,10 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1301
1292
|
}
|
|
1302
1293
|
const telemetryContext = new TelemetryContext();
|
|
1303
1294
|
const { stats, summary } = await this.summarizerNode.summarize(fullTree, trackState, telemetryContext);
|
|
1304
|
-
this.logger.sendTelemetryEvent({
|
|
1295
|
+
this.logger.sendTelemetryEvent({
|
|
1296
|
+
eventName: "SummarizeTelemetry",
|
|
1297
|
+
details: telemetryContext.serialize(),
|
|
1298
|
+
});
|
|
1305
1299
|
assert(summary.type === SummaryType.Tree, 0x12f /* "Container Runtime's summarize should always return a tree" */);
|
|
1306
1300
|
return { stats, summary, gcStats };
|
|
1307
1301
|
}
|
|
@@ -1314,6 +1308,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1314
1308
|
async updateStateBeforeGC() {
|
|
1315
1309
|
return this.dataStores.updateStateBeforeGC();
|
|
1316
1310
|
}
|
|
1311
|
+
async getGCDataInternal(fullGC) {
|
|
1312
|
+
return this.dataStores.getGCData(fullGC);
|
|
1313
|
+
}
|
|
1317
1314
|
/**
|
|
1318
1315
|
* Implementation of IGarbageCollectionRuntime::getGCData.
|
|
1319
1316
|
* Generates and returns the GC data for this container.
|
|
@@ -1321,7 +1318,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1321
1318
|
*/
|
|
1322
1319
|
async getGCData(fullGC) {
|
|
1323
1320
|
const builder = new GCDataBuilder();
|
|
1324
|
-
const dsGCData = await this.
|
|
1321
|
+
const dsGCData = await this.summarizerNode.getGCData(fullGC);
|
|
1325
1322
|
builder.addNodes(dsGCData.gcNodes);
|
|
1326
1323
|
const blobsGCData = this.blobManager.getGCData(fullGC);
|
|
1327
1324
|
builder.addNodes(blobsGCData.gcNodes);
|
|
@@ -1337,37 +1334,26 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1337
1334
|
// summarizing is required and asserted by the the summarizer node. We are the root and are
|
|
1338
1335
|
// always referenced, so the used routes is only self-route (empty string).
|
|
1339
1336
|
this.summarizerNode.updateUsedRoutes([""]);
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
if (route.split("/")[1] !== BlobManager.basePath) {
|
|
1343
|
-
dataStoreUsedRoutes.push(route);
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes);
|
|
1337
|
+
const { dataStoreRoutes } = this.getDataStoreAndBlobManagerRoutes(usedRoutes);
|
|
1338
|
+
this.dataStores.updateUsedRoutes(dataStoreRoutes);
|
|
1347
1339
|
}
|
|
1348
1340
|
/**
|
|
1349
|
-
* This is called to update objects whose routes are unused.
|
|
1350
|
-
*
|
|
1351
|
-
* @param unusedRoutes - The routes that are unused in all data stores in this Container.
|
|
1352
|
-
* @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
|
|
1353
|
-
* are deleted.
|
|
1341
|
+
* This is called to update objects whose routes are unused.
|
|
1342
|
+
* @param unusedRoutes - Data store and attachment blob routes that are unused in this Container.
|
|
1354
1343
|
*/
|
|
1355
|
-
updateUnusedRoutes(unusedRoutes
|
|
1356
|
-
const
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
|
|
1369
|
-
}
|
|
1370
|
-
this.dataStores.updateUnusedRoutes(dataStoreUnusedRoutes, tombstone);
|
|
1344
|
+
updateUnusedRoutes(unusedRoutes) {
|
|
1345
|
+
const { blobManagerRoutes, dataStoreRoutes } = this.getDataStoreAndBlobManagerRoutes(unusedRoutes);
|
|
1346
|
+
this.blobManager.updateUnusedRoutes(blobManagerRoutes);
|
|
1347
|
+
this.dataStores.updateUnusedRoutes(dataStoreRoutes);
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* This is called to update objects that are tombstones.
|
|
1351
|
+
* @param tombstonedRoutes - Data store and attachment blob routes that are tombstones in this Container.
|
|
1352
|
+
*/
|
|
1353
|
+
updateTombstonedRoutes(tombstonedRoutes) {
|
|
1354
|
+
const { blobManagerRoutes, dataStoreRoutes } = this.getDataStoreAndBlobManagerRoutes(tombstonedRoutes);
|
|
1355
|
+
this.blobManager.updateTombstonedRoutes(blobManagerRoutes);
|
|
1356
|
+
this.dataStores.updateTombstonedRoutes(dataStoreRoutes);
|
|
1371
1357
|
}
|
|
1372
1358
|
/**
|
|
1373
1359
|
* Returns a server generated referenced timestamp to be used to track unreferenced nodes by GC.
|
|
@@ -1396,7 +1382,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1396
1382
|
async getGCNodePackagePath(nodePath) {
|
|
1397
1383
|
switch (this.getNodeType(nodePath)) {
|
|
1398
1384
|
case GCNodeType.Blob:
|
|
1399
|
-
return [
|
|
1385
|
+
return [BlobManager.basePath];
|
|
1400
1386
|
case GCNodeType.DataStore:
|
|
1401
1387
|
case GCNodeType.SubDataStore:
|
|
1402
1388
|
return this.dataStores.getDataStorePackagePath(nodePath);
|
|
@@ -1414,6 +1400,25 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1414
1400
|
}
|
|
1415
1401
|
return true;
|
|
1416
1402
|
}
|
|
1403
|
+
/**
|
|
1404
|
+
* From a given list of routes, separate and return routes that belong to blob manager and data stores.
|
|
1405
|
+
* @param routes - A list of routes that can belong to data stores or blob manager.
|
|
1406
|
+
* @returns - Two route lists - One that contains routes for blob manager and another one that contains routes
|
|
1407
|
+
* for data stores.
|
|
1408
|
+
*/
|
|
1409
|
+
getDataStoreAndBlobManagerRoutes(routes) {
|
|
1410
|
+
const blobManagerRoutes = [];
|
|
1411
|
+
const dataStoreRoutes = [];
|
|
1412
|
+
for (const route of routes) {
|
|
1413
|
+
if (this.isBlobPath(route)) {
|
|
1414
|
+
blobManagerRoutes.push(route);
|
|
1415
|
+
}
|
|
1416
|
+
else {
|
|
1417
|
+
dataStoreRoutes.push(route);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return { blobManagerRoutes, dataStoreRoutes };
|
|
1421
|
+
}
|
|
1417
1422
|
/**
|
|
1418
1423
|
* Runs garbage collection and updates the reference / used state of the nodes in the container.
|
|
1419
1424
|
* @returns the statistics of the garbage collection run; undefined if GC did not run.
|
|
@@ -1447,7 +1452,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1447
1452
|
const summaryNumberLogger = ChildLogger.create(summaryLogger, undefined, {
|
|
1448
1453
|
all: { summaryNumber },
|
|
1449
1454
|
});
|
|
1450
|
-
assert(this.
|
|
1455
|
+
assert(this.outbox.isEmpty, 0x3d1 /* Can't trigger summary in the middle of a batch */);
|
|
1451
1456
|
let latestSnapshotVersionId;
|
|
1452
1457
|
if (refreshLatestAck) {
|
|
1453
1458
|
const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
|
|
@@ -1485,7 +1490,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1485
1490
|
if (this.deltaManager.lastSequenceNumber !== summaryRefSeqNum) {
|
|
1486
1491
|
return {
|
|
1487
1492
|
continue: false,
|
|
1488
|
-
// eslint-disable-next-line max-len
|
|
1489
1493
|
error: `lastSequenceNumber changed before uploading to storage. ${this.deltaManager.lastSequenceNumber} !== ${summaryRefSeqNum}`,
|
|
1490
1494
|
};
|
|
1491
1495
|
}
|
|
@@ -1493,7 +1497,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1493
1497
|
if (lastAck !== this.summaryCollection.latestAck) {
|
|
1494
1498
|
return {
|
|
1495
1499
|
continue: false,
|
|
1496
|
-
// eslint-disable-next-line max-len
|
|
1497
1500
|
error: `Last summary changed while summarizing. ${this.summaryCollection.latestAck} !== ${lastAck}`,
|
|
1498
1501
|
};
|
|
1499
1502
|
}
|
|
@@ -1560,8 +1563,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1560
1563
|
// latestSnapshotVersionId from storage and it does not match with the lastAck ackHandle, then use
|
|
1561
1564
|
// the one fetched from storage as parent as that is the latest.
|
|
1562
1565
|
let summaryContext;
|
|
1563
|
-
if ((lastAck === null || lastAck === void 0 ? void 0 : lastAck.summaryAck.contents.handle) !== latestSnapshotVersionId
|
|
1564
|
-
|
|
1566
|
+
if ((lastAck === null || lastAck === void 0 ? void 0 : lastAck.summaryAck.contents.handle) !== latestSnapshotVersionId &&
|
|
1567
|
+
latestSnapshotVersionId !== undefined) {
|
|
1565
1568
|
summaryContext = {
|
|
1566
1569
|
proposalHandle: undefined,
|
|
1567
1570
|
ackHandle: latestSnapshotVersionId,
|
|
@@ -1620,40 +1623,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1620
1623
|
this.deltaManager.inbound.resume();
|
|
1621
1624
|
}
|
|
1622
1625
|
}
|
|
1623
|
-
processRemoteChunkedMessage(message) {
|
|
1624
|
-
if (message.type !== ContainerMessageType.ChunkedOp) {
|
|
1625
|
-
return message;
|
|
1626
|
-
}
|
|
1627
|
-
const clientId = message.clientId;
|
|
1628
|
-
const chunkedContent = message.contents;
|
|
1629
|
-
this.addChunk(clientId, chunkedContent);
|
|
1630
|
-
if (chunkedContent.chunkId === chunkedContent.totalChunks) {
|
|
1631
|
-
const newMessage = Object.assign({}, message);
|
|
1632
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1633
|
-
const serializedContent = this.chunkMap.get(clientId).join("");
|
|
1634
|
-
newMessage.contents = JSON.parse(serializedContent);
|
|
1635
|
-
newMessage.type = chunkedContent.originalType;
|
|
1636
|
-
this.clearPartialChunks(clientId);
|
|
1637
|
-
return newMessage;
|
|
1638
|
-
}
|
|
1639
|
-
return message;
|
|
1640
|
-
}
|
|
1641
|
-
addChunk(clientId, chunkedContent) {
|
|
1642
|
-
let map = this.chunkMap.get(clientId);
|
|
1643
|
-
if (map === undefined) {
|
|
1644
|
-
map = [];
|
|
1645
|
-
this.chunkMap.set(clientId, map);
|
|
1646
|
-
}
|
|
1647
|
-
assert(chunkedContent.chunkId === map.length + 1, 0x131 /* "Mismatch between new chunkId and expected chunkMap" */); // 1-based indexing
|
|
1648
|
-
map.push(chunkedContent.contents);
|
|
1649
|
-
}
|
|
1650
|
-
clearPartialChunks(clientId) {
|
|
1651
|
-
if (this.chunkMap.has(clientId)) {
|
|
1652
|
-
this.chunkMap.delete(clientId);
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
1626
|
hasPendingMessages() {
|
|
1656
|
-
return this.pendingStateManager.hasPendingMessages() || !this.
|
|
1627
|
+
return this.pendingStateManager.hasPendingMessages() || !this.outbox.isEmpty;
|
|
1657
1628
|
}
|
|
1658
1629
|
updateDocumentDirtyState(dirty) {
|
|
1659
1630
|
if (this.attachState !== AttachState.Attached) {
|
|
@@ -1691,14 +1662,17 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1691
1662
|
return this.blobManager.createBlob(blob);
|
|
1692
1663
|
}
|
|
1693
1664
|
submit(type, contents, localOpMetadata = undefined, metadata = undefined) {
|
|
1694
|
-
var _a, _b, _c, _d;
|
|
1695
1665
|
this.verifyNotClosed();
|
|
1666
|
+
this.verifyCanSubmitOps();
|
|
1696
1667
|
// There should be no ops in detached container state!
|
|
1697
1668
|
assert(this.attachState !== AttachState.Detached, 0x132 /* "sending ops in detached container" */);
|
|
1698
1669
|
const deserializedContent = { type, contents };
|
|
1699
1670
|
const serializedContent = JSON.stringify(deserializedContent);
|
|
1700
1671
|
if (this.deltaManager.readOnlyInfo.readonly) {
|
|
1701
|
-
this.logger.
|
|
1672
|
+
this.logger.sendTelemetryEvent({
|
|
1673
|
+
eventName: "SubmitOpInReadonly",
|
|
1674
|
+
connected: this.connected,
|
|
1675
|
+
});
|
|
1702
1676
|
}
|
|
1703
1677
|
const message = {
|
|
1704
1678
|
contents: serializedContent,
|
|
@@ -1728,44 +1702,28 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1728
1702
|
// issue than sending.
|
|
1729
1703
|
// Please note that this does not change file format, so it can be disabled in the future if this
|
|
1730
1704
|
// optimization no longer makes sense (for example, batch compression may make it less appealing).
|
|
1731
|
-
if (this.currentlyBatching() &&
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
// when queue is not empty.
|
|
1736
|
-
// Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
|
|
1737
|
-
this.flushBatch(this.pendingAttachBatch.popBatch());
|
|
1738
|
-
if (!this.pendingAttachBatch.push(message)) {
|
|
1739
|
-
throw new GenericError("BatchTooLarge",
|
|
1740
|
-
/* error */ undefined, {
|
|
1741
|
-
opSize: (_b = ((_a = message.contents) === null || _a === void 0 ? void 0 : _a.length)) !== null && _b !== void 0 ? _b : 0,
|
|
1742
|
-
count: this.pendingAttachBatch.length,
|
|
1743
|
-
limit: this.pendingAttachBatch.options.hardLimit,
|
|
1744
|
-
});
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1705
|
+
if (this.currentlyBatching() &&
|
|
1706
|
+
type === ContainerMessageType.Attach &&
|
|
1707
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
|
|
1708
|
+
this.outbox.submitAttach(message);
|
|
1747
1709
|
}
|
|
1748
1710
|
else {
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1711
|
+
this.outbox.submit(message);
|
|
1712
|
+
}
|
|
1713
|
+
if (!this.currentlyBatching()) {
|
|
1714
|
+
this.flush();
|
|
1715
|
+
}
|
|
1716
|
+
else if (!this.flushMicroTaskExists) {
|
|
1717
|
+
this.flushMicroTaskExists = true;
|
|
1718
|
+
// Queue a microtask to detect the end of the turn and force a flush.
|
|
1719
|
+
Promise.resolve()
|
|
1720
|
+
.then(() => {
|
|
1721
|
+
this.flushMicroTaskExists = false;
|
|
1758
1722
|
this.flush();
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
this.
|
|
1762
|
-
|
|
1763
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1764
|
-
Promise.resolve().then(() => {
|
|
1765
|
-
this.flushMicroTaskExists = false;
|
|
1766
|
-
this.flush();
|
|
1767
|
-
});
|
|
1768
|
-
}
|
|
1723
|
+
})
|
|
1724
|
+
.catch((error) => {
|
|
1725
|
+
this.closeFn(error);
|
|
1726
|
+
});
|
|
1769
1727
|
}
|
|
1770
1728
|
}
|
|
1771
1729
|
catch (error) {
|
|
@@ -1780,7 +1738,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1780
1738
|
this.verifyNotClosed();
|
|
1781
1739
|
assert(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
|
|
1782
1740
|
// System message should not be sent in the middle of the batch.
|
|
1783
|
-
assert(this.
|
|
1741
|
+
assert(this.outbox.isEmpty, 0x3d4 /* System op in the middle of a batch */);
|
|
1784
1742
|
// back-compat: ADO #1385: Make this call unconditional in the future
|
|
1785
1743
|
return this.context.submitSummaryFn !== undefined
|
|
1786
1744
|
? this.context.submitSummaryFn(contents)
|
|
@@ -1795,6 +1753,32 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1795
1753
|
throw new Error("Runtime is closed");
|
|
1796
1754
|
}
|
|
1797
1755
|
}
|
|
1756
|
+
verifyCanSubmitOps() {
|
|
1757
|
+
if (this.ensureNoDataModelChangesCalls > 0) {
|
|
1758
|
+
const errorMessage = "Op was submitted from within a `ensureNoDataModelChanges` callback";
|
|
1759
|
+
if (this.opReentryCallsToReport > 0) {
|
|
1760
|
+
this.mc.logger.sendTelemetryEvent({ eventName: "OpReentry" },
|
|
1761
|
+
// We need to capture the call stack in order to inspect the source of this usage pattern
|
|
1762
|
+
new UsageError(errorMessage));
|
|
1763
|
+
this.opReentryCallsToReport--;
|
|
1764
|
+
}
|
|
1765
|
+
// Creating ops while processing ops can lead
|
|
1766
|
+
// to undefined behavior and events observed in the wrong order.
|
|
1767
|
+
// For example, we have two callbacks registered for a DDS, A and B.
|
|
1768
|
+
// Then if on change #1 callback A creates change #2, the invocation flow will be:
|
|
1769
|
+
//
|
|
1770
|
+
// A because of #1
|
|
1771
|
+
// A because of #2
|
|
1772
|
+
// B because of #2
|
|
1773
|
+
// B because of #1
|
|
1774
|
+
//
|
|
1775
|
+
// The runtime must enforce op coherence by not allowing ops to be submitted
|
|
1776
|
+
// while ops are being processed.
|
|
1777
|
+
if (this.enableOpReentryCheck) {
|
|
1778
|
+
throw new UsageError(errorMessage);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1798
1782
|
/**
|
|
1799
1783
|
* Finds the right store and asks it to resubmit the message. This typically happens when we
|
|
1800
1784
|
* reconnect and there are pending messages.
|
|
@@ -1854,13 +1838,32 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1854
1838
|
// It should only be done by the summarizerNode, if required.
|
|
1855
1839
|
// When fetching from storage we will always get the latest version and do not use the ackHandle.
|
|
1856
1840
|
const snapshotTreeFetcher = async () => {
|
|
1857
|
-
const fetchResult = await this.
|
|
1841
|
+
const fetchResult = await this.fetchLatestSnapshotFromStorage(summaryLogger, {
|
|
1858
1842
|
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
1859
1843
|
ackHandle,
|
|
1860
1844
|
summaryRefSeq,
|
|
1861
1845
|
fetchLatest: true,
|
|
1862
1846
|
});
|
|
1863
1847
|
const latestSnapshotRefSeq = await seqFromTree(fetchResult.snapshotTree, readAndParseBlob);
|
|
1848
|
+
/**
|
|
1849
|
+
* If the fetched snapshot is older than the one for which the ack was received, close the container.
|
|
1850
|
+
* This should never happen because an ack should be sent after the latest summary is updated in the server.
|
|
1851
|
+
* However, there are couple of scenarios where it's possible:
|
|
1852
|
+
* 1. A file was modified externally resulting in modifying the snapshot's sequence number. This can lead to
|
|
1853
|
+
* the document being unusable and we should not proceed.
|
|
1854
|
+
* 2. The server DB failed after the ack was sent which may delete the corresponding snapshot. Ideally, in
|
|
1855
|
+
* such cases, the file will be rolled back along with the ack and we will eventually reach a consistent
|
|
1856
|
+
* state.
|
|
1857
|
+
*/
|
|
1858
|
+
if (latestSnapshotRefSeq < summaryRefSeq) {
|
|
1859
|
+
const error = DataProcessingError.create("Fetched snapshot is older than the received ack", "RefreshLatestSummaryAck", undefined /* sequencedMessage */, {
|
|
1860
|
+
ackHandle,
|
|
1861
|
+
summaryRefSeq,
|
|
1862
|
+
latestSnapshotRefSeq,
|
|
1863
|
+
});
|
|
1864
|
+
this.closeFn(error);
|
|
1865
|
+
throw error;
|
|
1866
|
+
}
|
|
1864
1867
|
summaryLogger.sendTelemetryEvent({
|
|
1865
1868
|
eventName: "LatestSummaryRetrieved",
|
|
1866
1869
|
ackHandle,
|
|
@@ -1874,7 +1877,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1874
1877
|
};
|
|
1875
1878
|
const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, snapshotTreeFetcher, readAndParseBlob, summaryLogger);
|
|
1876
1879
|
// Notify the garbage collector so it can update its latest summary state.
|
|
1877
|
-
await this.garbageCollector.
|
|
1880
|
+
await this.garbageCollector.refreshLatestSummary(result, proposalHandle, summaryRefSeq, readAndParseBlob);
|
|
1878
1881
|
}
|
|
1879
1882
|
/**
|
|
1880
1883
|
* Fetches the latest snapshot from storage and uses it to refresh SummarizerNode's
|
|
@@ -1883,22 +1886,22 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1883
1886
|
* @returns downloaded snapshot's reference sequence number
|
|
1884
1887
|
*/
|
|
1885
1888
|
async refreshLatestSummaryAckFromServer(summaryLogger) {
|
|
1886
|
-
const { snapshotTree, versionId } = await this.
|
|
1889
|
+
const { snapshotTree, versionId } = await this.fetchLatestSnapshotFromStorage(summaryLogger, {
|
|
1887
1890
|
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
1888
1891
|
fetchLatest: true,
|
|
1889
|
-
}
|
|
1892
|
+
});
|
|
1890
1893
|
const readAndParseBlob = async (id) => readAndParse(this.storage, id);
|
|
1891
1894
|
const latestSnapshotRefSeq = await seqFromTree(snapshotTree, readAndParseBlob);
|
|
1892
1895
|
const result = await this.summarizerNode.refreshLatestSummary(undefined, latestSnapshotRefSeq, async () => snapshotTree, readAndParseBlob, summaryLogger);
|
|
1893
1896
|
// Notify the garbage collector so it can update its latest summary state.
|
|
1894
|
-
await this.garbageCollector.
|
|
1897
|
+
await this.garbageCollector.refreshLatestSummary(result, undefined, latestSnapshotRefSeq, readAndParseBlob);
|
|
1895
1898
|
return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
|
|
1896
1899
|
}
|
|
1897
|
-
async
|
|
1900
|
+
async fetchLatestSnapshotFromStorage(logger, event) {
|
|
1898
1901
|
return PerformanceEvent.timedExecAsync(logger, event, async (perfEvent) => {
|
|
1899
1902
|
const stats = {};
|
|
1900
1903
|
const trace = Trace.start();
|
|
1901
|
-
const versions = await this.storage.getVersions(
|
|
1904
|
+
const versions = await this.storage.getVersions(null, 1, "refreshLatestSummaryAckFromServer", FetchSource.noCache);
|
|
1902
1905
|
assert(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
|
|
1903
1906
|
stats.getVersionDuration = trace.trace().duration;
|
|
1904
1907
|
const maybeSnapshot = await this.storage.getSnapshotTree(versions[0]);
|
|
@@ -1911,13 +1914,15 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1911
1914
|
notifyAttaching(snapshot) {
|
|
1912
1915
|
var _a;
|
|
1913
1916
|
if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
|
|
1914
|
-
this.baseSnapshotBlobs =
|
|
1917
|
+
this.baseSnapshotBlobs =
|
|
1918
|
+
SerializedSnapshotStorage.serializeTreeWithBlobContents(snapshot);
|
|
1915
1919
|
}
|
|
1916
1920
|
}
|
|
1917
1921
|
async initializeBaseSnapshotBlobs() {
|
|
1918
1922
|
var _a;
|
|
1919
1923
|
if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) ||
|
|
1920
|
-
this.attachState !== AttachState.Attached ||
|
|
1924
|
+
this.attachState !== AttachState.Attached ||
|
|
1925
|
+
this.context.pendingLocalState) {
|
|
1921
1926
|
return;
|
|
1922
1927
|
}
|
|
1923
1928
|
assert(!!this.context.baseSnapshot, 0x2e5 /* "Must have a base snapshot" */);
|
|
@@ -1928,13 +1933,13 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1928
1933
|
if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad)) {
|
|
1929
1934
|
throw new UsageError("can't get state when offline load disabled");
|
|
1930
1935
|
}
|
|
1936
|
+
if (this._orderSequentiallyCalls !== 0) {
|
|
1937
|
+
throw new UsageError("can't get state during orderSequentially");
|
|
1938
|
+
}
|
|
1931
1939
|
// Flush pending batch.
|
|
1932
1940
|
// getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
|
|
1933
1941
|
// to close current batch.
|
|
1934
1942
|
this.flush();
|
|
1935
|
-
if (this._orderSequentiallyCalls !== 0) {
|
|
1936
|
-
throw new UsageError("can't get state during orderSequentially");
|
|
1937
|
-
}
|
|
1938
1943
|
const previousPendingState = this.context.pendingLocalState;
|
|
1939
1944
|
if (previousPendingState) {
|
|
1940
1945
|
return {
|
|
@@ -2014,6 +2019,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2014
2019
|
const waitForSeq = async (deltaManager, targetSeq) => new Promise((resolve, reject) => {
|
|
2015
2020
|
// TODO: remove cast to any when actual event is determined
|
|
2016
2021
|
deltaManager.on("closed", reject);
|
|
2022
|
+
deltaManager.on("disposed", reject);
|
|
2017
2023
|
// If we already reached target sequence number, simply resolve the promise.
|
|
2018
2024
|
if (deltaManager.lastSequenceNumber >= targetSeq) {
|
|
2019
2025
|
resolve();
|