@fluidframework/container-runtime 2.51.0-347100 → 2.52.0
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/CHANGELOG.md +14 -0
- package/api-report/container-runtime.legacy.alpha.api.md +1 -2
- package/container-runtime.test-files.tar +0 -0
- package/dist/blobManager/blobManager.d.ts +15 -7
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +72 -186
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/containerCompatibility.d.ts +34 -0
- package/dist/containerCompatibility.d.ts.map +1 -0
- package/dist/containerCompatibility.js +125 -0
- package/dist/containerCompatibility.js.map +1 -0
- package/dist/containerRuntime.d.ts +27 -15
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +175 -136
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +6 -6
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata.d.ts +3 -2
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +7 -1
- package/dist/metadata.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/storageServiceWithAttachBlobs.d.ts +40 -5
- package/dist/storageServiceWithAttachBlobs.d.ts.map +1 -1
- package/dist/storageServiceWithAttachBlobs.js +56 -5
- package/dist/storageServiceWithAttachBlobs.js.map +1 -1
- package/dist/summary/documentSchema.d.ts +1 -1
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js.map +1 -1
- package/dist/summary/summaryFormat.d.ts +3 -3
- package/dist/summary/summaryFormat.d.ts.map +1 -1
- package/dist/summary/summaryFormat.js.map +1 -1
- package/lib/blobManager/blobManager.d.ts +15 -7
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +39 -153
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/containerCompatibility.d.ts +34 -0
- package/lib/containerCompatibility.d.ts.map +1 -0
- package/lib/containerCompatibility.js +120 -0
- package/lib/containerCompatibility.js.map +1 -0
- package/lib/containerRuntime.d.ts +27 -15
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +103 -64
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +6 -6
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +1 -1
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/index.d.ts +5 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/metadata.d.ts +3 -2
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js +5 -0
- package/lib/metadata.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/storageServiceWithAttachBlobs.d.ts +40 -5
- package/lib/storageServiceWithAttachBlobs.d.ts.map +1 -1
- package/lib/storageServiceWithAttachBlobs.js +56 -5
- package/lib/storageServiceWithAttachBlobs.js.map +1 -1
- package/lib/summary/documentSchema.d.ts +1 -1
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js.map +1 -1
- package/lib/summary/summaryFormat.d.ts +3 -3
- package/lib/summary/summaryFormat.d.ts.map +1 -1
- package/lib/summary/summaryFormat.js.map +1 -1
- package/package.json +20 -20
- package/src/blobManager/blobManager.ts +53 -195
- package/src/containerCompatibility.ts +176 -0
- package/src/containerRuntime.ts +157 -122
- package/src/dataStoreContext.ts +13 -5
- package/src/index.ts +6 -1
- package/src/metadata.ts +10 -2
- package/src/packageVersion.ts +1 -1
- package/src/storageServiceWithAttachBlobs.ts +92 -10
- package/src/summary/documentSchema.ts +1 -1
- package/src/summary/summaryFormat.ts +2 -2
- package/dist/compatUtils.d.ts +0 -106
- package/dist/compatUtils.d.ts.map +0 -1
- package/dist/compatUtils.js +0 -251
- package/dist/compatUtils.js.map +0 -1
- package/lib/compatUtils.d.ts +0 -106
- package/lib/compatUtils.d.ts.map +0 -1
- package/lib/compatUtils.js +0 -242
- package/lib/compatUtils.js.map +0 -1
- package/src/compatUtils.ts +0 -365
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
FlushMode,
|
|
8
|
+
type MinimumVersionForCollab,
|
|
9
|
+
} from "@fluidframework/runtime-definitions/internal";
|
|
10
|
+
import {
|
|
11
|
+
configValueToMinVersionForCollab,
|
|
12
|
+
getConfigsForMinVersionForCollab,
|
|
13
|
+
getValidationForRuntimeOptions,
|
|
14
|
+
type ConfigMap,
|
|
15
|
+
type ConfigValidationMap,
|
|
16
|
+
} from "@fluidframework/runtime-utils/internal";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
disabledCompressionConfig,
|
|
20
|
+
enabledCompressionConfig,
|
|
21
|
+
} from "./compressionDefinitions.js";
|
|
22
|
+
import type { ContainerRuntimeOptionsInternal } from "./containerRuntime.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Subset of the {@link ContainerRuntimeOptionsInternal} properties which
|
|
26
|
+
* affect {@link IDocumentSchemaFeatures}.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* When a new option is added to {@link ContainerRuntimeOptionsInternal}, we
|
|
30
|
+
* must consider if it changes the DocumentSchema. If so, then a corresponding
|
|
31
|
+
* entry must be added to {@link runtimeOptionsAffectingDocSchemaConfigMap}
|
|
32
|
+
* below. If not, then it must be omitted from this type.
|
|
33
|
+
*
|
|
34
|
+
* Note: `Omit` is used instead of `Pick` to ensure that all new options are
|
|
35
|
+
* included in this type by default. If any new properties are added to
|
|
36
|
+
* {@link ContainerRuntimeOptionsInternal}, they will be included in this
|
|
37
|
+
* type unless explicitly omitted. This will prevent us from forgetting to
|
|
38
|
+
* account for any new properties in the future.
|
|
39
|
+
*/
|
|
40
|
+
export type RuntimeOptionsAffectingDocSchema = Omit<
|
|
41
|
+
ContainerRuntimeOptionsInternal,
|
|
42
|
+
| "chunkSizeInBytes"
|
|
43
|
+
| "maxBatchSizeInBytes"
|
|
44
|
+
| "loadSequenceNumberVerification"
|
|
45
|
+
| "summaryOptions"
|
|
46
|
+
>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Mapping of RuntimeOptionsAffectingDocSchema to their compatibility related configs.
|
|
50
|
+
*
|
|
51
|
+
* Each key in this map corresponds to a property in RuntimeOptionsAffectingDocSchema. The value is an object that maps MinimumVersionForCollab
|
|
52
|
+
* to the appropriate default value for that property to supporting that MinimumVersionForCollab. If clients running MinimumVersionForCollab X are able to understand
|
|
53
|
+
* the format changes introduced by the property, then the default value for that MinimumVersionForCollab will enable the feature associated with the property.
|
|
54
|
+
* Otherwise, the feature will be disabled.
|
|
55
|
+
*
|
|
56
|
+
* For example if the minVersionForCollab is a 1.x version (i.e. "1.5.0"), then the default value for `enableGroupedBatching` will be false since 1.x
|
|
57
|
+
* clients do not understand the document format when batching is enabled. If the minVersionForCollab is a 2.x client (i.e. "2.0.0" or later), then the
|
|
58
|
+
* default value for `enableGroupedBatching` will be true because clients running 2.0 or later will be able to understand the format changes associated
|
|
59
|
+
* with the batching feature.
|
|
60
|
+
*/
|
|
61
|
+
const runtimeOptionsAffectingDocSchemaConfigMap = {
|
|
62
|
+
enableGroupedBatching: {
|
|
63
|
+
"1.0.0": false,
|
|
64
|
+
"2.0.0-defaults": true,
|
|
65
|
+
},
|
|
66
|
+
compressionOptions: {
|
|
67
|
+
"1.0.0": disabledCompressionConfig,
|
|
68
|
+
"2.0.0-defaults": enabledCompressionConfig,
|
|
69
|
+
},
|
|
70
|
+
enableRuntimeIdCompressor: {
|
|
71
|
+
// For IdCompressorMode, `undefined` represents a logical state (off).
|
|
72
|
+
// However, to satisfy the Required<> constraint while
|
|
73
|
+
// `exactOptionalPropertyTypes` is `false` (TODO: AB#8215), we need
|
|
74
|
+
// to have it defined, so we trick the type checker here.
|
|
75
|
+
"1.0.0": undefined,
|
|
76
|
+
// We do not yet want to enable idCompressor by default since it will
|
|
77
|
+
// increase bundle sizes, and not all customers will benefit from it.
|
|
78
|
+
// Therefore, we will require customers to explicitly enable it. We
|
|
79
|
+
// are keeping it as a DocSchema affecting option for now as this may
|
|
80
|
+
// change in the future.
|
|
81
|
+
},
|
|
82
|
+
explicitSchemaControl: {
|
|
83
|
+
"1.0.0": false,
|
|
84
|
+
// This option's intention is to prevent 1.x clients from joining sessions
|
|
85
|
+
// when enabled. This is set to true when the minVersionForCollab is set
|
|
86
|
+
// to >=2.0.0 (explicitly). This is different than other 2.0 defaults
|
|
87
|
+
// because it was not enabled by default prior to the implementation of
|
|
88
|
+
// `minVersionForCollab`.
|
|
89
|
+
// `defaultMinVersionForCollab` is set to "2.0.0-defaults" which "2.0.0"
|
|
90
|
+
// does not satisfy to avoiding enabling this option by default as of
|
|
91
|
+
// `minVersionForCollab` introduction, which could be unexpected.
|
|
92
|
+
// Only enable as a default when `minVersionForCollab` is specified at
|
|
93
|
+
// 2.0.0+.
|
|
94
|
+
"2.0.0": true,
|
|
95
|
+
},
|
|
96
|
+
flushMode: {
|
|
97
|
+
// Note: 1.x clients are compatible with TurnBased flushing, but here we elect to remain on Immediate flush mode
|
|
98
|
+
// as a work-around for inability to send batches larger than 1Mb. Immediate flushing keeps batches smaller as
|
|
99
|
+
// fewer messages will be included per flush.
|
|
100
|
+
"1.0.0": FlushMode.Immediate,
|
|
101
|
+
"2.0.0-defaults": FlushMode.TurnBased,
|
|
102
|
+
},
|
|
103
|
+
gcOptions: {
|
|
104
|
+
"1.0.0": {},
|
|
105
|
+
// Although sweep is supported in 2.x, it is disabled by default until minVersionForCollab>=3.0.0 to be extra safe.
|
|
106
|
+
"3.0.0": { enableGCSweep: true },
|
|
107
|
+
},
|
|
108
|
+
createBlobPayloadPending: {
|
|
109
|
+
// This feature is new and disabled by default. In the future we will enable it by default, but we have not
|
|
110
|
+
// closed on the version where that will happen yet. Probably a .10 release since blob functionality is not
|
|
111
|
+
// exposed on the `@public` API surface.
|
|
112
|
+
"1.0.0": undefined,
|
|
113
|
+
},
|
|
114
|
+
} as const satisfies ConfigMap<RuntimeOptionsAffectingDocSchema>;
|
|
115
|
+
|
|
116
|
+
const runtimeOptionsAffectingDocSchemaConfigValidationMap = {
|
|
117
|
+
enableGroupedBatching: configValueToMinVersionForCollab([
|
|
118
|
+
[false, "1.0.0"],
|
|
119
|
+
[true, "2.0.0-defaults"],
|
|
120
|
+
]),
|
|
121
|
+
compressionOptions: configValueToMinVersionForCollab([
|
|
122
|
+
[{ ...disabledCompressionConfig }, "1.0.0"],
|
|
123
|
+
[{ ...enabledCompressionConfig }, "2.0.0-defaults"],
|
|
124
|
+
]),
|
|
125
|
+
enableRuntimeIdCompressor: configValueToMinVersionForCollab([
|
|
126
|
+
[undefined, "1.0.0"],
|
|
127
|
+
["on", "2.0.0-defaults"],
|
|
128
|
+
["delayed", "2.0.0-defaults"],
|
|
129
|
+
]),
|
|
130
|
+
explicitSchemaControl: configValueToMinVersionForCollab([
|
|
131
|
+
[false, "1.0.0"],
|
|
132
|
+
[true, "2.0.0-defaults"],
|
|
133
|
+
]),
|
|
134
|
+
flushMode: configValueToMinVersionForCollab([
|
|
135
|
+
[FlushMode.Immediate, "1.0.0"],
|
|
136
|
+
[FlushMode.TurnBased, "2.0.0-defaults"],
|
|
137
|
+
]),
|
|
138
|
+
gcOptions: configValueToMinVersionForCollab([
|
|
139
|
+
[{ enableGCSweep: undefined }, "1.0.0"],
|
|
140
|
+
[{ enableGCSweep: true }, "2.0.0-defaults"],
|
|
141
|
+
]),
|
|
142
|
+
createBlobPayloadPending: configValueToMinVersionForCollab([
|
|
143
|
+
[undefined, "1.0.0"],
|
|
144
|
+
[true, "2.40.0"],
|
|
145
|
+
]),
|
|
146
|
+
} as const satisfies ConfigValidationMap<RuntimeOptionsAffectingDocSchema>;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Returns the default RuntimeOptionsAffectingDocSchema configuration for a given minVersionForCollab.
|
|
150
|
+
*/
|
|
151
|
+
export function getMinVersionForCollabDefaults(
|
|
152
|
+
minVersionForCollab: MinimumVersionForCollab,
|
|
153
|
+
): RuntimeOptionsAffectingDocSchema {
|
|
154
|
+
return getConfigsForMinVersionForCollab(
|
|
155
|
+
minVersionForCollab,
|
|
156
|
+
runtimeOptionsAffectingDocSchemaConfigMap,
|
|
157
|
+
// This is a bad cast away from Partial that getConfigsForCompatMode provides.
|
|
158
|
+
// ConfigMap should be restructured to provide RuntimeOptionsAffectingDocSchema guarantee.
|
|
159
|
+
) as RuntimeOptionsAffectingDocSchema;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Validates if the runtime options passed in from the user are compatible with the minVersionForCollab.
|
|
164
|
+
* For example, if a user sets the `enableGroupedBatching` option to true, but the minVersionForCollab
|
|
165
|
+
* is set to "1.0.0", then we should throw a UsageError since 1.x clients do not support batching.
|
|
166
|
+
* */
|
|
167
|
+
export function validateRuntimeOptions(
|
|
168
|
+
minVersionForCollab: MinimumVersionForCollab,
|
|
169
|
+
runtimeOptions: Partial<ContainerRuntimeOptionsInternal>,
|
|
170
|
+
): void {
|
|
171
|
+
getValidationForRuntimeOptions<RuntimeOptionsAffectingDocSchema>(
|
|
172
|
+
minVersionForCollab,
|
|
173
|
+
runtimeOptions as Partial<RuntimeOptionsAffectingDocSchema>,
|
|
174
|
+
runtimeOptionsAffectingDocSchemaConfigValidationMap,
|
|
175
|
+
);
|
|
176
|
+
}
|
package/src/containerRuntime.ts
CHANGED
|
@@ -22,8 +22,12 @@ import type {
|
|
|
22
22
|
IDeltaManager,
|
|
23
23
|
IDeltaManagerFull,
|
|
24
24
|
ILoader,
|
|
25
|
+
IContainerStorageService,
|
|
26
|
+
} from "@fluidframework/container-definitions/internal";
|
|
27
|
+
import {
|
|
28
|
+
ConnectionState,
|
|
29
|
+
isIDeltaManagerFull,
|
|
25
30
|
} from "@fluidframework/container-definitions/internal";
|
|
26
|
-
import { isIDeltaManagerFull } from "@fluidframework/container-definitions/internal";
|
|
27
31
|
import type {
|
|
28
32
|
ContainerExtensionFactory,
|
|
29
33
|
ContainerExtensionId,
|
|
@@ -35,7 +39,9 @@ import type {
|
|
|
35
39
|
IContainerRuntimeInternal,
|
|
36
40
|
// eslint-disable-next-line import/no-deprecated
|
|
37
41
|
IContainerRuntimeWithResolveHandle_Deprecated,
|
|
42
|
+
JoinedStatus,
|
|
38
43
|
OutboundExtensionMessage,
|
|
44
|
+
UnverifiedBrand,
|
|
39
45
|
} from "@fluidframework/container-runtime-definitions/internal";
|
|
40
46
|
import type {
|
|
41
47
|
FluidObject,
|
|
@@ -46,12 +52,11 @@ import type {
|
|
|
46
52
|
Listenable,
|
|
47
53
|
} from "@fluidframework/core-interfaces";
|
|
48
54
|
import type {
|
|
49
|
-
IErrorBase,
|
|
50
55
|
IFluidHandleContext,
|
|
51
56
|
IFluidHandleInternal,
|
|
52
57
|
IProvideFluidHandleContext,
|
|
53
58
|
ISignalEnvelope,
|
|
54
|
-
|
|
59
|
+
OpaqueJsonDeserialized,
|
|
55
60
|
TypedMessage,
|
|
56
61
|
} from "@fluidframework/core-interfaces/internal";
|
|
57
62
|
import {
|
|
@@ -71,7 +76,6 @@ import type {
|
|
|
71
76
|
} from "@fluidframework/driver-definitions";
|
|
72
77
|
import { SummaryType } from "@fluidframework/driver-definitions";
|
|
73
78
|
import type {
|
|
74
|
-
IDocumentStorageService,
|
|
75
79
|
IDocumentMessage,
|
|
76
80
|
ISequencedDocumentMessage,
|
|
77
81
|
ISignalMessage,
|
|
@@ -121,7 +125,13 @@ import {
|
|
|
121
125
|
FlushModeExperimental,
|
|
122
126
|
channelsTreeName,
|
|
123
127
|
gcTreeKey,
|
|
128
|
+
type MinimumVersionForCollab,
|
|
124
129
|
} from "@fluidframework/runtime-definitions/internal";
|
|
130
|
+
import {
|
|
131
|
+
defaultMinVersionForCollab,
|
|
132
|
+
isValidMinVersionForCollab,
|
|
133
|
+
type SemanticVersion,
|
|
134
|
+
} from "@fluidframework/runtime-utils/internal";
|
|
125
135
|
import {
|
|
126
136
|
GCDataBuilder,
|
|
127
137
|
RequestParser,
|
|
@@ -178,18 +188,14 @@ import {
|
|
|
178
188
|
getSummaryForDatastores,
|
|
179
189
|
wrapContext,
|
|
180
190
|
} from "./channelCollection.js";
|
|
191
|
+
import type { ICompressionRuntimeOptions } from "./compressionDefinitions.js";
|
|
192
|
+
import { CompressionAlgorithms, disabledCompressionConfig } from "./compressionDefinitions.js";
|
|
193
|
+
import { ReportOpPerfTelemetry } from "./connectionTelemetry.js";
|
|
181
194
|
import {
|
|
182
|
-
defaultMinVersionForCollab,
|
|
183
195
|
getMinVersionForCollabDefaults,
|
|
184
|
-
isValidMinVersionForCollab,
|
|
185
196
|
type RuntimeOptionsAffectingDocSchema,
|
|
186
|
-
type MinimumVersionForCollab,
|
|
187
|
-
type SemanticVersion,
|
|
188
197
|
validateRuntimeOptions,
|
|
189
|
-
} from "./
|
|
190
|
-
import type { ICompressionRuntimeOptions } from "./compressionDefinitions.js";
|
|
191
|
-
import { CompressionAlgorithms, disabledCompressionConfig } from "./compressionDefinitions.js";
|
|
192
|
-
import { ReportOpPerfTelemetry } from "./connectionTelemetry.js";
|
|
198
|
+
} from "./containerCompatibility.js";
|
|
193
199
|
import { ContainerFluidHandleContext } from "./containerHandleContext.js";
|
|
194
200
|
import { channelToDataStore } from "./dataStore.js";
|
|
195
201
|
import { FluidDataStoreRegistry } from "./dataStoreRegistry.js";
|
|
@@ -368,7 +374,7 @@ export interface ISummaryRuntimeOptions {
|
|
|
368
374
|
*
|
|
369
375
|
* @privateRemarks If any new properties are added to this interface (or
|
|
370
376
|
* {@link IContainerRuntimeOptionsInternal}), then we will also need to make
|
|
371
|
-
* changes in {@link file://./
|
|
377
|
+
* changes in {@link file://./containerCompatibility.ts}.
|
|
372
378
|
* If the new property does not change the DocumentSchema, then it must be
|
|
373
379
|
* explicity omitted from {@link RuntimeOptionsAffectingDocSchema}.
|
|
374
380
|
* If it does change the DocumentSchema, then a corresponding entry must be
|
|
@@ -702,6 +708,21 @@ export let getSingleUseLegacyLogCallback = (logger: ITelemetryLoggerExt, type: s
|
|
|
702
708
|
};
|
|
703
709
|
};
|
|
704
710
|
|
|
711
|
+
/**
|
|
712
|
+
* A {@link TypedMessage} that has unknown content explicitly
|
|
713
|
+
* noted as deserialized JSON.
|
|
714
|
+
*/
|
|
715
|
+
export interface UnknownIncomingTypedMessage extends TypedMessage {
|
|
716
|
+
content: OpaqueJsonDeserialized<unknown>;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Does nothing helper to apply unverified branding to a value.
|
|
721
|
+
*/
|
|
722
|
+
function markUnverified<const T>(value: T): T & UnverifiedBrand<T> {
|
|
723
|
+
return value as T & UnverifiedBrand<T>;
|
|
724
|
+
}
|
|
725
|
+
|
|
705
726
|
type UnsequencedSignalEnvelope = Omit<ISignalEnvelope, "clientBroadcastSignalSequenceNumber">;
|
|
706
727
|
|
|
707
728
|
/**
|
|
@@ -1173,16 +1194,6 @@ export class ContainerRuntime
|
|
|
1173
1194
|
recentBatchInfo,
|
|
1174
1195
|
);
|
|
1175
1196
|
|
|
1176
|
-
runtime.blobManager.stashedBlobsUploadP.then(
|
|
1177
|
-
() => {
|
|
1178
|
-
// make sure we didn't reconnect before the promise resolved
|
|
1179
|
-
if (runtime.delayConnectClientId !== undefined && !runtime.disposed) {
|
|
1180
|
-
runtime.delayConnectClientId = undefined;
|
|
1181
|
-
runtime.setConnectionStateCore(true, runtime.delayConnectClientId);
|
|
1182
|
-
}
|
|
1183
|
-
},
|
|
1184
|
-
(error: IErrorBase) => runtime.closeFn(error),
|
|
1185
|
-
);
|
|
1186
1197
|
// Initialize the base state of the runtime before it's returned.
|
|
1187
1198
|
await runtime.initializeBaseState(context.loader);
|
|
1188
1199
|
|
|
@@ -1194,7 +1205,6 @@ export class ContainerRuntime
|
|
|
1194
1205
|
}
|
|
1195
1206
|
|
|
1196
1207
|
public readonly options: Record<string | number, unknown>;
|
|
1197
|
-
private imminentClosure: boolean = false;
|
|
1198
1208
|
|
|
1199
1209
|
private readonly _getClientId: () => string | undefined;
|
|
1200
1210
|
public get clientId(): string | undefined {
|
|
@@ -1205,7 +1215,7 @@ export class ContainerRuntime
|
|
|
1205
1215
|
|
|
1206
1216
|
private readonly isSummarizerClient: boolean;
|
|
1207
1217
|
|
|
1208
|
-
public get storage():
|
|
1218
|
+
public get storage(): IContainerStorageService {
|
|
1209
1219
|
return this._storage;
|
|
1210
1220
|
}
|
|
1211
1221
|
|
|
@@ -1338,14 +1348,11 @@ export class ContainerRuntime
|
|
|
1338
1348
|
private flushScheduled = false;
|
|
1339
1349
|
|
|
1340
1350
|
private canSendOps: boolean;
|
|
1351
|
+
private canSendSignals: boolean | undefined;
|
|
1341
1352
|
|
|
1342
|
-
private
|
|
1353
|
+
private readonly getConnectionState?: () => ConnectionState;
|
|
1343
1354
|
|
|
1344
|
-
|
|
1345
|
-
* Used to delay transition to "connected" state while we upload
|
|
1346
|
-
* attachment blobs that were added while disconnected
|
|
1347
|
-
*/
|
|
1348
|
-
private delayConnectClientId?: string;
|
|
1355
|
+
private consecutiveReconnects = 0;
|
|
1349
1356
|
|
|
1350
1357
|
private readonly dataModelChangeRunner = new RunCounter();
|
|
1351
1358
|
|
|
@@ -1497,7 +1504,7 @@ export class ContainerRuntime
|
|
|
1497
1504
|
existing: boolean,
|
|
1498
1505
|
|
|
1499
1506
|
blobManagerLoadInfo: IBlobManagerLoadInfo,
|
|
1500
|
-
private readonly _storage:
|
|
1507
|
+
private readonly _storage: IContainerStorageService,
|
|
1501
1508
|
private readonly createIdCompressorFn: () => IIdCompressor & IIdCompressorCore,
|
|
1502
1509
|
|
|
1503
1510
|
private readonly documentsSchemaController: DocumentsSchemaController,
|
|
@@ -1536,15 +1543,18 @@ export class ContainerRuntime
|
|
|
1536
1543
|
pendingLocalState,
|
|
1537
1544
|
supportedFeatures,
|
|
1538
1545
|
snapshotWithContents,
|
|
1546
|
+
getConnectionState,
|
|
1539
1547
|
} = context;
|
|
1540
1548
|
|
|
1549
|
+
this.getConnectionState = getConnectionState;
|
|
1550
|
+
|
|
1541
1551
|
// In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
|
|
1542
1552
|
this.disposeFn = disposeFn ?? closeFn;
|
|
1543
1553
|
|
|
1544
1554
|
// Validate that the Loader is compatible with this Runtime.
|
|
1545
|
-
const
|
|
1555
|
+
const maybeLoaderCompatDetailsForRuntime = context as FluidObject<ILayerCompatDetails>;
|
|
1546
1556
|
validateLoaderCompatibility(
|
|
1547
|
-
|
|
1557
|
+
maybeLoaderCompatDetailsForRuntime.ILayerCompatDetails,
|
|
1548
1558
|
this.disposeFn,
|
|
1549
1559
|
);
|
|
1550
1560
|
|
|
@@ -1680,6 +1690,9 @@ export class ContainerRuntime
|
|
|
1680
1690
|
// Note that we only need to pull the *initial* connected state from the context.
|
|
1681
1691
|
// Later updates come through calls to setConnectionState.
|
|
1682
1692
|
this.canSendOps = connected;
|
|
1693
|
+
this.canSendSignals = this.getConnectionState
|
|
1694
|
+
? this.getConnectionState() === ConnectionState.Connected
|
|
1695
|
+
: undefined;
|
|
1683
1696
|
|
|
1684
1697
|
this.mc.logger.sendTelemetryEvent({
|
|
1685
1698
|
eventName: "GCFeatureMatrix",
|
|
@@ -1763,7 +1776,7 @@ export class ContainerRuntime
|
|
|
1763
1776
|
// If the context has ILayerCompatDetails, it supports referenceSequenceNumbers since that features
|
|
1764
1777
|
// predates ILayerCompatDetails.
|
|
1765
1778
|
const referenceSequenceNumbersSupported =
|
|
1766
|
-
|
|
1779
|
+
maybeLoaderCompatDetailsForRuntime.ILayerCompatDetails === undefined
|
|
1767
1780
|
? supportedFeatures?.get("referenceSequenceNumbers") === true
|
|
1768
1781
|
: true;
|
|
1769
1782
|
if (
|
|
@@ -1898,7 +1911,7 @@ export class ContainerRuntime
|
|
|
1898
1911
|
routeContext: this.handleContext,
|
|
1899
1912
|
blobManagerLoadInfo,
|
|
1900
1913
|
storage: this.storage,
|
|
1901
|
-
sendBlobAttachOp: (localId: string, blobId
|
|
1914
|
+
sendBlobAttachOp: (localId: string, blobId: string) => {
|
|
1902
1915
|
if (!this.disposed) {
|
|
1903
1916
|
this.submit(
|
|
1904
1917
|
{ type: ContainerMessageType.BlobAttach, contents: undefined },
|
|
@@ -2774,28 +2787,6 @@ export class ContainerRuntime
|
|
|
2774
2787
|
if (canSendOps && this.sessionSchema.idCompressorMode === "delayed") {
|
|
2775
2788
|
this.loadIdCompressor();
|
|
2776
2789
|
}
|
|
2777
|
-
if (canSendOps === false && this.delayConnectClientId !== undefined) {
|
|
2778
|
-
this.delayConnectClientId = undefined;
|
|
2779
|
-
this.mc.logger.sendTelemetryEvent({
|
|
2780
|
-
eventName: "UnsuccessfulConnectedTransition",
|
|
2781
|
-
});
|
|
2782
|
-
// Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
|
|
2783
|
-
return;
|
|
2784
|
-
}
|
|
2785
|
-
|
|
2786
|
-
// If there are stashed blobs in the pending state, we need to delay
|
|
2787
|
-
// propagation of the "connected" event until we have uploaded them to
|
|
2788
|
-
// ensure we don't submit ops referencing a blob that has not been uploaded
|
|
2789
|
-
const connecting = canSendOps && !this.canSendOps;
|
|
2790
|
-
if (connecting && this.blobManager.hasPendingStashedUploads()) {
|
|
2791
|
-
assert(
|
|
2792
|
-
!this.delayConnectClientId,
|
|
2793
|
-
0x791 /* Connect event delay must be canceled before subsequent connect event */,
|
|
2794
|
-
);
|
|
2795
|
-
assert(!!clientId, 0x792 /* Must have clientId when connecting */);
|
|
2796
|
-
this.delayConnectClientId = clientId;
|
|
2797
|
-
return;
|
|
2798
|
-
}
|
|
2799
2790
|
|
|
2800
2791
|
this.setConnectionStateCore(canSendOps, clientId);
|
|
2801
2792
|
}
|
|
@@ -2806,10 +2797,6 @@ export class ContainerRuntime
|
|
|
2806
2797
|
* @remarks The connection state from container context used here when raising connected events.
|
|
2807
2798
|
*/
|
|
2808
2799
|
private setConnectionStateCore(canSendOps: boolean, clientId?: string): void {
|
|
2809
|
-
assert(
|
|
2810
|
-
!this.delayConnectClientId,
|
|
2811
|
-
0x394 /* connect event delay must be cleared before propagating connect event */,
|
|
2812
|
-
);
|
|
2813
2800
|
this.verifyNotClosed();
|
|
2814
2801
|
|
|
2815
2802
|
// There might be no change of state due to Container calling this API after loading runtime.
|
|
@@ -2863,7 +2850,44 @@ export class ContainerRuntime
|
|
|
2863
2850
|
this.channelCollection.setConnectionState(canSendOps, clientId);
|
|
2864
2851
|
this.garbageCollector.setConnectionState(canSendOps, clientId);
|
|
2865
2852
|
|
|
2853
|
+
// Emit "connected" and "disconnected" events based on ability to send ops
|
|
2866
2854
|
raiseConnectedEvent(this.mc.logger, this, this.connected /* canSendOps */, clientId);
|
|
2855
|
+
// Emit "connectedToService" and "disconnectedFromService" events based on service connection status
|
|
2856
|
+
this.emitServiceConnectionEvents(canSendOpsChanged, canSendOps, clientId);
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
/**
|
|
2860
|
+
* Emits service connection events based on connection state changes.
|
|
2861
|
+
*
|
|
2862
|
+
* @remarks
|
|
2863
|
+
* "connectedToService" is emitted when container connection state transitions to 'Connected' regardless of connection mode.
|
|
2864
|
+
* "disconnectedFromService" excludes false "disconnected" events that happen when readonly client transitions to 'Connected'.
|
|
2865
|
+
*/
|
|
2866
|
+
private emitServiceConnectionEvents(
|
|
2867
|
+
canSendOpsChanged: boolean,
|
|
2868
|
+
canSendOps: boolean,
|
|
2869
|
+
clientId?: string,
|
|
2870
|
+
): void {
|
|
2871
|
+
if (!this.getConnectionState) {
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
const canSendSignals = this.getConnectionState() === ConnectionState.Connected;
|
|
2876
|
+
const canSendSignalsChanged = this.canSendSignals !== canSendSignals;
|
|
2877
|
+
this.canSendSignals = canSendSignals;
|
|
2878
|
+
if (canSendSignalsChanged) {
|
|
2879
|
+
// If canSendSignals changed, we either transitioned from Connected to Disconnected or CatchingUp to Connected
|
|
2880
|
+
if (canSendSignals) {
|
|
2881
|
+
// Emit for CatchingUp to Connected transition
|
|
2882
|
+
this.emit("connectedToService", clientId, canSendOps);
|
|
2883
|
+
} else {
|
|
2884
|
+
// Emit for Connected to Disconnected transition
|
|
2885
|
+
this.emit("disconnectedFromService");
|
|
2886
|
+
}
|
|
2887
|
+
} else if (canSendOpsChanged) {
|
|
2888
|
+
// If canSendSignals did not change but canSendOps did, then connection type has changed.
|
|
2889
|
+
this.emit("connectionTypeChanged", canSendOps);
|
|
2890
|
+
}
|
|
2867
2891
|
}
|
|
2868
2892
|
|
|
2869
2893
|
public async notifyOpReplay(message: ISequencedDocumentMessage): Promise<void> {
|
|
@@ -3289,17 +3313,17 @@ export class ContainerRuntime
|
|
|
3289
3313
|
public processSignal(
|
|
3290
3314
|
message: ISignalMessage<{
|
|
3291
3315
|
type: string;
|
|
3292
|
-
content: ISignalEnvelope<{ type: string; content:
|
|
3316
|
+
content: ISignalEnvelope<{ type: string; content: OpaqueJsonDeserialized<unknown> }>;
|
|
3293
3317
|
}>,
|
|
3294
3318
|
local: boolean,
|
|
3295
3319
|
): void {
|
|
3296
3320
|
const envelope = message.content;
|
|
3297
|
-
const transformed = {
|
|
3321
|
+
const transformed = markUnverified({
|
|
3298
3322
|
clientId: message.clientId,
|
|
3299
3323
|
content: envelope.contents.content,
|
|
3300
3324
|
type: envelope.contents.type,
|
|
3301
3325
|
targetClientId: message.targetClientId,
|
|
3302
|
-
};
|
|
3326
|
+
});
|
|
3303
3327
|
|
|
3304
3328
|
// Only collect signal telemetry for broadcast messages sent by the current client.
|
|
3305
3329
|
if (message.clientId === this.clientId) {
|
|
@@ -3322,7 +3346,8 @@ export class ContainerRuntime
|
|
|
3322
3346
|
|
|
3323
3347
|
private routeNonContainerSignal(
|
|
3324
3348
|
address: string,
|
|
3325
|
-
signalMessage: IInboundSignalMessage<
|
|
3349
|
+
signalMessage: IInboundSignalMessage<UnknownIncomingTypedMessage> &
|
|
3350
|
+
UnverifiedBrand<UnknownIncomingTypedMessage>,
|
|
3326
3351
|
local: boolean,
|
|
3327
3352
|
): void {
|
|
3328
3353
|
// channelCollection signals are identified by no starting `/` in address.
|
|
@@ -3330,13 +3355,15 @@ export class ContainerRuntime
|
|
|
3330
3355
|
// Due to a mismatch between different layers in terms of
|
|
3331
3356
|
// what is the interface of passing signals, we need to adjust
|
|
3332
3357
|
// the signal envelope before sending it to the datastores to be processed
|
|
3333
|
-
const
|
|
3334
|
-
|
|
3335
|
-
|
|
3358
|
+
const channelSignalMessage = {
|
|
3359
|
+
...signalMessage,
|
|
3360
|
+
content: {
|
|
3361
|
+
address,
|
|
3362
|
+
contents: signalMessage.content,
|
|
3363
|
+
},
|
|
3336
3364
|
};
|
|
3337
|
-
signalMessage.content = envelope;
|
|
3338
3365
|
|
|
3339
|
-
this.channelCollection.processSignal(
|
|
3366
|
+
this.channelCollection.processSignal(channelSignalMessage, local);
|
|
3340
3367
|
return;
|
|
3341
3368
|
}
|
|
3342
3369
|
|
|
@@ -3605,9 +3632,7 @@ export class ContainerRuntime
|
|
|
3605
3632
|
private shouldSendOps(): boolean {
|
|
3606
3633
|
// Note that the real (non-proxy) delta manager is needed here to get the readonly info. This is because
|
|
3607
3634
|
// container runtime's ability to send ops depend on the actual readonly state of the delta manager.
|
|
3608
|
-
return
|
|
3609
|
-
this.connected && !this.innerDeltaManager.readOnlyInfo.readonly && !this.imminentClosure
|
|
3610
|
-
);
|
|
3635
|
+
return this.connected && !this.innerDeltaManager.readOnlyInfo.readonly;
|
|
3611
3636
|
}
|
|
3612
3637
|
|
|
3613
3638
|
private readonly _quorum: IQuorumClients;
|
|
@@ -5016,60 +5041,45 @@ export class ContainerRuntime
|
|
|
5016
5041
|
|
|
5017
5042
|
public getPendingLocalState(props?: IGetPendingLocalStateProps): unknown {
|
|
5018
5043
|
this.verifyNotClosed();
|
|
5044
|
+
if (props?.notifyImminentClosure) {
|
|
5045
|
+
throw new UsageError("notifyImminentClosure is no longer supported in ContainerRuntime");
|
|
5046
|
+
}
|
|
5019
5047
|
|
|
5020
5048
|
if (this.batchRunner.running) {
|
|
5021
5049
|
throw new UsageError("can't get state while manually accumulating a batch");
|
|
5022
5050
|
}
|
|
5023
|
-
this.imminentClosure ||= props?.notifyImminentClosure ?? false;
|
|
5024
|
-
|
|
5025
|
-
const getSyncState = (
|
|
5026
|
-
pendingAttachmentBlobs?: IPendingBlobs,
|
|
5027
|
-
): IPendingRuntimeState | undefined => {
|
|
5028
|
-
const pending = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
|
|
5029
|
-
const sessionExpiryTimerStarted =
|
|
5030
|
-
props?.sessionExpiryTimerStarted ?? this.garbageCollector.sessionExpiryTimerStarted;
|
|
5031
|
-
|
|
5032
|
-
const pendingIdCompressorState = this._idCompressor?.serialize(true);
|
|
5033
|
-
|
|
5034
|
-
return {
|
|
5035
|
-
pending,
|
|
5036
|
-
pendingIdCompressorState,
|
|
5037
|
-
pendingAttachmentBlobs,
|
|
5038
|
-
sessionExpiryTimerStarted,
|
|
5039
|
-
};
|
|
5040
|
-
};
|
|
5041
|
-
const perfEvent = {
|
|
5042
|
-
eventName: "getPendingLocalState",
|
|
5043
|
-
notifyImminentClosure: props?.notifyImminentClosure,
|
|
5044
|
-
};
|
|
5045
|
-
const logAndReturnPendingState = (
|
|
5046
|
-
event: PerformanceEvent,
|
|
5047
|
-
pendingState?: IPendingRuntimeState,
|
|
5048
|
-
): IPendingRuntimeState | undefined => {
|
|
5049
|
-
event.end({
|
|
5050
|
-
attachmentBlobsSize: Object.keys(pendingState?.pendingAttachmentBlobs ?? {}).length,
|
|
5051
|
-
pendingOpsSize: pendingState?.pending?.pendingStates.length,
|
|
5052
|
-
});
|
|
5053
|
-
return pendingState;
|
|
5054
|
-
};
|
|
5055
5051
|
|
|
5056
5052
|
// Flush pending batch.
|
|
5057
|
-
// getPendingLocalState() is only exposed through Container.
|
|
5053
|
+
// getPendingLocalState() is only exposed through Container.getPendingLocalState(), so it's safe
|
|
5058
5054
|
// to close current batch.
|
|
5059
5055
|
this.flush();
|
|
5060
5056
|
|
|
5061
|
-
return
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
);
|
|
5057
|
+
return PerformanceEvent.timedExec<IPendingRuntimeState | undefined>(
|
|
5058
|
+
this.mc.logger,
|
|
5059
|
+
{
|
|
5060
|
+
eventName: "getPendingLocalState",
|
|
5061
|
+
},
|
|
5062
|
+
(event) => {
|
|
5063
|
+
const pending = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
|
|
5064
|
+
const sessionExpiryTimerStarted =
|
|
5065
|
+
props?.sessionExpiryTimerStarted ?? this.garbageCollector.sessionExpiryTimerStarted;
|
|
5066
|
+
|
|
5067
|
+
const pendingIdCompressorState = this._idCompressor?.serialize(true);
|
|
5068
|
+
const pendingAttachmentBlobs = this.blobManager.getPendingBlobs();
|
|
5069
|
+
|
|
5070
|
+
const pendingRuntimeState: IPendingRuntimeState = {
|
|
5071
|
+
pending,
|
|
5072
|
+
pendingIdCompressorState,
|
|
5073
|
+
pendingAttachmentBlobs,
|
|
5074
|
+
sessionExpiryTimerStarted,
|
|
5075
|
+
};
|
|
5076
|
+
event.end({
|
|
5077
|
+
attachmentBlobsSize: Object.keys(pendingAttachmentBlobs ?? {}).length,
|
|
5078
|
+
pendingOpsSize: pendingRuntimeState?.pending?.pendingStates.length,
|
|
5079
|
+
});
|
|
5080
|
+
return pendingRuntimeState;
|
|
5081
|
+
},
|
|
5082
|
+
);
|
|
5073
5083
|
}
|
|
5074
5084
|
|
|
5075
5085
|
public summarizeOnDemand(options: IOnDemandSummarizeOptions): ISummarizeResults {
|
|
@@ -5103,11 +5113,36 @@ export class ContainerRuntime
|
|
|
5103
5113
|
// It is lazily create to avoid listeners (old events) that ultimately go nowhere.
|
|
5104
5114
|
private readonly lazyEventsForExtensions = new Lazy<Listenable<ExtensionHostEvents>>(() => {
|
|
5105
5115
|
const eventEmitter = createEmitter<ExtensionHostEvents>();
|
|
5106
|
-
this.
|
|
5107
|
-
|
|
5116
|
+
if (this.getConnectionState) {
|
|
5117
|
+
this.on("connectedToService", (clientId: string, canWrite: boolean) => {
|
|
5118
|
+
eventEmitter.emit("joined", { clientId, canWrite });
|
|
5119
|
+
});
|
|
5120
|
+
this.on("disconnectedFromService", () => eventEmitter.emit("disconnected"));
|
|
5121
|
+
this.on("connectionTypeChanged", (canWrite: boolean) =>
|
|
5122
|
+
eventEmitter.emit("connectionTypeChanged", canWrite),
|
|
5123
|
+
);
|
|
5124
|
+
} else {
|
|
5125
|
+
this.on("connected", (clientId: string) => {
|
|
5126
|
+
eventEmitter.emit("joined", { clientId, canWrite: true });
|
|
5127
|
+
});
|
|
5128
|
+
this.on("disconnected", () => eventEmitter.emit("disconnected"));
|
|
5129
|
+
}
|
|
5108
5130
|
return eventEmitter;
|
|
5109
5131
|
});
|
|
5110
5132
|
|
|
5133
|
+
private getJoinedStatus(): JoinedStatus {
|
|
5134
|
+
const getConnectionState = this.getConnectionState;
|
|
5135
|
+
if (getConnectionState) {
|
|
5136
|
+
const connectionState = getConnectionState();
|
|
5137
|
+
if (connectionState === ConnectionState.Connected) {
|
|
5138
|
+
return this.canSendOps ? "joinedForWriting" : "joinedForReading";
|
|
5139
|
+
}
|
|
5140
|
+
} else if (this.canSendOps) {
|
|
5141
|
+
return "joinedForWriting";
|
|
5142
|
+
}
|
|
5143
|
+
return "disconnected";
|
|
5144
|
+
}
|
|
5145
|
+
|
|
5111
5146
|
private readonly submitExtensionSignal: <TMessage extends TypedMessage>(
|
|
5112
5147
|
id: string,
|
|
5113
5148
|
addressChain: string[],
|
|
@@ -5126,7 +5161,7 @@ export class ContainerRuntime
|
|
|
5126
5161
|
let entry = this.extensions.get(id);
|
|
5127
5162
|
if (entry === undefined) {
|
|
5128
5163
|
const runtime = {
|
|
5129
|
-
|
|
5164
|
+
getJoinedStatus: this.getJoinedStatus.bind(this),
|
|
5130
5165
|
getClientId: () => this.clientId,
|
|
5131
5166
|
events: this.lazyEventsForExtensions.value,
|
|
5132
5167
|
logger: this.baseLogger,
|