@fluidframework/container-runtime 2.70.0-361788 → 2.71.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/.eslintrc.cjs +5 -1
- package/CHANGELOG.md +14 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/channelCollection.d.ts +66 -17
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +118 -84
- package/dist/channelCollection.js.map +1 -1
- package/dist/containerRuntime.d.ts +19 -11
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +146 -52
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts +3 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +8 -9
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +6 -5
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +7 -4
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/messageTypes.d.ts +17 -4
- package/dist/messageTypes.d.ts.map +1 -1
- package/dist/messageTypes.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/runtimeLayerCompatState.d.ts +2 -2
- package/dist/runtimeLayerCompatState.d.ts.map +1 -1
- package/dist/runtimeLayerCompatState.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.js +3 -1
- package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/lib/channelCollection.d.ts +66 -17
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +115 -82
- package/lib/channelCollection.js.map +1 -1
- package/lib/containerRuntime.d.ts +19 -11
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +148 -53
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts +3 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +3 -4
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +6 -5
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +8 -5
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/index.d.ts +3 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/messageTypes.d.ts +17 -4
- package/lib/messageTypes.d.ts.map +1 -1
- package/lib/messageTypes.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/runtimeLayerCompatState.d.ts +2 -2
- package/lib/runtimeLayerCompatState.d.ts.map +1 -1
- package/lib/runtimeLayerCompatState.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.js +3 -1
- package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/package.json +22 -30
- package/src/channelCollection.ts +255 -109
- package/src/containerRuntime.ts +262 -92
- package/src/dataStore.ts +12 -10
- package/src/dataStoreContext.ts +45 -39
- package/src/index.ts +8 -3
- package/src/messageTypes.ts +17 -2
- package/src/packageVersion.ts +1 -1
- package/src/runtimeLayerCompatState.ts +1 -1
- package/src/summary/summarizerNode/summarizerNode.ts +1 -0
package/src/containerRuntime.ts
CHANGED
|
@@ -7,7 +7,12 @@ import type {
|
|
|
7
7
|
ILayerCompatDetails,
|
|
8
8
|
IProvideLayerCompatDetails,
|
|
9
9
|
} from "@fluid-internal/client-utils";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
checkLayerCompatibility,
|
|
12
|
+
createEmitter,
|
|
13
|
+
Trace,
|
|
14
|
+
TypedEventEmitter,
|
|
15
|
+
} from "@fluid-internal/client-utils";
|
|
11
16
|
import type {
|
|
12
17
|
IAudience,
|
|
13
18
|
ISelf,
|
|
@@ -23,6 +28,7 @@ import type {
|
|
|
23
28
|
IDeltaManagerFull,
|
|
24
29
|
ILoader,
|
|
25
30
|
IContainerStorageService,
|
|
31
|
+
ConnectionStatus,
|
|
26
32
|
} from "@fluidframework/container-definitions/internal";
|
|
27
33
|
import {
|
|
28
34
|
ConnectionState,
|
|
@@ -33,6 +39,7 @@ import type {
|
|
|
33
39
|
ContainerExtensionId,
|
|
34
40
|
ExtensionHost,
|
|
35
41
|
ExtensionHostEvents,
|
|
42
|
+
ExtensionInstantiationResult,
|
|
36
43
|
ExtensionRuntimeProperties,
|
|
37
44
|
IContainerRuntime,
|
|
38
45
|
IContainerRuntimeEvents,
|
|
@@ -110,9 +117,9 @@ import type {
|
|
|
110
117
|
IGarbageCollectionData,
|
|
111
118
|
CreateChildSummarizerNodeParam,
|
|
112
119
|
IDataStore,
|
|
113
|
-
IEnvelope,
|
|
114
120
|
IFluidDataStoreContextDetached,
|
|
115
121
|
IFluidDataStoreRegistry,
|
|
122
|
+
IFluidParentContext,
|
|
116
123
|
ISummarizeInternalResult,
|
|
117
124
|
InboundAttachMessage,
|
|
118
125
|
NamedFluidDataStoreRegistryEntries,
|
|
@@ -120,12 +127,10 @@ import type {
|
|
|
120
127
|
IInboundSignalMessage,
|
|
121
128
|
IRuntimeMessagesContent,
|
|
122
129
|
ISummarizerNodeWithGC,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// eslint-disable-next-line import/no-deprecated
|
|
126
|
-
IContainerRuntimeBaseExperimental,
|
|
127
|
-
IFluidParentContext,
|
|
130
|
+
StageControlsInternal,
|
|
131
|
+
IContainerRuntimeBaseInternal,
|
|
128
132
|
MinimumVersionForCollab,
|
|
133
|
+
ContainerExtensionExpectations,
|
|
129
134
|
} from "@fluidframework/runtime-definitions/internal";
|
|
130
135
|
import {
|
|
131
136
|
addBlobToSummary,
|
|
@@ -181,10 +186,14 @@ import {
|
|
|
181
186
|
loadBlobManagerLoadInfo,
|
|
182
187
|
type IBlobManagerLoadInfo,
|
|
183
188
|
} from "./blobManager/index.js";
|
|
189
|
+
import type {
|
|
190
|
+
AddressedUnsequencedSignalEnvelope,
|
|
191
|
+
IFluidRootParentContextPrivate,
|
|
192
|
+
} from "./channelCollection.js";
|
|
184
193
|
import {
|
|
185
194
|
ChannelCollection,
|
|
195
|
+
formParentContext,
|
|
186
196
|
getSummaryForDatastores,
|
|
187
|
-
wrapContext,
|
|
188
197
|
} from "./channelCollection.js";
|
|
189
198
|
import type { ICompressionRuntimeOptions } from "./compressionDefinitions.js";
|
|
190
199
|
import { CompressionAlgorithms, disabledCompressionConfig } from "./compressionDefinitions.js";
|
|
@@ -218,11 +227,14 @@ import {
|
|
|
218
227
|
import { InboundBatchAggregator } from "./inboundBatchAggregator.js";
|
|
219
228
|
import {
|
|
220
229
|
ContainerMessageType,
|
|
230
|
+
type ContainerRuntimeAliasMessage,
|
|
231
|
+
type ContainerRuntimeDataStoreOpMessage,
|
|
221
232
|
type OutboundContainerRuntimeDocumentSchemaMessage,
|
|
222
233
|
type ContainerRuntimeGCMessage,
|
|
223
234
|
type ContainerRuntimeIdAllocationMessage,
|
|
224
235
|
type InboundSequencedContainerRuntimeMessage,
|
|
225
236
|
type LocalContainerRuntimeMessage,
|
|
237
|
+
type OutboundContainerRuntimeAttachMessage,
|
|
226
238
|
type UnknownContainerRuntimeMessage,
|
|
227
239
|
} from "./messageTypes.js";
|
|
228
240
|
import type { ISavedOpMetadata } from "./metadata.js";
|
|
@@ -251,6 +263,7 @@ import {
|
|
|
251
263
|
import { BatchRunCounter, RunCounter } from "./runCounter.js";
|
|
252
264
|
import {
|
|
253
265
|
runtimeCompatDetailsForLoader,
|
|
266
|
+
runtimeCoreCompatDetails,
|
|
254
267
|
validateLoaderCompatibility,
|
|
255
268
|
} from "./runtimeLayerCompatState.js";
|
|
256
269
|
import { SignalTelemetryManager } from "./signalTelemetryProcessing.js";
|
|
@@ -311,11 +324,19 @@ import { Throttler, formExponentialFn } from "./throttler.js";
|
|
|
311
324
|
* A {@link ContainerExtension}'s factory function as stored in extension map.
|
|
312
325
|
*/
|
|
313
326
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- `any` required to allow typed factory to be assignable per ContainerExtension.processSignal
|
|
314
|
-
type ExtensionEntry =
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
327
|
+
type ExtensionEntry = ExtensionInstantiationResult<unknown, any, unknown[]>;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* ContainerRuntime's compatibility details that is exposed to Container Extensions.
|
|
331
|
+
*/
|
|
332
|
+
const containerRuntimeCompatDetailsForContainerExtensions = {
|
|
333
|
+
...runtimeCoreCompatDetails,
|
|
334
|
+
/**
|
|
335
|
+
* The features supported by the ContainerRuntime's ContainerExtensionStore
|
|
336
|
+
* implementation.
|
|
337
|
+
*/
|
|
338
|
+
supportedFeatures: new Set<string>(),
|
|
339
|
+
} as const satisfies ILayerCompatDetails;
|
|
319
340
|
|
|
320
341
|
/**
|
|
321
342
|
* Creates an error object to be thrown / passed to Container's close fn in case of an unknown message type.
|
|
@@ -817,18 +838,23 @@ export class ContainerRuntime
|
|
|
817
838
|
extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
818
839
|
implements
|
|
819
840
|
IContainerRuntimeInternal,
|
|
820
|
-
|
|
821
|
-
IContainerRuntimeBaseExperimental,
|
|
841
|
+
IContainerRuntimeBaseInternal,
|
|
822
842
|
// eslint-disable-next-line import/no-deprecated
|
|
823
843
|
IContainerRuntimeWithResolveHandle_Deprecated,
|
|
824
844
|
IRuntime,
|
|
825
845
|
IGarbageCollectionRuntime,
|
|
826
846
|
ISummarizerRuntime,
|
|
827
847
|
ISummarizerInternalsProvider,
|
|
828
|
-
|
|
848
|
+
// If ContainerRuntime stops being exported from this package, this can
|
|
849
|
+
// be updated to implement IFluidRootParentContextPrivate and leave
|
|
850
|
+
// submitMessage included.
|
|
851
|
+
// IFluidParentContextPrivate is also better than IFluidParentContext
|
|
852
|
+
// and is also internal only; so, not usable here.
|
|
853
|
+
Omit<IFluidParentContext, "submitMessage" | "submitSignal">,
|
|
829
854
|
IProvideFluidHandleContext,
|
|
830
855
|
IProvideLayerCompatDetails
|
|
831
856
|
{
|
|
857
|
+
/* eslint-disable @fluid-internal/fluid/no-hyphen-after-jsdoc-tag -- false positive AB#50920 */
|
|
832
858
|
/**
|
|
833
859
|
* Load the stores from a snapshot and returns the runtime.
|
|
834
860
|
* @param params - An object housing the runtime properties.
|
|
@@ -857,6 +883,7 @@ export class ContainerRuntime
|
|
|
857
883
|
registry: new FluidDataStoreRegistry(params.registryEntries),
|
|
858
884
|
});
|
|
859
885
|
}
|
|
886
|
+
/* eslint-enable @fluid-internal/fluid/no-hyphen-after-jsdoc-tag */
|
|
860
887
|
|
|
861
888
|
/**
|
|
862
889
|
* Load the stores from a snapshot and returns the runtime.
|
|
@@ -1570,6 +1597,7 @@ export class ContainerRuntime
|
|
|
1570
1597
|
deltaManager,
|
|
1571
1598
|
quorum,
|
|
1572
1599
|
audience,
|
|
1600
|
+
signalAudience,
|
|
1573
1601
|
pendingLocalState,
|
|
1574
1602
|
supportedFeatures,
|
|
1575
1603
|
snapshotWithContents,
|
|
@@ -1643,11 +1671,10 @@ export class ContainerRuntime
|
|
|
1643
1671
|
message: OutboundExtensionMessage<TMessage>,
|
|
1644
1672
|
): void => {
|
|
1645
1673
|
this.verifyNotClosed();
|
|
1646
|
-
const envelope =
|
|
1647
|
-
`/ext/${id}/${addressChain.join("/")}`,
|
|
1648
|
-
message
|
|
1649
|
-
|
|
1650
|
-
);
|
|
1674
|
+
const envelope = {
|
|
1675
|
+
address: `/ext/${id}/${addressChain.join("/")}`,
|
|
1676
|
+
contents: message,
|
|
1677
|
+
} satisfies UnsequencedSignalEnvelope;
|
|
1651
1678
|
sequenceAndSubmitSignal(envelope, message.targetClientId);
|
|
1652
1679
|
};
|
|
1653
1680
|
|
|
@@ -1721,10 +1748,11 @@ export class ContainerRuntime
|
|
|
1721
1748
|
this.messageAtLastSummary = lastMessageFromMetadata(metadata);
|
|
1722
1749
|
|
|
1723
1750
|
// Note that we only need to pull the *initial* connected state from the context.
|
|
1724
|
-
// Later updates come through calls to setConnectionState.
|
|
1751
|
+
// Later updates come through calls to setConnectionState/Status.
|
|
1725
1752
|
this.canSendOps = connected;
|
|
1726
1753
|
this.canSendSignals = this.getConnectionState
|
|
1727
|
-
? this.getConnectionState() === ConnectionState.Connected
|
|
1754
|
+
? this.getConnectionState() === ConnectionState.Connected ||
|
|
1755
|
+
this.getConnectionState() === ConnectionState.CatchingUp
|
|
1728
1756
|
: undefined;
|
|
1729
1757
|
|
|
1730
1758
|
this.mc.logger.sendTelemetryEvent({
|
|
@@ -1899,18 +1927,20 @@ export class ContainerRuntime
|
|
|
1899
1927
|
async () => this.garbageCollector.getBaseGCDetails(),
|
|
1900
1928
|
);
|
|
1901
1929
|
|
|
1902
|
-
const parentContext =
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1930
|
+
const parentContext = formParentContext<IFluidRootParentContextPrivate>(this, {
|
|
1931
|
+
submitMessage: this.submitMessage.bind(this),
|
|
1932
|
+
|
|
1933
|
+
// Due to a mismatch between different layers in terms of
|
|
1934
|
+
// what is the interface of passing signals, we need the
|
|
1935
|
+
// downstream stores to wrap the signal.
|
|
1936
|
+
submitSignal: (
|
|
1937
|
+
envelope: AddressedUnsequencedSignalEnvelope,
|
|
1938
|
+
targetClientId?: string,
|
|
1939
|
+
): void => {
|
|
1940
|
+
// verifyNotClosed is called in FluidDataStoreContext, which is *the* expected caller.
|
|
1941
|
+
this.submitSignalFn(envelope, targetClientId);
|
|
1942
|
+
},
|
|
1943
|
+
});
|
|
1914
1944
|
|
|
1915
1945
|
let snapshot: ISnapshot | ISnapshotTree | undefined = getSummaryForDatastores(
|
|
1916
1946
|
baseSnapshot,
|
|
@@ -1934,7 +1964,6 @@ export class ContainerRuntime
|
|
|
1934
1964
|
}),
|
|
1935
1965
|
(path: string) => this.garbageCollector.isNodeDeleted(path),
|
|
1936
1966
|
new Map<string, string>(dataStoreAliasMap),
|
|
1937
|
-
async (runtime: ChannelCollection) => provideEntryPoint,
|
|
1938
1967
|
);
|
|
1939
1968
|
this._deltaManager.on("readonly", this.notifyReadOnlyState);
|
|
1940
1969
|
|
|
@@ -2040,6 +2069,8 @@ export class ContainerRuntime
|
|
|
2040
2069
|
});
|
|
2041
2070
|
}
|
|
2042
2071
|
|
|
2072
|
+
this.signalAudience = signalAudience;
|
|
2073
|
+
|
|
2043
2074
|
const closeSummarizerDelayOverride = this.mc.config.getNumber(
|
|
2044
2075
|
"Fluid.ContainerRuntime.Test.CloseSummarizerDelayOverrideMs",
|
|
2045
2076
|
);
|
|
@@ -2812,6 +2843,59 @@ export class ContainerRuntime
|
|
|
2812
2843
|
this.channelCollection.notifyReadOnlyState(readonly);
|
|
2813
2844
|
|
|
2814
2845
|
public setConnectionState(canSendOps: boolean, clientId?: string): void {
|
|
2846
|
+
this.setConnectionStateToConnectedOrDisconnected(canSendOps, clientId);
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
public setConnectionStatus(status: ConnectionStatus): void {
|
|
2850
|
+
switch (status.connectionState) {
|
|
2851
|
+
case ConnectionState.Connected: {
|
|
2852
|
+
this.setConnectionStateToConnectedOrDisconnected(
|
|
2853
|
+
status.canSendOps,
|
|
2854
|
+
status.clientConnectionId,
|
|
2855
|
+
);
|
|
2856
|
+
|
|
2857
|
+
break;
|
|
2858
|
+
}
|
|
2859
|
+
case ConnectionState.Disconnected: {
|
|
2860
|
+
this.setConnectionStateToConnectedOrDisconnected(
|
|
2861
|
+
status.canSendOps,
|
|
2862
|
+
status.priorConnectedClientConnectionId,
|
|
2863
|
+
);
|
|
2864
|
+
|
|
2865
|
+
break;
|
|
2866
|
+
}
|
|
2867
|
+
case ConnectionState.CatchingUp: {
|
|
2868
|
+
assert(
|
|
2869
|
+
this.getConnectionState !== undefined &&
|
|
2870
|
+
this.getConnectionState() === ConnectionState.CatchingUp,
|
|
2871
|
+
0xc8d /* connection state mismatch between getConnectionState and setConnectionStatus notification */,
|
|
2872
|
+
);
|
|
2873
|
+
|
|
2874
|
+
// Note: Historically when only `setConnectionState` of `IRuntime`
|
|
2875
|
+
// was supported, it was possible to be in `CatchingUp` state and
|
|
2876
|
+
// call through to `setConnectionStateCore` when there is a readonly
|
|
2877
|
+
// change - see `Container`'s `"deltaManager.on("readonly"`. There
|
|
2878
|
+
// would not be a transition of `canSendOps` in that case, but
|
|
2879
|
+
// `channelCollection` and `garbageCollector` would receive early
|
|
2880
|
+
// `setConnectionState` call AND `this` would `emit` "disconnected"
|
|
2881
|
+
// event.
|
|
2882
|
+
|
|
2883
|
+
this.emitServiceConnectionEvents(
|
|
2884
|
+
/* canSendOpsChanged */ this.canSendOps,
|
|
2885
|
+
/* canSendOps */ false,
|
|
2886
|
+
status.pendingClientConnectionId,
|
|
2887
|
+
);
|
|
2888
|
+
|
|
2889
|
+
break;
|
|
2890
|
+
}
|
|
2891
|
+
// No default
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
private setConnectionStateToConnectedOrDisconnected(
|
|
2896
|
+
canSendOps: boolean,
|
|
2897
|
+
clientId: string | undefined,
|
|
2898
|
+
): void {
|
|
2815
2899
|
// Validate we have consistent state
|
|
2816
2900
|
const currentClientId = this._audience.getSelf()?.clientId;
|
|
2817
2901
|
assert(clientId === currentClientId, 0x977 /* input clientId does not match Audience */);
|
|
@@ -2896,7 +2980,7 @@ export class ContainerRuntime
|
|
|
2896
2980
|
* Emits service connection events based on connection state changes.
|
|
2897
2981
|
*
|
|
2898
2982
|
* @remarks
|
|
2899
|
-
* "connectedToService" is emitted when container connection state transitions to 'Connected' regardless of connection mode.
|
|
2983
|
+
* "connectedToService" is emitted when container connection state transitions to 'CatchingUp' or 'Connected' regardless of connection mode.
|
|
2900
2984
|
* "disconnectedFromService" excludes false "disconnected" events that happen when readonly client transitions to 'Connected'.
|
|
2901
2985
|
*/
|
|
2902
2986
|
private emitServiceConnectionEvents(
|
|
@@ -2908,21 +2992,25 @@ export class ContainerRuntime
|
|
|
2908
2992
|
return;
|
|
2909
2993
|
}
|
|
2910
2994
|
|
|
2911
|
-
const
|
|
2995
|
+
const connectionState = this.getConnectionState();
|
|
2996
|
+
const canSendSignals =
|
|
2997
|
+
connectionState === ConnectionState.Connected ||
|
|
2998
|
+
connectionState === ConnectionState.CatchingUp;
|
|
2912
2999
|
const canSendSignalsChanged = this.canSendSignals !== canSendSignals;
|
|
2913
3000
|
this.canSendSignals = canSendSignals;
|
|
2914
3001
|
if (canSendSignalsChanged) {
|
|
2915
|
-
// If canSendSignals changed, we either transitioned from
|
|
3002
|
+
// If canSendSignals changed, we either transitioned from CatchingUp or
|
|
3003
|
+
// Connected to Disconnected or EstablishingConnection to CatchingUp.
|
|
2916
3004
|
if (canSendSignals) {
|
|
2917
|
-
// Emit for
|
|
3005
|
+
// Emit for EstablishingConnection to CatchingUp or Connected transition
|
|
2918
3006
|
this.emit("connectedToService", clientId, canSendOps);
|
|
2919
3007
|
} else {
|
|
2920
|
-
// Emit for Connected to Disconnected transition
|
|
3008
|
+
// Emit for CatchingUp or Connected to Disconnected transition
|
|
2921
3009
|
this.emit("disconnectedFromService");
|
|
2922
3010
|
}
|
|
2923
3011
|
} else if (canSendOpsChanged) {
|
|
2924
|
-
// If canSendSignals did not change but canSendOps did, then
|
|
2925
|
-
this.emit("
|
|
3012
|
+
// If canSendSignals did not change but canSendOps did, then operations possible has changed.
|
|
3013
|
+
this.emit("operabilityChanged", canSendOps);
|
|
2926
3014
|
}
|
|
2927
3015
|
}
|
|
2928
3016
|
|
|
@@ -3455,8 +3543,7 @@ export class ContainerRuntime
|
|
|
3455
3543
|
*/
|
|
3456
3544
|
public orderSequentially<T>(callback: () => T): T {
|
|
3457
3545
|
let checkpoint: IBatchCheckpoint | undefined;
|
|
3458
|
-
|
|
3459
|
-
let stageControls: StageControlsExperimental | undefined;
|
|
3546
|
+
let stageControls: StageControlsInternal | undefined;
|
|
3460
3547
|
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback") === true) {
|
|
3461
3548
|
if (!this.batchRunner.running && !this.inStagingMode) {
|
|
3462
3549
|
stageControls = this.enterStagingMode();
|
|
@@ -3520,8 +3607,7 @@ export class ContainerRuntime
|
|
|
3520
3607
|
return result;
|
|
3521
3608
|
}
|
|
3522
3609
|
|
|
3523
|
-
|
|
3524
|
-
private stageControls: StageControlsExperimental | undefined;
|
|
3610
|
+
private stageControls: StageControlsInternal | undefined;
|
|
3525
3611
|
|
|
3526
3612
|
/**
|
|
3527
3613
|
* If true, the ContainerRuntime is not submitting any new ops to the ordering service.
|
|
@@ -3536,10 +3622,9 @@ export class ContainerRuntime
|
|
|
3536
3622
|
* Enter Staging Mode, such that ops submitted to the ContainerRuntime will not be sent to the ordering service.
|
|
3537
3623
|
* To exit Staging Mode, call either discardChanges or commitChanges on the Stage Controls returned from this method.
|
|
3538
3624
|
*
|
|
3539
|
-
* @returns
|
|
3625
|
+
* @returns Controls for exiting Staging Mode.
|
|
3540
3626
|
*/
|
|
3541
|
-
|
|
3542
|
-
public enterStagingMode = (): StageControlsExperimental => {
|
|
3627
|
+
public enterStagingMode = (): StageControlsInternal => {
|
|
3543
3628
|
if (this.stageControls !== undefined) {
|
|
3544
3629
|
throw new UsageError("Already in staging mode");
|
|
3545
3630
|
}
|
|
@@ -3572,8 +3657,7 @@ export class ContainerRuntime
|
|
|
3572
3657
|
}
|
|
3573
3658
|
};
|
|
3574
3659
|
|
|
3575
|
-
|
|
3576
|
-
const stageControls: StageControlsExperimental = {
|
|
3660
|
+
const stageControls: StageControlsInternal = {
|
|
3577
3661
|
discardChanges: () =>
|
|
3578
3662
|
exitStagingMode(() => {
|
|
3579
3663
|
// Pop all staged batches from the PSM and roll them back in LIFO order
|
|
@@ -3680,6 +3764,14 @@ export class ContainerRuntime
|
|
|
3680
3764
|
return this._audience;
|
|
3681
3765
|
}
|
|
3682
3766
|
|
|
3767
|
+
/**
|
|
3768
|
+
* When defined, this {@link @fluidframework/container-definitions#IAudience}
|
|
3769
|
+
* maintains member list using signals only.
|
|
3770
|
+
* Thus "write" members may be known earlier than quorum and avoid noise from
|
|
3771
|
+
* un-summarized quorum history.
|
|
3772
|
+
*/
|
|
3773
|
+
private readonly signalAudience?: IAudience;
|
|
3774
|
+
|
|
3683
3775
|
/**
|
|
3684
3776
|
* Returns true of container is dirty, i.e. there are some pending local changes that
|
|
3685
3777
|
* either were not sent out to delta stream or were not yet acknowledged.
|
|
@@ -3715,7 +3807,9 @@ export class ContainerRuntime
|
|
|
3715
3807
|
*/
|
|
3716
3808
|
public submitSignal(type: string, content: unknown, targetClientId?: string): void {
|
|
3717
3809
|
this.verifyNotClosed();
|
|
3718
|
-
const envelope =
|
|
3810
|
+
const envelope = {
|
|
3811
|
+
contents: { type, content },
|
|
3812
|
+
} satisfies UnsequencedSignalEnvelope;
|
|
3719
3813
|
this.submitSignalFn(envelope, targetClientId);
|
|
3720
3814
|
}
|
|
3721
3815
|
|
|
@@ -4541,17 +4635,15 @@ export class ContainerRuntime
|
|
|
4541
4635
|
}
|
|
4542
4636
|
}
|
|
4543
4637
|
|
|
4638
|
+
// Keep in sync with IFluidRootParentContextPrivate.submitMessage.
|
|
4544
4639
|
public submitMessage(
|
|
4545
|
-
|
|
4546
|
-
|
|
|
4547
|
-
|
|
|
4548
|
-
|
|
|
4549
|
-
// TODO: better typing
|
|
4550
|
-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
|
4551
|
-
contents: any,
|
|
4640
|
+
containerRuntimeMessage:
|
|
4641
|
+
| ContainerRuntimeDataStoreOpMessage
|
|
4642
|
+
| OutboundContainerRuntimeAttachMessage
|
|
4643
|
+
| ContainerRuntimeAliasMessage,
|
|
4552
4644
|
localOpMetadata: unknown = undefined,
|
|
4553
4645
|
): void {
|
|
4554
|
-
this.submit(
|
|
4646
|
+
this.submit(containerRuntimeMessage, localOpMetadata);
|
|
4555
4647
|
}
|
|
4556
4648
|
|
|
4557
4649
|
public async uploadBlob(
|
|
@@ -4822,9 +4914,8 @@ export class ContainerRuntime
|
|
|
4822
4914
|
);
|
|
4823
4915
|
switch (message.type) {
|
|
4824
4916
|
case ContainerMessageType.FluidDataStoreOp: {
|
|
4825
|
-
this.channelCollection.
|
|
4826
|
-
message
|
|
4827
|
-
message.contents,
|
|
4917
|
+
this.channelCollection.reSubmitContainerMessage(
|
|
4918
|
+
message,
|
|
4828
4919
|
resubmitData.localOpMetadata,
|
|
4829
4920
|
/* squash: */ true,
|
|
4830
4921
|
);
|
|
@@ -4856,11 +4947,10 @@ export class ContainerRuntime
|
|
|
4856
4947
|
case ContainerMessageType.FluidDataStoreOp:
|
|
4857
4948
|
case ContainerMessageType.Attach:
|
|
4858
4949
|
case ContainerMessageType.Alias: {
|
|
4859
|
-
//
|
|
4950
|
+
// Call reSubmitContainerMessage which will find the right store
|
|
4860
4951
|
// and trigger resubmission on it.
|
|
4861
|
-
this.channelCollection.
|
|
4862
|
-
message
|
|
4863
|
-
message.contents,
|
|
4952
|
+
this.channelCollection.reSubmitContainerMessage(
|
|
4953
|
+
message,
|
|
4864
4954
|
localOpMetadata,
|
|
4865
4955
|
/* squash: */ false,
|
|
4866
4956
|
);
|
|
@@ -4915,7 +5005,7 @@ export class ContainerRuntime
|
|
|
4915
5005
|
case ContainerMessageType.FluidDataStoreOp: {
|
|
4916
5006
|
// For operations, call rollbackDataStoreOp which will find the right store
|
|
4917
5007
|
// and trigger rollback on it.
|
|
4918
|
-
this.channelCollection.
|
|
5008
|
+
this.channelCollection.rollbackDataStoreOp(contents, localOpMetadata);
|
|
4919
5009
|
break;
|
|
4920
5010
|
}
|
|
4921
5011
|
case ContainerMessageType.GC: {
|
|
@@ -5208,8 +5298,8 @@ export class ContainerRuntime
|
|
|
5208
5298
|
eventEmitter.emit("joined", { clientId, canWrite });
|
|
5209
5299
|
});
|
|
5210
5300
|
this.on("disconnectedFromService", () => eventEmitter.emit("disconnected"));
|
|
5211
|
-
this.on("
|
|
5212
|
-
eventEmitter.emit("
|
|
5301
|
+
this.on("operabilityChanged", (canWrite: boolean) =>
|
|
5302
|
+
eventEmitter.emit("operabilityChanged", canWrite),
|
|
5213
5303
|
);
|
|
5214
5304
|
} else {
|
|
5215
5305
|
this.on("connected", (clientId: string) => {
|
|
@@ -5224,7 +5314,11 @@ export class ContainerRuntime
|
|
|
5224
5314
|
const getConnectionState = this.getConnectionState;
|
|
5225
5315
|
if (getConnectionState) {
|
|
5226
5316
|
const connectionState = getConnectionState();
|
|
5227
|
-
if (
|
|
5317
|
+
if (
|
|
5318
|
+
connectionState === ConnectionState.Connected ||
|
|
5319
|
+
connectionState === ConnectionState.CatchingUp
|
|
5320
|
+
) {
|
|
5321
|
+
// Note: when CatchingUp, canSendOps will always be false.
|
|
5228
5322
|
return this.canSendOps ? "joinedForWriting" : "joinedForReading";
|
|
5229
5323
|
}
|
|
5230
5324
|
} else if (this.canSendOps) {
|
|
@@ -5248,11 +5342,71 @@ export class ContainerRuntime
|
|
|
5248
5342
|
factory: ContainerExtensionFactory<T, TRuntimeProperties, TUseContext>,
|
|
5249
5343
|
...useContext: TUseContext
|
|
5250
5344
|
): T {
|
|
5345
|
+
return this.acquireExtensionInternal(
|
|
5346
|
+
/* injectionPermitted */ true,
|
|
5347
|
+
id,
|
|
5348
|
+
factory,
|
|
5349
|
+
...useContext,
|
|
5350
|
+
);
|
|
5351
|
+
}
|
|
5352
|
+
|
|
5353
|
+
public getExtension<
|
|
5354
|
+
T,
|
|
5355
|
+
TRuntimeProperties extends ExtensionRuntimeProperties,
|
|
5356
|
+
TUseContext extends unknown[],
|
|
5357
|
+
>(
|
|
5358
|
+
id: ContainerExtensionId,
|
|
5359
|
+
requirements: ContainerExtensionExpectations,
|
|
5360
|
+
...useContext: TUseContext
|
|
5361
|
+
): T {
|
|
5362
|
+
// Temporarily allow injection for extensions.
|
|
5363
|
+
// `requirements` are expected to be a factory as well.
|
|
5364
|
+
return this.acquireExtensionInternal(
|
|
5365
|
+
/* injectionPermitted */ true,
|
|
5366
|
+
id,
|
|
5367
|
+
requirements as ContainerExtensionFactory<T, TRuntimeProperties, TUseContext>,
|
|
5368
|
+
...useContext,
|
|
5369
|
+
);
|
|
5370
|
+
}
|
|
5371
|
+
|
|
5372
|
+
private acquireExtensionInternal<
|
|
5373
|
+
T,
|
|
5374
|
+
TRuntimeProperties extends ExtensionRuntimeProperties,
|
|
5375
|
+
TUseContext extends unknown[],
|
|
5376
|
+
>(
|
|
5377
|
+
injectionPermitted: boolean,
|
|
5378
|
+
id: ContainerExtensionId,
|
|
5379
|
+
factory: ContainerExtensionFactory<T, TRuntimeProperties, TUseContext>,
|
|
5380
|
+
...useContext: TUseContext
|
|
5381
|
+
): T {
|
|
5382
|
+
const compatCheckResult = checkLayerCompatibility(
|
|
5383
|
+
factory.hostRequirements,
|
|
5384
|
+
containerRuntimeCompatDetailsForContainerExtensions,
|
|
5385
|
+
);
|
|
5386
|
+
if (!compatCheckResult.isCompatible) {
|
|
5387
|
+
throw new UsageError("Extension is not compatible with ContainerRuntime", {
|
|
5388
|
+
errorDetails: JSON.stringify({
|
|
5389
|
+
containerRuntimeVersion:
|
|
5390
|
+
containerRuntimeCompatDetailsForContainerExtensions.pkgVersion,
|
|
5391
|
+
containerRuntimeGeneration:
|
|
5392
|
+
containerRuntimeCompatDetailsForContainerExtensions.generation,
|
|
5393
|
+
minSupportedGeneration: factory.hostRequirements.minSupportedGeneration,
|
|
5394
|
+
isGenerationCompatible: compatCheckResult.isGenerationCompatible,
|
|
5395
|
+
unsupportedFeatures: compatCheckResult.unsupportedFeatures,
|
|
5396
|
+
}),
|
|
5397
|
+
});
|
|
5398
|
+
}
|
|
5399
|
+
|
|
5251
5400
|
let entry = this.extensions.get(id);
|
|
5252
5401
|
if (entry === undefined) {
|
|
5402
|
+
if (!injectionPermitted) {
|
|
5403
|
+
throw new Error(`Extension ${id} not found`);
|
|
5404
|
+
}
|
|
5405
|
+
|
|
5406
|
+
const audience = this.signalAudience;
|
|
5253
5407
|
const runtime = {
|
|
5254
5408
|
getJoinedStatus: this.getJoinedStatus.bind(this),
|
|
5255
|
-
getClientId: () => this.clientId,
|
|
5409
|
+
getClientId: audience ? () => audience.getSelf()?.clientId : () => this.clientId,
|
|
5256
5410
|
events: this.lazyEventsForExtensions.value,
|
|
5257
5411
|
logger: this.baseLogger,
|
|
5258
5412
|
submitAddressedSignal: (
|
|
@@ -5262,16 +5416,45 @@ export class ContainerRuntime
|
|
|
5262
5416
|
this.submitExtensionSignal(id, addressChain, message);
|
|
5263
5417
|
},
|
|
5264
5418
|
getQuorum: this.getQuorum.bind(this),
|
|
5265
|
-
getAudience: this.getAudience.bind(this),
|
|
5419
|
+
getAudience: audience ? () => audience : this.getAudience.bind(this),
|
|
5266
5420
|
supportedFeatures: this.ILayerCompatDetails.supportedFeatures,
|
|
5267
5421
|
} satisfies ExtensionHost<TRuntimeProperties>;
|
|
5268
|
-
entry =
|
|
5422
|
+
entry = factory.instantiateExtension(runtime, ...useContext);
|
|
5269
5423
|
this.extensions.set(id, entry);
|
|
5270
5424
|
} else {
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5425
|
+
const { extension, compatibility } = entry;
|
|
5426
|
+
if (
|
|
5427
|
+
// Check short-circuit (re-use) for same instance which must be
|
|
5428
|
+
// same version and capabilities.
|
|
5429
|
+
!(entry instanceof factory) &&
|
|
5430
|
+
// Check version and capabilities if different instance. If
|
|
5431
|
+
// version matches and existing has all capabilities of
|
|
5432
|
+
// requested, then allow direct reuse.
|
|
5433
|
+
(compatibility.version !== factory.instanceExpectations.version ||
|
|
5434
|
+
[...factory.instanceExpectations.capabilities].some(
|
|
5435
|
+
(cap) => !compatibility.capabilities.has(cap),
|
|
5436
|
+
))
|
|
5437
|
+
) {
|
|
5438
|
+
// eslint-disable-next-line unicorn/prefer-ternary -- operations are significant and deserve own blocks
|
|
5439
|
+
if (
|
|
5440
|
+
!injectionPermitted ||
|
|
5441
|
+
gt(compatibility.version, factory.instanceExpectations.version)
|
|
5442
|
+
) {
|
|
5443
|
+
// This is an attempt to acquire an older version of an
|
|
5444
|
+
// extension that is already acquired OR updating (form of
|
|
5445
|
+
// injection) is not permitted.
|
|
5446
|
+
entry = extension.handleVersionOrCapabilitiesMismatch(
|
|
5447
|
+
entry,
|
|
5448
|
+
factory.instanceExpectations,
|
|
5449
|
+
);
|
|
5450
|
+
} else {
|
|
5451
|
+
// This is an attempt to acquire a newer or more capable
|
|
5452
|
+
// version of an extension that is already acquired. Replace
|
|
5453
|
+
// existing with new.
|
|
5454
|
+
entry = factory.resolvePriorInstantiation(entry);
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
// eslint-disable-next-line unicorn/consistent-destructuring -- 'entry' may have been update and thus use of 'extension' would be incorrect
|
|
5275
5458
|
entry.extension.onNewUse(...useContext);
|
|
5276
5459
|
}
|
|
5277
5460
|
return entry.interface as T;
|
|
@@ -5282,19 +5465,6 @@ export class ContainerRuntime
|
|
|
5282
5465
|
}
|
|
5283
5466
|
}
|
|
5284
5467
|
|
|
5285
|
-
export function createNewSignalEnvelope(
|
|
5286
|
-
address: string | undefined,
|
|
5287
|
-
type: string,
|
|
5288
|
-
content: unknown,
|
|
5289
|
-
): UnsequencedSignalEnvelope {
|
|
5290
|
-
const newEnvelope: UnsequencedSignalEnvelope = {
|
|
5291
|
-
address,
|
|
5292
|
-
contents: { type, content },
|
|
5293
|
-
};
|
|
5294
|
-
|
|
5295
|
-
return newEnvelope;
|
|
5296
|
-
}
|
|
5297
|
-
|
|
5298
5468
|
export function isContainerMessageDirtyable({
|
|
5299
5469
|
type,
|
|
5300
5470
|
contents,
|
package/src/dataStore.ts
CHANGED
|
@@ -7,12 +7,11 @@ import { AttachState } from "@fluidframework/container-definitions";
|
|
|
7
7
|
import type { FluidObject } from "@fluidframework/core-interfaces";
|
|
8
8
|
import type { IFluidHandleInternal } from "@fluidframework/core-interfaces/internal";
|
|
9
9
|
import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
|
|
10
|
-
import
|
|
11
|
-
AliasResult,
|
|
12
|
-
IDataStore,
|
|
13
|
-
IFluidDataStoreChannel,
|
|
14
|
-
|
|
15
|
-
IContainerRuntimeBaseExperimental,
|
|
10
|
+
import {
|
|
11
|
+
type AliasResult,
|
|
12
|
+
type IDataStore,
|
|
13
|
+
type IFluidDataStoreChannel,
|
|
14
|
+
asLegacyAlpha,
|
|
16
15
|
} from "@fluidframework/runtime-definitions/internal";
|
|
17
16
|
import {
|
|
18
17
|
type ITelemetryLoggerExt,
|
|
@@ -26,6 +25,8 @@ import { ContainerMessageType } from "./messageTypes.js";
|
|
|
26
25
|
/**
|
|
27
26
|
* Interface for an op to be used for assigning an
|
|
28
27
|
* alias to a datastore
|
|
28
|
+
* @internal
|
|
29
|
+
* @privateRemarks exported per ContainerRuntime export for testing purposes
|
|
29
30
|
*/
|
|
30
31
|
export interface IDataStoreAliasMessage {
|
|
31
32
|
/**
|
|
@@ -80,9 +81,7 @@ class DataStore implements IDataStore {
|
|
|
80
81
|
if (alias.includes("/")) {
|
|
81
82
|
throw new UsageError(`The alias cannot contain slashes: '${alias}'`);
|
|
82
83
|
}
|
|
83
|
-
|
|
84
|
-
const runtime = this.parentContext.containerRuntime as IContainerRuntimeBaseExperimental;
|
|
85
|
-
if (runtime.inStagingMode === true) {
|
|
84
|
+
if (asLegacyAlpha(this.parentContext.containerRuntime).inStagingMode === true) {
|
|
86
85
|
throw new UsageError("Cannot set aliases while in staging mode");
|
|
87
86
|
}
|
|
88
87
|
|
|
@@ -147,7 +146,10 @@ class DataStore implements IDataStore {
|
|
|
147
146
|
}
|
|
148
147
|
|
|
149
148
|
const aliased = await this.ackBasedPromise<boolean>((resolve) => {
|
|
150
|
-
this.parentContext.submitMessage(
|
|
149
|
+
this.parentContext.submitMessage(
|
|
150
|
+
{ type: ContainerMessageType.Alias, contents: message },
|
|
151
|
+
resolve,
|
|
152
|
+
);
|
|
151
153
|
})
|
|
152
154
|
.catch((error) => {
|
|
153
155
|
this.logger.sendErrorEvent(
|