@fluidframework/container-runtime 0.59.4000 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.js +3 -3
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +125 -29
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +242 -110
- package/dist/containerRuntime.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/dataStores.d.ts +4 -3
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +9 -3
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +14 -3
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +56 -26
- 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.js +3 -3
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +125 -29
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +243 -111
- package/lib/containerRuntime.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/dataStores.d.ts +4 -3
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +9 -3
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +14 -3
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +54 -6
- 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 +45 -31
- package/src/blobManager.ts +29 -15
- package/src/connectionTelemetry.ts +3 -3
- package/src/containerRuntime.ts +388 -135
- package/src/dataStoreContext.ts +27 -5
- package/src/dataStores.ts +15 -3
- package/src/garbageCollection.ts +69 -12
- 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/src/containerRuntime.ts
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
-
// See #9219
|
|
6
|
-
/* eslint-disable max-lines */
|
|
7
5
|
import { EventEmitter } from "events";
|
|
8
6
|
import { ITelemetryBaseLogger, ITelemetryGenericEvent, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
9
7
|
import {
|
|
@@ -25,6 +23,7 @@ import {
|
|
|
25
23
|
AttachState,
|
|
26
24
|
ILoaderOptions,
|
|
27
25
|
LoaderHeader,
|
|
26
|
+
ISnapshotTreeWithBlobContents,
|
|
28
27
|
} from "@fluidframework/container-definitions";
|
|
29
28
|
import {
|
|
30
29
|
IContainerRuntime,
|
|
@@ -41,7 +40,6 @@ import {
|
|
|
41
40
|
ChildLogger,
|
|
42
41
|
raiseConnectedEvent,
|
|
43
42
|
PerformanceEvent,
|
|
44
|
-
normalizeError,
|
|
45
43
|
TaggedLoggerAdapter,
|
|
46
44
|
MonitoringContext,
|
|
47
45
|
loggerToMonitoringContext,
|
|
@@ -61,7 +59,7 @@ import {
|
|
|
61
59
|
IQuorumClients,
|
|
62
60
|
ISequencedDocumentMessage,
|
|
63
61
|
ISignalMessage,
|
|
64
|
-
|
|
62
|
+
ISnapshotTree,
|
|
65
63
|
ISummaryContent,
|
|
66
64
|
ISummaryTree,
|
|
67
65
|
MessageType,
|
|
@@ -86,9 +84,11 @@ import {
|
|
|
86
84
|
channelsTreeName,
|
|
87
85
|
IAttachMessage,
|
|
88
86
|
IDataStore,
|
|
87
|
+
ITelemetryContext,
|
|
89
88
|
} from "@fluidframework/runtime-definitions";
|
|
90
89
|
import {
|
|
91
90
|
addBlobToSummary,
|
|
91
|
+
addSummarizeResultToSummary,
|
|
92
92
|
addTreeToSummary,
|
|
93
93
|
createRootSummarizerNodeWithGC,
|
|
94
94
|
IRootSummarizerNodeWithGC,
|
|
@@ -99,7 +99,7 @@ import {
|
|
|
99
99
|
responseToException,
|
|
100
100
|
seqFromTree,
|
|
101
101
|
calculateStats,
|
|
102
|
-
|
|
102
|
+
TelemetryContext,
|
|
103
103
|
} from "@fluidframework/runtime-utils";
|
|
104
104
|
import { GCDataBuilder, trimLeadingAndTrailingSlashes } from "@fluidframework/garbage-collector";
|
|
105
105
|
import { v4 as uuid } from "uuid";
|
|
@@ -154,6 +154,7 @@ import {
|
|
|
154
154
|
isDataStoreAliasMessage,
|
|
155
155
|
} from "./dataStore";
|
|
156
156
|
import { BindBatchTracker } from "./batchTracker";
|
|
157
|
+
import { ISerializedBaseSnapshotBlobs, SerializedSnapshotStorage } from "./serializedSnapshotStorage";
|
|
157
158
|
import { OpTracker } from "./opTelemetry";
|
|
158
159
|
|
|
159
160
|
export enum ContainerMessageType {
|
|
@@ -190,22 +191,85 @@ export interface ContainerRuntimeMessage {
|
|
|
190
191
|
contents: any;
|
|
191
192
|
type: ContainerMessageType;
|
|
192
193
|
}
|
|
194
|
+
export interface ISummaryBaseConfiguration {
|
|
195
|
+
/**
|
|
196
|
+
* Delay before first attempt to spawn summarizing container.
|
|
197
|
+
*/
|
|
198
|
+
initialSummarizerDelayMs: number;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Flag that will enable changing elected summarizer client after maxOpsSinceLastSummary.
|
|
202
|
+
* This defaults to false (disabled) and must be explicitly set to true to enable.
|
|
203
|
+
*/
|
|
204
|
+
summarizerClientElection: boolean;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Defines the maximum allowed time to wait for a pending summary ack.
|
|
208
|
+
* The maximum amount of time client will wait for a summarize is the minimum of
|
|
209
|
+
* maxSummarizeAckWaitTime (currently 10 * 60 * 1000) and maxAckWaitTime.
|
|
210
|
+
*/
|
|
211
|
+
maxAckWaitTime: number;
|
|
212
|
+
/**
|
|
213
|
+
* Defines the maximum number of Ops in between Summaries that can be
|
|
214
|
+
* allowed before forcibly electing a new summarizer client.
|
|
215
|
+
*/
|
|
216
|
+
maxOpsSinceLastSummary: number;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface ISummaryConfigurationHeuristics extends ISummaryBaseConfiguration {
|
|
220
|
+
state: "enabled";
|
|
221
|
+
/**
|
|
222
|
+
* Defines the maximum allowed time in between summarizations.
|
|
223
|
+
*/
|
|
224
|
+
idleTime: number;
|
|
225
|
+
/**
|
|
226
|
+
* Defines the maximum allowed time, since the last received Ack, before running the summary
|
|
227
|
+
* with reason maxTime.
|
|
228
|
+
*/
|
|
229
|
+
maxTime: number;
|
|
230
|
+
/**
|
|
231
|
+
* Defines the maximum number of Ops, since the last received Ack, that can be allowed
|
|
232
|
+
* before running the summary with reason maxOps.
|
|
233
|
+
*/
|
|
234
|
+
maxOps: number;
|
|
235
|
+
/**
|
|
236
|
+
* Defnines the minimum number of Ops, since the last received Ack, that can be allowed
|
|
237
|
+
* before running the last summary.
|
|
238
|
+
*/
|
|
239
|
+
minOpsForLastSummaryAttempt: number;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface ISummaryConfigurationDisableSummarizer {
|
|
243
|
+
state: "disabled";
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface ISummaryConfigurationDisableHeuristics extends ISummaryBaseConfiguration {
|
|
247
|
+
state: "disableHeuristics";
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export type ISummaryConfiguration =
|
|
251
|
+
| ISummaryConfigurationDisableSummarizer
|
|
252
|
+
| ISummaryConfigurationDisableHeuristics
|
|
253
|
+
| ISummaryConfigurationHeuristics;
|
|
254
|
+
|
|
255
|
+
export const DefaultSummaryConfiguration: ISummaryConfiguration = {
|
|
256
|
+
state: "enabled",
|
|
257
|
+
|
|
258
|
+
idleTime: 5000 * 3,
|
|
259
|
+
|
|
260
|
+
maxTime: 5000 * 12,
|
|
261
|
+
|
|
262
|
+
maxOps: 100, // Summarize if 100 ops received since last snapshot.
|
|
193
263
|
|
|
194
|
-
|
|
195
|
-
const IdleDetectionTime = 5000;
|
|
264
|
+
minOpsForLastSummaryAttempt: 10,
|
|
196
265
|
|
|
197
|
-
|
|
198
|
-
idleTime: IdleDetectionTime * 3,
|
|
266
|
+
maxAckWaitTime: 6 * 10 * 1000, // 6 min.
|
|
199
267
|
|
|
200
|
-
|
|
268
|
+
maxOpsSinceLastSummary: 7000,
|
|
201
269
|
|
|
202
|
-
|
|
203
|
-
maxOps: 1000,
|
|
270
|
+
initialSummarizerDelayMs: 5000, // 5 secs.
|
|
204
271
|
|
|
205
|
-
|
|
206
|
-
// this is less than maxSummarizeAckWaitTime
|
|
207
|
-
// the min of the two will be chosen
|
|
208
|
-
maxAckWaitTime: 600000,
|
|
272
|
+
summarizerClientElection: false,
|
|
209
273
|
};
|
|
210
274
|
|
|
211
275
|
export interface IGCRuntimeOptions {
|
|
@@ -245,39 +309,43 @@ export interface IGCRuntimeOptions {
|
|
|
245
309
|
}
|
|
246
310
|
|
|
247
311
|
export interface ISummaryRuntimeOptions {
|
|
248
|
-
/**
|
|
249
|
-
* Flag that disables summaries if it is set to true.
|
|
250
|
-
*/
|
|
251
|
-
disableSummaries?: boolean;
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* @deprecated - To disable summaries, please set disableSummaries===true.
|
|
255
|
-
* Flag that will generate summaries if connected to a service that supports them.
|
|
256
|
-
* This defaults to true and must be explicitly set to false to disable.
|
|
257
|
-
*/
|
|
258
|
-
generateSummaries?: boolean;
|
|
259
|
-
|
|
260
|
-
/* Delay before first attempt to spawn summarizing container. */
|
|
261
|
-
initialSummarizerDelayMs?: number;
|
|
262
312
|
|
|
263
313
|
/** Override summary configurations set by the server. */
|
|
264
|
-
summaryConfigOverrides?:
|
|
314
|
+
summaryConfigOverrides?: ISummaryConfiguration;
|
|
265
315
|
|
|
266
316
|
// Flag that disables putting channels in isolated subtrees for each data store
|
|
267
317
|
// and the root node when generating a summary if set to true.
|
|
268
318
|
// Defaults to FALSE (enabled) for now.
|
|
269
319
|
disableIsolatedChannels?: boolean;
|
|
270
320
|
|
|
271
|
-
|
|
272
|
-
|
|
321
|
+
/**
|
|
322
|
+
* @deprecated - use `summaryConfigOverrides.initialSummarizerDelayMs` instead.
|
|
323
|
+
* Delay before first attempt to spawn summarizing container.
|
|
324
|
+
*/
|
|
325
|
+
initialSummarizerDelayMs?: number;
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* @deprecated - use `summaryConfigOverrides.disableSummaries` instead.
|
|
329
|
+
* Flag that disables summaries if it is set to true.
|
|
330
|
+
*/
|
|
331
|
+
disableSummaries?: boolean;
|
|
273
332
|
|
|
274
333
|
/**
|
|
334
|
+
* @deprecated - use `summaryConfigOverrides.maxOpsSinceLastSummary` instead.
|
|
335
|
+
* Defaults to 7000 ops
|
|
336
|
+
*/
|
|
337
|
+
maxOpsSinceLastSummary?: number;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @deprecated - use `summaryConfigOverrides.summarizerClientElection` instead.
|
|
275
341
|
* Flag that will enable changing elected summarizer client after maxOpsSinceLastSummary.
|
|
276
|
-
*
|
|
342
|
+
* This defaults to false (disabled) and must be explicitly set to true to enable.
|
|
277
343
|
*/
|
|
278
344
|
summarizerClientElection?: boolean;
|
|
279
345
|
|
|
280
|
-
/**
|
|
346
|
+
/**
|
|
347
|
+
* @deprecated - use `summaryConfigOverrides.state = "DisableHeuristics"` instead.
|
|
348
|
+
* Options that control the running summarizer behavior. */
|
|
281
349
|
summarizerOptions?: Readonly<Partial<ISummarizerOptions>>;
|
|
282
350
|
}
|
|
283
351
|
|
|
@@ -309,6 +377,10 @@ export interface IContainerRuntimeOptions {
|
|
|
309
377
|
* By default, flush mode is TurnBased.
|
|
310
378
|
*/
|
|
311
379
|
readonly flushMode?: FlushMode;
|
|
380
|
+
/**
|
|
381
|
+
* Save enough runtime state to be able to serialize upon request and load to the same state in a new container.
|
|
382
|
+
*/
|
|
383
|
+
readonly enableOfflineLoad?: boolean;
|
|
312
384
|
}
|
|
313
385
|
|
|
314
386
|
type IRuntimeMessageMetadata = undefined | {
|
|
@@ -349,6 +421,33 @@ interface OldContainerContextWithLogger extends Omit<IContainerContext, "taggedL
|
|
|
349
421
|
taggedLogger: undefined;
|
|
350
422
|
}
|
|
351
423
|
|
|
424
|
+
/**
|
|
425
|
+
* State saved when the container closes, to be given back to a newly
|
|
426
|
+
* instantiated runtime in a new instance of the container, so it can load to the
|
|
427
|
+
* same state
|
|
428
|
+
*/
|
|
429
|
+
export interface IPendingRuntimeState {
|
|
430
|
+
/**
|
|
431
|
+
* Pending ops from PendingStateManager
|
|
432
|
+
*/
|
|
433
|
+
pending?: IPendingLocalState;
|
|
434
|
+
/**
|
|
435
|
+
* A base snapshot at a sequence number prior to the first pending op
|
|
436
|
+
*/
|
|
437
|
+
baseSnapshot: ISnapshotTree;
|
|
438
|
+
/**
|
|
439
|
+
* Serialized blobs from the base snapshot. Used to load offline since
|
|
440
|
+
* storage is not available.
|
|
441
|
+
*/
|
|
442
|
+
snapshotBlobs: ISerializedBaseSnapshotBlobs;
|
|
443
|
+
/**
|
|
444
|
+
* All runtime ops since base snapshot sequence number up to the latest op
|
|
445
|
+
* seen when the container was closed. Used to apply stashed (saved pending)
|
|
446
|
+
* ops at the same sequence number at which they were made.
|
|
447
|
+
*/
|
|
448
|
+
savedOps: ISequencedDocumentMessage[];
|
|
449
|
+
}
|
|
450
|
+
|
|
352
451
|
const useDataStoreAliasingKey = "Fluid.ContainerRuntime.UseDataStoreAliasing";
|
|
353
452
|
const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
|
|
354
453
|
|
|
@@ -749,15 +848,20 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
749
848
|
loadSequenceNumberVerification = "close",
|
|
750
849
|
useDataStoreAliasing = false,
|
|
751
850
|
flushMode = defaultFlushMode,
|
|
851
|
+
enableOfflineLoad = false,
|
|
752
852
|
} = runtimeOptions;
|
|
753
853
|
|
|
754
|
-
const
|
|
854
|
+
const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
|
|
855
|
+
const baseSnapshot: ISnapshotTree | undefined = pendingRuntimeState?.baseSnapshot ?? context.baseSnapshot;
|
|
856
|
+
const storage = !pendingRuntimeState ?
|
|
857
|
+
context.storage :
|
|
858
|
+
new SerializedSnapshotStorage(() => { return context.storage; }, pendingRuntimeState.snapshotBlobs);
|
|
755
859
|
|
|
756
860
|
const registry = new FluidDataStoreRegistry(registryEntries);
|
|
757
861
|
|
|
758
862
|
const tryFetchBlob = async <T>(blobName: string): Promise<T | undefined> => {
|
|
759
|
-
const blobId =
|
|
760
|
-
if (
|
|
863
|
+
const blobId = baseSnapshot?.blobs[blobName];
|
|
864
|
+
if (baseSnapshot && blobId) {
|
|
761
865
|
// IContainerContext storage api return type still has undefined in 0.39 package version.
|
|
762
866
|
// So once we release 0.40 container-defn package we can remove this check.
|
|
763
867
|
assert(storage !== undefined, 0x1f5 /* "Attached state should have storage" */);
|
|
@@ -776,7 +880,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
776
880
|
|
|
777
881
|
// read snapshot blobs needed for BlobManager to load
|
|
778
882
|
const blobManagerSnapshot = await BlobManager.load(
|
|
779
|
-
|
|
883
|
+
baseSnapshot?.trees[blobsTreeName],
|
|
780
884
|
async (id) => {
|
|
781
885
|
// IContainerContext storage api return type still has undefined in 0.39 package version.
|
|
782
886
|
// So once we release 0.40 container-defn package we can remove this check.
|
|
@@ -819,14 +923,24 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
819
923
|
loadSequenceNumberVerification,
|
|
820
924
|
useDataStoreAliasing,
|
|
821
925
|
flushMode,
|
|
926
|
+
enableOfflineLoad,
|
|
822
927
|
},
|
|
823
928
|
containerScope,
|
|
824
929
|
logger,
|
|
825
930
|
loadExisting,
|
|
826
931
|
blobManagerSnapshot,
|
|
932
|
+
storage,
|
|
827
933
|
requestHandler,
|
|
828
934
|
);
|
|
829
935
|
|
|
936
|
+
if (pendingRuntimeState) {
|
|
937
|
+
await runtime.processSavedOps(pendingRuntimeState);
|
|
938
|
+
// delete these once runtime has seen them to save space
|
|
939
|
+
pendingRuntimeState.savedOps = [];
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
await runtime.getSnapshotBlobs();
|
|
943
|
+
|
|
830
944
|
return runtime;
|
|
831
945
|
}
|
|
832
946
|
|
|
@@ -847,7 +961,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
847
961
|
}
|
|
848
962
|
|
|
849
963
|
public get storage(): IDocumentStorageService {
|
|
850
|
-
return this.
|
|
964
|
+
return this._storage;
|
|
851
965
|
}
|
|
852
966
|
|
|
853
967
|
public get reSubmitFn(): (
|
|
@@ -910,7 +1024,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
910
1024
|
|
|
911
1025
|
private _connected: boolean;
|
|
912
1026
|
|
|
913
|
-
private
|
|
1027
|
+
private readonly savedOps: ISequencedDocumentMessage[] = [];
|
|
1028
|
+
private baseSnapshotBlobs?: ISerializedBaseSnapshotBlobs;
|
|
914
1029
|
|
|
915
1030
|
private consecutiveReconnects = 0;
|
|
916
1031
|
|
|
@@ -923,15 +1038,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
923
1038
|
return this.summarizerClientElection?.electedClientId;
|
|
924
1039
|
}
|
|
925
1040
|
|
|
926
|
-
private get summaryConfiguration() {
|
|
927
|
-
return {
|
|
928
|
-
// the defaults
|
|
929
|
-
... DefaultSummaryConfiguration,
|
|
930
|
-
// the runtime configuration overrides
|
|
931
|
-
... this.runtimeOptions.summaryOptions?.summaryConfigOverrides,
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
|
|
935
1041
|
private _disposed = false;
|
|
936
1042
|
public get disposed() { return this._disposed; }
|
|
937
1043
|
|
|
@@ -969,9 +1075,68 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
969
1075
|
return this._summarizer;
|
|
970
1076
|
}
|
|
971
1077
|
|
|
972
|
-
private
|
|
973
|
-
|
|
974
|
-
|
|
1078
|
+
private readonly summariesDisabled: boolean;
|
|
1079
|
+
private isSummariesDisabled(): boolean {
|
|
1080
|
+
// back-compat: disableSummaries was moved from ISummaryRuntimeOptions
|
|
1081
|
+
// to ISummaryConfiguration in 0.60.
|
|
1082
|
+
if (this.runtimeOptions.summaryOptions.disableSummaries === true) {
|
|
1083
|
+
return true;
|
|
1084
|
+
}
|
|
1085
|
+
return this.summaryConfiguration.state === "disabled";
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
private readonly heuristicsDisabled: boolean;
|
|
1089
|
+
private isHeuristicsDisabled(): boolean {
|
|
1090
|
+
// back-compat: disableHeuristics was moved from ISummarizerOptions
|
|
1091
|
+
// to ISummaryConfiguration in 0.60.
|
|
1092
|
+
if (this.runtimeOptions.summaryOptions.summarizerOptions?.disableHeuristics === true) {
|
|
1093
|
+
return true;
|
|
1094
|
+
}
|
|
1095
|
+
return this.summaryConfiguration.state === "disableHeuristics";
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
private readonly summarizerClientElectionEnabled: boolean;
|
|
1099
|
+
private isSummarizerClientElectionEnabled(): boolean {
|
|
1100
|
+
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection")) {
|
|
1101
|
+
return this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection") ?? true;
|
|
1102
|
+
}
|
|
1103
|
+
// back-compat: summarizerClientElection was moved from ISummaryRuntimeOptions
|
|
1104
|
+
// to ISummaryConfiguration in 0.60.
|
|
1105
|
+
if (this.runtimeOptions.summaryOptions.summarizerClientElection === true) {
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
if (this.summaryConfiguration.state !== "disabled") {
|
|
1109
|
+
return this.summaryConfiguration.summarizerClientElection === true;
|
|
1110
|
+
} else {
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
private readonly maxOpsSinceLastSummary: number;
|
|
1115
|
+
private getMaxOpsSinceLastSummary(): number {
|
|
1116
|
+
// back-compat: maxOpsSinceLastSummary was moved from ISummaryRuntimeOptions
|
|
1117
|
+
// to ISummaryConfiguration in 0.60.
|
|
1118
|
+
if (this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary !== undefined) {
|
|
1119
|
+
return this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary;
|
|
1120
|
+
}
|
|
1121
|
+
if (this.summaryConfiguration.state !== "disabled") {
|
|
1122
|
+
return this.summaryConfiguration.maxOpsSinceLastSummary;
|
|
1123
|
+
} else {
|
|
1124
|
+
return 0;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
private readonly initialSummarizerDelayMs: number;
|
|
1129
|
+
private getInitialSummarizerDelayMs(): number {
|
|
1130
|
+
// back-compat: initialSummarizerDelayMs was moved from ISummaryRuntimeOptions
|
|
1131
|
+
// to ISummaryConfiguration in 0.60.
|
|
1132
|
+
if (this.runtimeOptions.summaryOptions.initialSummarizerDelayMs !== undefined) {
|
|
1133
|
+
return this.runtimeOptions.summaryOptions.initialSummarizerDelayMs;
|
|
1134
|
+
}
|
|
1135
|
+
if (this.summaryConfiguration.state !== "disabled") {
|
|
1136
|
+
return this.summaryConfiguration.initialSummarizerDelayMs;
|
|
1137
|
+
} else {
|
|
1138
|
+
return 0;
|
|
1139
|
+
}
|
|
975
1140
|
}
|
|
976
1141
|
|
|
977
1142
|
private readonly createContainerMetadata: ICreateContainerMetadata;
|
|
@@ -994,10 +1159,16 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
994
1159
|
public readonly logger: ITelemetryLogger,
|
|
995
1160
|
existing: boolean,
|
|
996
1161
|
blobManagerSnapshot: IBlobManagerLoadInfo,
|
|
1162
|
+
private readonly _storage: IDocumentStorageService,
|
|
997
1163
|
private readonly requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise<IResponse>,
|
|
1164
|
+
private readonly summaryConfiguration: ISummaryConfiguration = {
|
|
1165
|
+
// the defaults
|
|
1166
|
+
... DefaultSummaryConfiguration,
|
|
1167
|
+
// the runtime configuration overrides
|
|
1168
|
+
... runtimeOptions.summaryOptions?.summaryConfigOverrides,
|
|
1169
|
+
},
|
|
998
1170
|
) {
|
|
999
1171
|
super();
|
|
1000
|
-
|
|
1001
1172
|
this.messageAtLastSummary = metadata?.message;
|
|
1002
1173
|
|
|
1003
1174
|
// Default to false (enabled).
|
|
@@ -1011,6 +1182,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1011
1182
|
this.mc = loggerToMonitoringContext(
|
|
1012
1183
|
ChildLogger.create(this.logger, "ContainerRuntime"));
|
|
1013
1184
|
|
|
1185
|
+
this.summariesDisabled = this.isSummariesDisabled();
|
|
1186
|
+
this.heuristicsDisabled = this.isHeuristicsDisabled();
|
|
1187
|
+
this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
|
|
1188
|
+
this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
|
|
1189
|
+
this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
|
|
1190
|
+
|
|
1014
1191
|
this._aliasingEnabled =
|
|
1015
1192
|
(this.mc.config.getBoolean(useDataStoreAliasingKey) ?? false) ||
|
|
1016
1193
|
(runtimeOptions.useDataStoreAliasing ?? false);
|
|
@@ -1020,12 +1197,16 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1020
1197
|
this.mc.config.getNumber(maxConsecutiveReconnectsKey) ?? this.defaultMaxConsecutiveReconnects;
|
|
1021
1198
|
|
|
1022
1199
|
this._flushMode = runtimeOptions.flushMode;
|
|
1200
|
+
|
|
1201
|
+
const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
|
|
1202
|
+
const baseSnapshot: ISnapshotTree | undefined = pendingRuntimeState?.baseSnapshot ?? context.baseSnapshot;
|
|
1203
|
+
|
|
1023
1204
|
this.garbageCollector = GarbageCollector.create(
|
|
1024
1205
|
this,
|
|
1025
1206
|
this.runtimeOptions.gcOptions,
|
|
1026
1207
|
(nodePath: string) => this.getGCNodePackagePath(nodePath),
|
|
1027
1208
|
() => this.messageAtLastSummary?.timestamp,
|
|
1028
|
-
|
|
1209
|
+
baseSnapshot,
|
|
1029
1210
|
async <T>(id: string) => readAndParse<T>(this.storage, id),
|
|
1030
1211
|
this.mc.logger,
|
|
1031
1212
|
existing,
|
|
@@ -1037,11 +1218,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1037
1218
|
this.summarizerNode = createRootSummarizerNodeWithGC(
|
|
1038
1219
|
ChildLogger.create(this.logger, "SummarizerNode"),
|
|
1039
1220
|
// Summarize function to call when summarize is called. Summarizer node always tracks summary state.
|
|
1040
|
-
async (fullTree: boolean, trackState: boolean) =>
|
|
1221
|
+
async (fullTree: boolean, trackState: boolean, telemetryContext?: ITelemetryContext) =>
|
|
1222
|
+
this.summarizeInternal(fullTree, trackState, telemetryContext),
|
|
1041
1223
|
// Latest change sequence number, no changes since summary applied yet
|
|
1042
1224
|
loadedFromSequenceNumber,
|
|
1043
1225
|
// Summary reference sequence number, undefined if no summary yet
|
|
1044
|
-
|
|
1226
|
+
baseSnapshot ? loadedFromSequenceNumber : undefined,
|
|
1045
1227
|
{
|
|
1046
1228
|
// Must set to false to prevent sending summary handle which would be pointing to
|
|
1047
1229
|
// a summary with an older protocol state.
|
|
@@ -1054,12 +1236,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1054
1236
|
},
|
|
1055
1237
|
);
|
|
1056
1238
|
|
|
1057
|
-
if (
|
|
1058
|
-
this.summarizerNode.loadBaseSummaryWithoutDifferential(
|
|
1239
|
+
if (baseSnapshot) {
|
|
1240
|
+
this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
|
|
1059
1241
|
}
|
|
1060
1242
|
|
|
1061
1243
|
this.dataStores = new DataStores(
|
|
1062
|
-
getSummaryForDatastores(
|
|
1244
|
+
getSummaryForDatastores(baseSnapshot, metadata),
|
|
1063
1245
|
this,
|
|
1064
1246
|
(attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg),
|
|
1065
1247
|
(id: string, createParam: CreateChildSummarizerNodeParam) => (
|
|
@@ -1106,10 +1288,19 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1106
1288
|
this.deltaSender = this.deltaManager;
|
|
1107
1289
|
|
|
1108
1290
|
this.pendingStateManager = new PendingStateManager(
|
|
1109
|
-
|
|
1110
|
-
|
|
1291
|
+
{
|
|
1292
|
+
applyStashedOp: this.applyStashedOp.bind(this),
|
|
1293
|
+
clientId: () => this.clientId,
|
|
1294
|
+
close: this.closeFn,
|
|
1295
|
+
connected: () => this.connected,
|
|
1296
|
+
flush: this.flush.bind(this),
|
|
1297
|
+
flushMode: () => this.flushMode,
|
|
1298
|
+
reSubmit: this.reSubmit.bind(this),
|
|
1299
|
+
rollback: this.rollback.bind(this),
|
|
1300
|
+
setFlushMode: (mode) => this.setFlushMode(mode),
|
|
1301
|
+
},
|
|
1111
1302
|
this._flushMode,
|
|
1112
|
-
|
|
1303
|
+
pendingRuntimeState?.pending);
|
|
1113
1304
|
|
|
1114
1305
|
this.context.quorum.on("removeMember", (clientId: string) => {
|
|
1115
1306
|
this.clearPartialChunks(clientId);
|
|
@@ -1117,16 +1308,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1117
1308
|
|
|
1118
1309
|
this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
|
|
1119
1310
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|| (pendingLocalState as IPendingLocalState)?.pendingStates.length > 0;
|
|
1311
|
+
this.dirtyContainer = this.context.attachState !== AttachState.Attached
|
|
1312
|
+
|| this.pendingStateManager.hasPendingMessages();
|
|
1123
1313
|
this.context.updateDirtyContainerState(this.dirtyContainer);
|
|
1124
1314
|
|
|
1125
|
-
// Map the deprecated generateSummaries flag to disableSummaries.
|
|
1126
|
-
if (this.runtimeOptions.summaryOptions.generateSummaries === false) {
|
|
1127
|
-
this.runtimeOptions.summaryOptions.disableSummaries = true;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
1315
|
if (this.summariesDisabled) {
|
|
1131
1316
|
this.mc.logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
|
|
1132
1317
|
} else {
|
|
@@ -1143,16 +1328,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1143
1328
|
electedSummarizerData ?? this.context.deltaManager.lastSequenceNumber,
|
|
1144
1329
|
SummarizerClientElection.isClientEligible,
|
|
1145
1330
|
);
|
|
1146
|
-
|
|
1147
|
-
this.mc.config.getBoolean("Fluid.ContainerRuntime.summarizerClientElection") ??
|
|
1148
|
-
this.runtimeOptions.summaryOptions?.summarizerClientElection === true;
|
|
1149
|
-
const maxOpsSinceLastSummary = this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary ?? 7000;
|
|
1331
|
+
|
|
1150
1332
|
this.summarizerClientElection = new SummarizerClientElection(
|
|
1151
1333
|
orderedClientLogger,
|
|
1152
1334
|
this.summaryCollection,
|
|
1153
1335
|
orderedClientElectionForSummarizer,
|
|
1154
|
-
maxOpsSinceLastSummary,
|
|
1155
|
-
summarizerClientElectionEnabled,
|
|
1336
|
+
this.maxOpsSinceLastSummary,
|
|
1337
|
+
this.summarizerClientElectionEnabled,
|
|
1156
1338
|
);
|
|
1157
1339
|
|
|
1158
1340
|
if (this.context.clientDetails.type === summarizerClientType) {
|
|
@@ -1169,7 +1351,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1169
1351
|
// Only create a SummaryManager and SummarizerClientElection
|
|
1170
1352
|
// if summaries are enabled and we are not the summarizer client.
|
|
1171
1353
|
const defaultAction = () => {
|
|
1172
|
-
if (this.summaryCollection.opsSinceLastAck > maxOpsSinceLastSummary) {
|
|
1354
|
+
if (this.summaryCollection.opsSinceLastAck > this.maxOpsSinceLastSummary) {
|
|
1173
1355
|
this.logger.sendErrorEvent({ eventName: "SummaryStatus:Behind" });
|
|
1174
1356
|
// unregister default to no log on every op after falling behind
|
|
1175
1357
|
// and register summary ack handler to re-register this handler
|
|
@@ -1200,9 +1382,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1200
1382
|
formExponentialFn({ coefficient: 20, initialDelay: 0 }),
|
|
1201
1383
|
),
|
|
1202
1384
|
{
|
|
1203
|
-
initialDelayMs: this.
|
|
1385
|
+
initialDelayMs: this.initialSummarizerDelayMs,
|
|
1204
1386
|
},
|
|
1205
|
-
this.
|
|
1387
|
+
this.heuristicsDisabled,
|
|
1206
1388
|
);
|
|
1207
1389
|
this.summaryManager.start();
|
|
1208
1390
|
}
|
|
@@ -1231,10 +1413,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1231
1413
|
this.replayPendingStates();
|
|
1232
1414
|
});
|
|
1233
1415
|
|
|
1234
|
-
if (context.pendingLocalState !== undefined) {
|
|
1235
|
-
this.deltaManager.on("op", this.onOp);
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
1416
|
// logging hardware telemetry
|
|
1239
1417
|
logger.sendTelemetryEvent({
|
|
1240
1418
|
eventName: "DeviceSpec",
|
|
@@ -1439,6 +1617,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1439
1617
|
summaryTree: ISummaryTreeWithStats,
|
|
1440
1618
|
fullTree: boolean,
|
|
1441
1619
|
trackState: boolean,
|
|
1620
|
+
telemetryContext?: ITelemetryContext,
|
|
1442
1621
|
) {
|
|
1443
1622
|
this.addMetadataToSummary(summaryTree);
|
|
1444
1623
|
|
|
@@ -1465,7 +1644,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1465
1644
|
}
|
|
1466
1645
|
|
|
1467
1646
|
if (this.garbageCollector.writeDataAtRoot) {
|
|
1468
|
-
const gcSummary = this.garbageCollector.summarize(fullTree, trackState);
|
|
1647
|
+
const gcSummary = this.garbageCollector.summarize(fullTree, trackState, telemetryContext);
|
|
1469
1648
|
if (gcSummary !== undefined) {
|
|
1470
1649
|
addSummarizeResultToSummary(summaryTree, gcTreeKey, gcSummary);
|
|
1471
1650
|
}
|
|
@@ -1489,7 +1668,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1489
1668
|
return true;
|
|
1490
1669
|
}
|
|
1491
1670
|
|
|
1492
|
-
this.consecutiveReconnects++;
|
|
1493
1671
|
if (this.consecutiveReconnects === Math.floor(this.maxConsecutiveReconnects / 2)) {
|
|
1494
1672
|
// If we're halfway through the max reconnects, send an event in order
|
|
1495
1673
|
// to better identify false positives, if any. If the rate of this event
|
|
@@ -1498,6 +1676,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1498
1676
|
this.mc.logger.sendTelemetryEvent({
|
|
1499
1677
|
eventName: "ReconnectsWithNoProgress",
|
|
1500
1678
|
attempts: this.consecutiveReconnects,
|
|
1679
|
+
pendingMessages: this.pendingStateManager.pendingMessagesCount,
|
|
1501
1680
|
});
|
|
1502
1681
|
}
|
|
1503
1682
|
|
|
@@ -1538,28 +1717,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1538
1717
|
this.updateDocumentDirtyState(newState);
|
|
1539
1718
|
}
|
|
1540
1719
|
|
|
1541
|
-
/**
|
|
1542
|
-
* Used to apply stashed ops at their reference sequence number.
|
|
1543
|
-
* Normal op processing is synchronous, but applying stashed ops is async since the
|
|
1544
|
-
* data store may not be loaded yet, so we pause DeltaManager between ops.
|
|
1545
|
-
* It's also important that we see each op so we know all stashed ops have
|
|
1546
|
-
* been applied by "connected" event, but process() doesn't see system ops,
|
|
1547
|
-
* so we listen directly from DeltaManager instead.
|
|
1548
|
-
*/
|
|
1549
|
-
private readonly onOp = (op: ISequencedDocumentMessage) => {
|
|
1550
|
-
assert(!this.paused, 0x128 /* "Container should not already be paused before applying stashed ops" */);
|
|
1551
|
-
this.paused = true;
|
|
1552
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1553
|
-
this.context.deltaManager.inbound.pause();
|
|
1554
|
-
const stashP = this.pendingStateManager.applyStashedOpsAt(op.sequenceNumber);
|
|
1555
|
-
stashP.then(() => {
|
|
1556
|
-
this.paused = false;
|
|
1557
|
-
this.context.deltaManager.inbound.resume();
|
|
1558
|
-
}, (error) => {
|
|
1559
|
-
this.closeFn(normalizeError(error));
|
|
1560
|
-
});
|
|
1561
|
-
};
|
|
1562
|
-
|
|
1563
1720
|
private async applyStashedOp(type: ContainerMessageType, op: ISequencedDocumentMessage): Promise<unknown> {
|
|
1564
1721
|
switch (type) {
|
|
1565
1722
|
case ContainerMessageType.FluidDataStoreOp:
|
|
@@ -1583,20 +1740,26 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1583
1740
|
|
|
1584
1741
|
// There might be no change of state due to Container calling this API after loading runtime.
|
|
1585
1742
|
const changeOfState = this._connected !== connected;
|
|
1743
|
+
const reconnection = changeOfState && connected;
|
|
1586
1744
|
this._connected = connected;
|
|
1587
1745
|
|
|
1588
|
-
if (
|
|
1589
|
-
this.
|
|
1590
|
-
|
|
1746
|
+
if (reconnection) {
|
|
1747
|
+
this.consecutiveReconnects++;
|
|
1748
|
+
|
|
1591
1749
|
if (!this.shouldContinueReconnecting()) {
|
|
1592
1750
|
this.closeFn(new GenericError(
|
|
1593
1751
|
// pre-0.58 error message: MaxReconnectsWithNoProgress
|
|
1594
1752
|
"Runtime detected too many reconnects with no progress syncing local ops",
|
|
1595
1753
|
undefined, // error
|
|
1596
|
-
{
|
|
1754
|
+
{
|
|
1755
|
+
attempts: this.consecutiveReconnects,
|
|
1756
|
+
pendingMessages: this.pendingStateManager.pendingMessagesCount,
|
|
1757
|
+
}));
|
|
1597
1758
|
return;
|
|
1598
1759
|
}
|
|
1760
|
+
}
|
|
1599
1761
|
|
|
1762
|
+
if (changeOfState) {
|
|
1600
1763
|
this.replayPendingStates();
|
|
1601
1764
|
}
|
|
1602
1765
|
|
|
@@ -1613,6 +1776,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1613
1776
|
return;
|
|
1614
1777
|
}
|
|
1615
1778
|
|
|
1779
|
+
if (this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) {
|
|
1780
|
+
this.savedOps.push(messageArg);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1616
1783
|
// Do shallow copy of message, as methods below will modify it.
|
|
1617
1784
|
// There might be multiple container instances receiving same message
|
|
1618
1785
|
// We do not need to make deep copy, as each layer will just replace message.content itself,
|
|
@@ -1631,8 +1798,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1631
1798
|
// once all pieces are available
|
|
1632
1799
|
message = this.processRemoteChunkedMessage(message);
|
|
1633
1800
|
|
|
1634
|
-
|
|
1635
|
-
|
|
1801
|
+
let localOpMetadata: unknown;
|
|
1802
|
+
if (local) {
|
|
1803
|
+
// Call the PendingStateManager to process local messages.
|
|
1804
|
+
// Do not process local chunked ops until all pieces are available.
|
|
1805
|
+
if (message.type !== ContainerMessageType.ChunkedOp) {
|
|
1806
|
+
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1636
1809
|
|
|
1637
1810
|
// If there are no more pending messages after processing a local message,
|
|
1638
1811
|
// the document is no longer dirty.
|
|
@@ -1642,14 +1815,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1642
1815
|
|
|
1643
1816
|
switch (message.type) {
|
|
1644
1817
|
case ContainerMessageType.Attach:
|
|
1645
|
-
this.dataStores.processAttachMessage(message, local
|
|
1818
|
+
this.dataStores.processAttachMessage(message, local);
|
|
1646
1819
|
break;
|
|
1647
1820
|
case ContainerMessageType.Alias:
|
|
1648
1821
|
this.processAliasMessage(message, localOpMetadata, local);
|
|
1649
1822
|
break;
|
|
1650
1823
|
case ContainerMessageType.FluidDataStoreOp:
|
|
1651
|
-
|
|
1652
|
-
this.dataStores.processFluidDataStoreOp(message, local || localAck, localOpMetadata);
|
|
1824
|
+
this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
|
|
1653
1825
|
break;
|
|
1654
1826
|
case ContainerMessageType.BlobAttach:
|
|
1655
1827
|
assert(message?.metadata?.blobId, 0x12a /* "Missing blob id on metadata" */);
|
|
@@ -1770,18 +1942,31 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1770
1942
|
const savedFlushMode = this.flushMode;
|
|
1771
1943
|
this.setFlushMode(FlushMode.TurnBased);
|
|
1772
1944
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1945
|
+
try {
|
|
1946
|
+
this.trackOrderSequentiallyCalls(callback);
|
|
1947
|
+
this.flush();
|
|
1948
|
+
} finally {
|
|
1949
|
+
this.setFlushMode(savedFlushMode);
|
|
1950
|
+
}
|
|
1776
1951
|
}
|
|
1777
1952
|
|
|
1778
1953
|
private trackOrderSequentiallyCalls(callback: () => void): void {
|
|
1954
|
+
let checkpoint: { rollback: () => void; } | undefined;
|
|
1955
|
+
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
|
|
1956
|
+
checkpoint = this.pendingStateManager.checkpoint();
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1779
1959
|
try {
|
|
1780
1960
|
this._orderSequentiallyCalls++;
|
|
1781
1961
|
callback();
|
|
1782
1962
|
} catch (error) {
|
|
1783
|
-
|
|
1784
|
-
|
|
1963
|
+
if (checkpoint) {
|
|
1964
|
+
// This will throw and close the container if rollback fails
|
|
1965
|
+
checkpoint.rollback();
|
|
1966
|
+
} else {
|
|
1967
|
+
// pre-0.58 error message: orderSequentiallyCallbackException
|
|
1968
|
+
this.closeFn(new GenericError("orderSequentially callback exception", error));
|
|
1969
|
+
}
|
|
1785
1970
|
throw error; // throw the original error for the consumer of the runtime
|
|
1786
1971
|
} finally {
|
|
1787
1972
|
this._orderSequentiallyCalls--;
|
|
@@ -1996,13 +2181,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1996
2181
|
* @param blobRedirectTable - A table passed during the attach process. While detached, blob upload is supported
|
|
1997
2182
|
* using IDs generated locally. After attach, these IDs cannot be used, so this table maps the old local IDs to the
|
|
1998
2183
|
* new storage IDs so requests can be redirected.
|
|
2184
|
+
* @param telemetryContext - summary data passed through the layers for telemetry purposes
|
|
1999
2185
|
*/
|
|
2000
|
-
public createSummary(blobRedirectTable?: Map<string, string
|
|
2186
|
+
public createSummary(blobRedirectTable?: Map<string, string>, telemetryContext?: ITelemetryContext): ISummaryTree {
|
|
2001
2187
|
if (blobRedirectTable) {
|
|
2002
2188
|
this.blobManager.setRedirectTable(blobRedirectTable);
|
|
2003
2189
|
}
|
|
2004
2190
|
|
|
2005
|
-
const summarizeResult = this.dataStores.createSummary();
|
|
2191
|
+
const summarizeResult = this.dataStores.createSummary(telemetryContext);
|
|
2006
2192
|
if (!this.disableIsolatedChannels) {
|
|
2007
2193
|
// Wrap data store summaries in .channels subtree.
|
|
2008
2194
|
wrapSummaryInChannelsTree(summarizeResult);
|
|
@@ -2010,7 +2196,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2010
2196
|
this.addContainerStateToSummary(
|
|
2011
2197
|
summarizeResult,
|
|
2012
2198
|
true /* fullTree */,
|
|
2013
|
-
false /* trackState
|
|
2199
|
+
false /* trackState */,
|
|
2200
|
+
telemetryContext,
|
|
2201
|
+
);
|
|
2014
2202
|
return summarizeResult.summary;
|
|
2015
2203
|
}
|
|
2016
2204
|
|
|
@@ -2024,8 +2212,12 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2024
2212
|
return this.context.getAbsoluteUrl(relativeUrl);
|
|
2025
2213
|
}
|
|
2026
2214
|
|
|
2027
|
-
private async summarizeInternal(
|
|
2028
|
-
|
|
2215
|
+
private async summarizeInternal(
|
|
2216
|
+
fullTree: boolean,
|
|
2217
|
+
trackState: boolean,
|
|
2218
|
+
telemetryContext?: ITelemetryContext,
|
|
2219
|
+
): Promise<ISummarizeInternalResult> {
|
|
2220
|
+
const summarizeResult = await this.dataStores.summarize(fullTree, trackState, telemetryContext);
|
|
2029
2221
|
let pathPartsForChildren: string[] | undefined;
|
|
2030
2222
|
|
|
2031
2223
|
if (!this.disableIsolatedChannels) {
|
|
@@ -2033,7 +2225,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2033
2225
|
wrapSummaryInChannelsTree(summarizeResult);
|
|
2034
2226
|
pathPartsForChildren = [channelsTreeName];
|
|
2035
2227
|
}
|
|
2036
|
-
this.addContainerStateToSummary(summarizeResult, fullTree, trackState);
|
|
2228
|
+
this.addContainerStateToSummary(summarizeResult, fullTree, trackState, telemetryContext);
|
|
2037
2229
|
return {
|
|
2038
2230
|
...summarizeResult,
|
|
2039
2231
|
id: "",
|
|
@@ -2074,7 +2266,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2074
2266
|
gcStats = await this.collectGarbage({ logger: summaryLogger, runSweep, fullGC });
|
|
2075
2267
|
}
|
|
2076
2268
|
|
|
2077
|
-
const
|
|
2269
|
+
const telemetryContext = new TelemetryContext();
|
|
2270
|
+
const { stats, summary } = await this.summarizerNode.summarize(fullTree, trackState, telemetryContext);
|
|
2271
|
+
|
|
2272
|
+
this.logger.sendTelemetryEvent({ eventName: "SummarizeTelemetry", details: telemetryContext.serialize() });
|
|
2078
2273
|
|
|
2079
2274
|
assert(summary.type === SummaryType.Tree,
|
|
2080
2275
|
0x12f /* "Container Runtime's summarize should always return a tree" */);
|
|
@@ -2531,9 +2726,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2531
2726
|
): void {
|
|
2532
2727
|
this.verifyNotClosed();
|
|
2533
2728
|
|
|
2534
|
-
if (this.context.pendingLocalState !== undefined) {
|
|
2535
|
-
this.closeFn(new GenericError("containerRuntimeSubmitWithPendingLocalState"));
|
|
2536
|
-
}
|
|
2537
2729
|
// There should be no ops in detached container state!
|
|
2538
2730
|
assert(this.attachState !== AttachState.Detached, 0x132 /* "sending ops in detached container" */);
|
|
2539
2731
|
|
|
@@ -2729,6 +2921,22 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2729
2921
|
}
|
|
2730
2922
|
}
|
|
2731
2923
|
|
|
2924
|
+
private rollback(
|
|
2925
|
+
type: ContainerMessageType,
|
|
2926
|
+
content: any,
|
|
2927
|
+
localOpMetadata: unknown,
|
|
2928
|
+
) {
|
|
2929
|
+
switch (type) {
|
|
2930
|
+
case ContainerMessageType.FluidDataStoreOp:
|
|
2931
|
+
// For operations, call rollbackDataStoreOp which will find the right store
|
|
2932
|
+
// and trigger rollback on it.
|
|
2933
|
+
this.dataStores.rollbackDataStoreOp(content, localOpMetadata);
|
|
2934
|
+
break;
|
|
2935
|
+
default:
|
|
2936
|
+
throw new Error(`Can't rollback ${type}`);
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2732
2940
|
/** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
|
|
2733
2941
|
public async refreshLatestSummaryAck(
|
|
2734
2942
|
proposalHandle: string | undefined,
|
|
@@ -2807,8 +3015,43 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2807
3015
|
});
|
|
2808
3016
|
}
|
|
2809
3017
|
|
|
2810
|
-
public
|
|
2811
|
-
|
|
3018
|
+
public notifyAttaching(snapshot: ISnapshotTreeWithBlobContents) {
|
|
3019
|
+
if (this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) {
|
|
3020
|
+
this.baseSnapshotBlobs = SerializedSnapshotStorage.serializeTreeWithBlobContents(snapshot);
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
public async getSnapshotBlobs(): Promise<void> {
|
|
3025
|
+
if (!(this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad) ||
|
|
3026
|
+
this.attachState !== AttachState.Attached || this.context.pendingLocalState) {
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
assert(!!this.context.baseSnapshot, 0x2e5 /* "Must have a base snapshot" */);
|
|
3030
|
+
this.baseSnapshotBlobs = await SerializedSnapshotStorage.serializeTree(this.context.baseSnapshot, this.storage);
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
public getPendingLocalState(): IPendingRuntimeState {
|
|
3034
|
+
if (!(this.mc.config.getBoolean("enableOfflineLoad") ?? this.runtimeOptions.enableOfflineLoad)) {
|
|
3035
|
+
throw new UsageError("can't get state when offline load disabled");
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
const previousPendingState = this.context.pendingLocalState as IPendingRuntimeState | undefined;
|
|
3039
|
+
if (previousPendingState) {
|
|
3040
|
+
return {
|
|
3041
|
+
pending: this.pendingStateManager.getLocalState(),
|
|
3042
|
+
snapshotBlobs: previousPendingState.snapshotBlobs,
|
|
3043
|
+
baseSnapshot: previousPendingState.baseSnapshot,
|
|
3044
|
+
savedOps: this.savedOps,
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
assert(!!this.context.baseSnapshot, 0x2e6 /* "Must have a base snapshot" */);
|
|
3048
|
+
assert(!!this.baseSnapshotBlobs, 0x2e7 /* "Must serialize base snapshot blobs before getting runtime state" */);
|
|
3049
|
+
return {
|
|
3050
|
+
pending: this.pendingStateManager.getLocalState(),
|
|
3051
|
+
snapshotBlobs: this.baseSnapshotBlobs,
|
|
3052
|
+
baseSnapshot: this.context.baseSnapshot,
|
|
3053
|
+
savedOps: this.savedOps,
|
|
3054
|
+
};
|
|
2812
3055
|
}
|
|
2813
3056
|
|
|
2814
3057
|
public readonly summarizeOnDemand: ISummarizer["summarizeOnDemand"] = (...args) => {
|
|
@@ -2837,7 +3080,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2837
3080
|
// because it is a misuse of the API rather than an expected failure.
|
|
2838
3081
|
throw new UsageError(
|
|
2839
3082
|
`Can't summarize, disableSummaries: ${this.summariesDisabled}`,
|
|
2840
|
-
|
|
3083
|
+
);
|
|
2841
3084
|
}
|
|
2842
3085
|
};
|
|
2843
3086
|
|
|
@@ -2870,6 +3113,16 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2870
3113
|
return summarizer;
|
|
2871
3114
|
};
|
|
2872
3115
|
}
|
|
3116
|
+
|
|
3117
|
+
private async processSavedOps(state: IPendingRuntimeState) {
|
|
3118
|
+
for (const op of state.savedOps) {
|
|
3119
|
+
this.process(op, false);
|
|
3120
|
+
await this.pendingStateManager.applyStashedOpsAt(op.sequenceNumber);
|
|
3121
|
+
}
|
|
3122
|
+
// we may not have seen every sequence number (because of system ops) so apply everything once we
|
|
3123
|
+
// don't have any more saved ops
|
|
3124
|
+
await this.pendingStateManager.applyStashedOpsAt();
|
|
3125
|
+
}
|
|
2873
3126
|
}
|
|
2874
3127
|
|
|
2875
3128
|
/**
|