forked from microsoft/FluidFramework
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontainerRuntime.ts
More file actions
5549 lines (5042 loc) · 198 KB
/
containerRuntime.ts
File metadata and controls
5549 lines (5042 loc) · 198 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/
import type {
ILayerCompatDetails,
IProvideLayerCompatDetails,
} from "@fluid-internal/client-utils";
import {
checkLayerCompatibility,
createEmitter,
Trace,
TypedEventEmitter,
} from "@fluid-internal/client-utils";
import type {
IAudience,
ISelf,
ICriticalContainerError,
IAudienceEvents,
} from "@fluidframework/container-definitions";
import { AttachState } from "@fluidframework/container-definitions";
import type {
IContainerContext,
IGetPendingLocalStateProps,
IRuntime,
IDeltaManager,
IDeltaManagerFull,
ILoader,
IContainerStorageService,
ConnectionStatus,
} from "@fluidframework/container-definitions/internal";
import {
ConnectionState,
isIDeltaManagerFull,
} from "@fluidframework/container-definitions/internal";
import type {
ContainerExtensionFactory,
ContainerExtensionId,
ExtensionHost,
ExtensionHostEvents,
ExtensionInstantiationResult,
ExtensionRuntimeProperties,
IContainerRuntime,
IContainerRuntimeEvents,
IContainerRuntimeInternal,
// eslint-disable-next-line import-x/no-deprecated
IContainerRuntimeWithResolveHandle_Deprecated,
JoinedStatus,
OutboundExtensionMessage,
} from "@fluidframework/container-runtime-definitions/internal";
import type {
FluidObject,
IFluidHandle,
IRequest,
IResponse,
ITelemetryBaseLogger,
Listenable,
} from "@fluidframework/core-interfaces";
import type {
IFluidHandleContext,
IFluidHandleInternal,
IProvideFluidHandleContext,
ISignalEnvelope,
OpaqueJsonDeserialized,
TypedMessage,
} from "@fluidframework/core-interfaces/internal";
import {
assert,
Deferred,
Lazy,
LazyPromise,
PromiseCache,
delay,
fail,
unreachableCase,
} from "@fluidframework/core-utils/internal";
import type {
IClientDetails,
IQuorumClients,
ISummaryTree,
} from "@fluidframework/driver-definitions";
import { SummaryType } from "@fluidframework/driver-definitions";
import type {
IDocumentMessage,
ISequencedDocumentMessage,
ISignalMessage,
ISnapshot,
ISnapshotTree,
ISummaryContent,
ISummaryContext,
SummaryObject,
} from "@fluidframework/driver-definitions/internal";
import { FetchSource, MessageType } from "@fluidframework/driver-definitions/internal";
import { readAndParse } from "@fluidframework/driver-utils/internal";
import type { IIdCompressor } from "@fluidframework/id-compressor";
import type {
IIdCompressorCore,
IdCreationRange,
SerializedIdCompressorWithNoSession,
SerializedIdCompressorWithOngoingSession,
} from "@fluidframework/id-compressor/internal";
import {
createIdCompressor,
createSessionId,
deserializeIdCompressor,
} from "@fluidframework/id-compressor/internal";
import {
FlushMode,
FlushModeExperimental,
channelsTreeName,
gcTreeKey,
} from "@fluidframework/runtime-definitions/internal";
import type {
ISummaryTreeWithStats,
ITelemetryContext,
IGarbageCollectionData,
CreateChildSummarizerNodeParam,
IDataStore,
IFluidDataStoreContextDetached,
IFluidDataStoreRegistry,
IFluidParentContext,
ISummarizeInternalResult,
InboundAttachMessage,
NamedFluidDataStoreRegistryEntries,
SummarizeInternalFn,
IInboundSignalMessage,
IRuntimeMessagesContent,
ISummarizerNodeWithGC,
StageControlsInternal,
IContainerRuntimeBaseInternal,
MinimumVersionForCollab,
ContainerExtensionExpectations,
} from "@fluidframework/runtime-definitions/internal";
import {
addBlobToSummary,
addSummarizeResultToSummary,
calculateStats,
create404Response,
defaultMinVersionForCollab,
exceptionToResponse,
GCDataBuilder,
isValidMinVersionForCollab,
RequestParser,
RuntimeHeaders,
validateMinimumVersionForCollab,
seqFromTree,
TelemetryContext,
} from "@fluidframework/runtime-utils/internal";
import type {
IEventSampler,
IFluidErrorBase,
ITelemetryGenericEventExt,
ITelemetryLoggerExt,
MonitoringContext,
} from "@fluidframework/telemetry-utils/internal";
import {
DataCorruptionError,
DataProcessingError,
extractSafePropertiesFromMessage,
GenericError,
LoggingError,
PerformanceEvent,
// eslint-disable-next-line import-x/no-deprecated
TaggedLoggerAdapter,
UsageError,
createChildLogger,
createChildMonitoringContext,
createSampledLogger,
loggerToMonitoringContext,
raiseConnectedEvent,
wrapError,
tagCodeArtifacts,
normalizeError,
} from "@fluidframework/telemetry-utils/internal";
import { gt } from "semver-ts";
import { v4 as uuid } from "uuid";
import { BindBatchTracker } from "./batchTracker.js";
import {
BlobManager,
type IPendingBlobs,
blobManagerBasePath,
blobsTreeName,
isBlobPath,
loadBlobManagerLoadInfo,
type IBlobManagerLoadInfo,
} from "./blobManager/index.js";
import type {
AddressedUnsequencedSignalEnvelope,
IFluidRootParentContextPrivate,
} from "./channelCollection.js";
import {
ChannelCollection,
formParentContext,
getSummaryForDatastores,
} from "./channelCollection.js";
import type { ICompressionRuntimeOptions } from "./compressionDefinitions.js";
import { CompressionAlgorithms, disabledCompressionConfig } from "./compressionDefinitions.js";
import { ReportOpPerfTelemetry } from "./connectionTelemetry.js";
import {
getMinVersionForCollabDefaults,
type RuntimeOptionsAffectingDocSchema,
validateRuntimeOptions,
runtimeOptionKeysThatRequireExplicitSchemaControl,
type RuntimeOptionKeysThatRequireExplicitSchemaControl,
} from "./containerCompatibility.js";
import { ContainerFluidHandleContext } from "./containerHandleContext.js";
import { channelToDataStore } from "./dataStore.js";
import { FluidDataStoreRegistry } from "./dataStoreRegistry.js";
import {
BaseDeltaManagerProxy,
DeltaManagerPendingOpsProxy,
DeltaManagerSummarizerProxy,
} from "./deltaManagerProxies.js";
import { DeltaScheduler } from "./deltaScheduler.js";
import {
GCNodeType,
GarbageCollector,
type IGCRuntimeOptions,
type IGCStats,
type IGarbageCollector,
gcGenerationOptionName,
type GarbageCollectionMessage,
type IGarbageCollectionRuntime,
} from "./gc/index.js";
import { InboundBatchAggregator } from "./inboundBatchAggregator.js";
import {
ContainerMessageType,
type ContainerRuntimeAliasMessage,
type ContainerRuntimeDataStoreOpMessage,
type OutboundContainerRuntimeDocumentSchemaMessage,
type ContainerRuntimeGCMessage,
type ContainerRuntimeIdAllocationMessage,
type InboundSequencedContainerRuntimeMessage,
type LocalContainerRuntimeMessage,
type OutboundContainerRuntimeAttachMessage,
type UnknownContainerRuntimeMessage,
} from "./messageTypes.js";
import type { ISavedOpMetadata } from "./metadata.js";
import {
type LocalBatchMessage,
type BatchStartInfo,
DuplicateBatchDetector,
ensureContentsDeserialized,
type IBatchCheckpoint,
OpCompressor,
OpDecompressor,
OpGroupingManager,
OpSplitter,
Outbox,
RemoteMessageProcessor,
type OutboundBatch,
type BatchResubmitInfo,
} from "./opLifecycle/index.js";
import { pkgVersion } from "./packageVersion.js";
import {
type PendingMessageResubmitData,
type IPendingLocalState,
PendingStateManager,
type PendingBatchResubmitMetadata,
} from "./pendingStateManager.js";
import { BatchRunCounter, RunCounter } from "./runCounter.js";
import {
runtimeCompatDetailsForLoader,
runtimeCoreCompatDetails,
validateLoaderCompatibility,
} from "./runtimeLayerCompatState.js";
import { SignalTelemetryManager } from "./signalTelemetryProcessing.js";
// These types are imported as types here because they are present in summaryDelayLoadedModule, which is loaded dynamically when required.
import {
aliasBlobName,
chunksBlobName,
createRootSummarizerNodeWithGC,
DefaultSummaryConfiguration,
DocumentsSchemaController,
electedSummarizerBlobName,
type EnqueueSummarizeResult,
extractSummaryMetadataMessage,
formCreateSummarizerFn,
type IBaseSummarizeResult,
type IConnectableRuntime,
type IContainerRuntimeMetadata,
type ICreateContainerMetadata,
idCompressorBlobName,
type IdCompressorMode,
type IDocumentSchemaChangeMessageIncoming,
type IDocumentSchemaCurrent,
type IDocumentSchemaFeatures,
type IEnqueueSummarizeOptions,
type IGeneratedSummaryStats,
type IGenerateSummaryTreeResult,
type IOnDemandSummarizeOptions,
type IRefreshSummaryAckOptions,
type IRootSummarizerNodeWithGC,
type ISerializedElection,
isSummariesDisabled,
isSummaryOnRequest,
type ISubmitSummaryOptions,
type ISummarizeResults,
type ISummarizerInternalsProvider,
type ISummarizerRuntime,
type ISummaryConfiguration,
type ISummaryMetadataMessage,
metadataBlobName,
OrderedClientCollection,
OrderedClientElection,
recentBatchInfoBlobName,
RetriableSummaryError,
rootHasIsolatedChannels,
type SubmitSummaryResult,
type Summarizer,
SummarizerClientElection,
summarizerClientType,
summarizerRequestUrl,
SummaryCollection,
SummaryManager,
validateSummaryHeuristicConfiguration,
wrapSummaryInChannelsTree,
} from "./summary/index.js";
import { Throttler, formExponentialFn } from "./throttler.js";
/**
* A {@link ContainerExtension}'s factory function as stored in extension map.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- `any` required to allow typed factory to be assignable per ContainerExtension.processSignal
type ExtensionEntry = ExtensionInstantiationResult<unknown, any, unknown[]>;
/**
* ContainerRuntime's compatibility details that is exposed to Container Extensions.
*/
const containerRuntimeCompatDetailsForContainerExtensions = {
...runtimeCoreCompatDetails,
/**
* The features supported by the ContainerRuntime's ContainerExtensionStore
* implementation.
*/
supportedFeatures: new Set<string>(),
} as const satisfies ILayerCompatDetails;
/**
* Creates an error object to be thrown / passed to Container's close fn in case of an unknown message type.
* The parameters are typed to support compile-time enforcement of handling all known types/behaviors
*
* @param unknownContainerRuntimeMessageType - Typed as something unexpected, to ensure all known types have been
* handled before calling this function (e.g. in a switch statement).
*
* @param codePath - The code path where the unexpected message type was encountered.
*
* @param sequencedMessage - The sequenced message that contained the unexpected message type.
*
*/
function getUnknownMessageTypeError(
unknownContainerRuntimeMessageType: UnknownContainerRuntimeMessage["type"],
codePath: string,
sequencedMessage?: ISequencedDocumentMessage,
): IFluidErrorBase {
return DataProcessingError.create(
"Runtime message of unknown type",
codePath,
sequencedMessage,
{
messageDetails: {
type: unknownContainerRuntimeMessageType,
},
},
);
}
/**
* @legacy @beta
*/
export interface ISummaryRuntimeOptions {
/**
* Override summary configurations set by the server.
*/
summaryConfigOverrides?: ISummaryConfiguration;
/**
* Delay before first attempt to spawn summarizing container.
*
* @deprecated Use {@link ISummaryRuntimeOptions.summaryConfigOverrides}'s
* {@link ISummaryBaseConfiguration.initialSummarizerDelayMs} instead.
*/
initialSummarizerDelayMs?: number;
}
/**
* Full set of options for container runtime as "required".
*
* @remarks
* {@link IContainerRuntimeOptions} is expected to be used by consumers.
*
* @privateRemarks If any new properties are added to this interface (or
* {@link IContainerRuntimeOptionsInternal}), then we will also need to make
* changes in {@link file://./containerCompatibility.ts}.
* If the new property does not change the DocumentSchema, then it must be
* explicity omitted from {@link RuntimeOptionsAffectingDocSchema}.
* If it does change the DocumentSchema, then a corresponding entry must be
* added to `runtimeOptionsAffectingDocSchemaConfigMap` with the appropriate
* compat configuration info.
* If neither of the above is done, then the build will fail to compile.
*
* @legacy @beta
*/
export interface ContainerRuntimeOptions {
readonly summaryOptions: ISummaryRuntimeOptions;
readonly gcOptions: IGCRuntimeOptions;
/**
* Affects the behavior while loading the runtime when the data verification check which
* compares the DeltaManager sequence number (obtained from protocol in summary) to the
* runtime sequence number (obtained from runtime metadata in summary) finds a mismatch.
* 1. "close" (default) will close the container with an assertion.
* 2. "log" will log an error event to telemetry, but still continue to load.
* 3. "bypass" will skip the check entirely. This is not recommended.
*/
readonly loadSequenceNumberVerification: "close" | "log" | "bypass";
/**
* Enables the runtime to compress ops. See {@link ICompressionRuntimeOptions}.
*/
readonly compressionOptions: ICompressionRuntimeOptions;
/**
* If specified, when in FlushMode.TurnBased, if the size of the ops between JS turns exceeds this value,
* an error will be thrown and the container will close.
*
* If unspecified, the limit is 700Kb.
*
* 'Infinity' will disable any limit.
*
* @experimental This config should be driven by the connection with the service and will be moved in the future.
*/
readonly maxBatchSizeInBytes: number;
/**
* If the op payload needs to be chunked in order to work around the maximum size of the batch, this value represents
* how large the individual chunks will be. This is only supported when compression is enabled. If after compression, the
* batch content size exceeds this value, it will be chunked into smaller ops of this exact size.
*
* This value is a trade-off between having many small chunks vs fewer larger chunks and by default, the runtime is configured to use
* 200 * 1024 = 204800 bytes. This default value ensures that no compressed payload's content is able to exceed {@link ContainerRuntimeOptions.maxBatchSizeInBytes}
* regardless of the overhead of an individual op.
*
* Any value of `chunkSizeInBytes` exceeding {@link ContainerRuntimeOptions.maxBatchSizeInBytes} will disable this feature, therefore if a compressed batch's content
* size exceeds {@link ContainerRuntimeOptions.maxBatchSizeInBytes} after compression, the container will close with an instance of `DataProcessingError` with
* the `BatchTooLarge` message.
*/
readonly chunkSizeInBytes: number;
/**
* Enable the IdCompressor in the runtime.
* @experimental Not ready for use.
*/
readonly enableRuntimeIdCompressor: IdCompressorMode;
/**
* If enabled, the runtime will group messages within a batch into a single
* message to be sent to the service.
* The grouping and ungrouping of such messages is handled by the "OpGroupingManager".
*
* By default, the feature is enabled. This feature can only be disabled when compression is also disabled.
* @deprecated The ability to disable Grouped Batching is deprecated and will be removed in a future release. This feature is required for the proper functioning of the Fluid Framework.
*/
readonly enableGroupedBatching: boolean;
/**
* When this property is set to true, it requires runtime to control is document schema properly through ops
* The benefit of this mode is that clients who do not understand schema will fail in predictable way, with predictable message,
* and will not attempt to limp along, which could cause data corruptions and crashes in random places.
* When this property is not set (or set to false), runtime operates in legacy mode, where new features (modifying document schema)
* are engaged as they become available, without giving legacy clients any chance to fail predictably.
*/
readonly explicitSchemaControl: boolean;
/**
* Create blob handles with pending payloads when calling createBlob (default is `undefined` (disabled)).
* When enabled (`true`), createBlob will return a handle before the blob upload completes.
*/
readonly createBlobPayloadPending: true | undefined;
}
/**
* Options for container runtime.
*
* @legacy @beta
*/
export type IContainerRuntimeOptions = Partial<ContainerRuntimeOptions>;
/**
* Internal extension of {@link ContainerRuntimeOptions}
*
* @privateRemarks
* These options are not available to consumers when creating a new container runtime,
* but we do need to expose them for internal use, e.g. when configuring the container runtime
* to ensure compatibility with older versions.
*
* This is defined as a fully required set of options as this package does not yet
* use `exactOptionalPropertyTypes` and `Required<>` applied to optional type allowing
* `undefined` like {@link IdCompressorMode} will exclude `undefined`.
*
* @internal
*/
export interface ContainerRuntimeOptionsInternal extends ContainerRuntimeOptions {
/**
* Sets the flush mode for the runtime. In Immediate flush mode the runtime will immediately
* send all operations to the driver layer, while in TurnBased the operations will be buffered
* and then sent them as a single batch at the end of the turn.
* By default, flush mode is TurnBased.
*/
readonly flushMode: FlushMode;
/**
* Allows Grouped Batching to be disabled by setting to false (default is true).
* In that case, batched messages will be sent individually (but still all at the same time).
*/
readonly enableGroupedBatching: boolean;
/**
* Controls automatic batch flushing during staging mode.
* Normal turn-based/async flush scheduling is suppressed while in staging mode
* until the accumulated batch reaches this many ops, at which point the batch
* is flushed. Incoming ops always break the current batch regardless of this setting.
*
* Set to Infinity to only break batches on system events (incoming ops).
*
* @defaultValue 1000
*/
readonly stagingModeMaxBatchOps?: number;
}
/**
* Internal extension of {@link IContainerRuntimeOptions}
*
* @internal
*/
export type IContainerRuntimeOptionsInternal = Partial<ContainerRuntimeOptionsInternal>;
/**
* Error responses when requesting a deleted object will have this header set to true
* @internal
*/
export const DeletedResponseHeaderKey = "wasDeleted";
/**
* Tombstone error responses will have this header set to true
* @legacy @beta
*/
export const TombstoneResponseHeaderKey = "isTombstoned";
/**
* Inactive error responses will have this header set to true
* @legacy @beta
*
* @deprecated this header is deprecated and will be removed in the future. The functionality corresponding
* to this was experimental and is no longer supported.
*/
export const InactiveResponseHeaderKey = "isInactive";
/**
* The full set of parsed header data that may be found on Runtime requests
* @internal
*/
export interface RuntimeHeaderData {
wait?: boolean;
viaHandle?: boolean;
allowTombstone?: boolean;
}
/**
* Default values for Runtime Headers
*/
export const defaultRuntimeHeaderData: Required<RuntimeHeaderData> = {
wait: true,
viaHandle: false,
allowTombstone: false,
};
const defaultStagingCommitOptions = { squash: false };
/**
* @deprecated
* Untagged logger is unsupported going forward. There are old loaders with old ContainerContexts that only
* have the untagged logger, so to accommodate that scenario the below interface is used. It can be removed once
* its usage is removed from TaggedLoggerAdapter fallback.
*/
interface OldContainerContextWithLogger extends Omit<IContainerContext, "taggedLogger"> {
logger: ITelemetryBaseLogger;
taggedLogger: undefined;
}
/**
* State saved when the container closes, to be given back to a newly
* instantiated runtime in a new instance of the container, so it can load to the
* same state
*/
export interface IPendingRuntimeState {
/**
* Pending ops from PendingStateManager
*/
pending?: IPendingLocalState;
/**
* Pending blobs from BlobManager
*/
pendingAttachmentBlobs?: IPendingBlobs;
/**
* Pending idCompressor state
*/
pendingIdCompressorState?: SerializedIdCompressorWithOngoingSession;
/**
* Time at which session expiry timer started.
*/
sessionExpiryTimerStarted?: number | undefined;
}
const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
// The actual limit is 1Mb (socket.io and Kafka limits)
// We can't estimate it fully, as we
// - do not know what properties relay service will add
// - we do not stringify final op, thus we do not know how much escaping will be added.
const defaultMaxBatchSizeInBytes = 700 * 1024;
const defaultChunkSizeInBytes = 204800;
/**
* Default maximum ops per staging-mode batch before automatic flush scheduling resumes.
*
* Chosen based on production telemetry: copy-paste operations routinely produce batches
* of 1000+ ops (435K instances over 30 days), and receivers on modern Fluid versions
* handle them without issues. 1000 also matches the existing "large batch" telemetry
* threshold ({@link OpGroupingManager}).
*/
const defaultStagingModeMaxBatchOps = 1000;
/**
* The default time to wait for pending ops to be processed during summarization
*/
export const defaultPendingOpsWaitTimeoutMs = 1000;
/**
* The default time to delay a summarization retry attempt when there are pending ops
*/
export const defaultPendingOpsRetryDelayMs = 1000;
/**
* Instead of refreshing from latest because we do not have 100% confidence in the state
* of the current system, we should close the summarizer and let it recover.
* This delay's goal is to prevent tight restart loops
*/
const defaultCloseSummarizerDelayMs = 5000; // 5 seconds
/**
* Checks whether a message.type is one of the values in ContainerMessageType
*/
export function isUnpackedRuntimeMessage(message: ISequencedDocumentMessage): boolean {
return (Object.values(ContainerMessageType) as string[]).includes(message.type);
}
/**
* Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
* special-case for document dirty state. Ultimately we should have no special-cases from the
* ContainerRuntime's perspective.
* @internal
*/
export const agentSchedulerId = "_scheduler";
// safely check navigator and get the hardware spec value
export function getDeviceSpec(): {
deviceMemory?: number | undefined;
hardwareConcurrency?: number | undefined;
} {
try {
if (typeof navigator === "object" && navigator !== null) {
return {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
deviceMemory: (navigator as any).deviceMemory,
hardwareConcurrency: navigator.hardwareConcurrency,
};
}
} catch {
// Eat the error
}
return {};
}
/**
* Older loader doesn't have a submitBatchFn member, this is the older way of submitting a batch.
* Rather than exposing the submitFn (now deprecated) and IDeltaManager (dangerous to hand out) to the Outbox,
* we can provide a partially-applied function to keep those items private to the ContainerRuntime.
*/
export const makeLegacySendBatchFn =
(
submitFn: (
type: MessageType,
contents: unknown,
batch: boolean,
appData?: unknown,
) => number,
deltaManager: Pick<IDeltaManager<unknown, unknown>, "flush">,
) =>
(batch: OutboundBatch): number => {
// Default to negative one to match Container.submitBatch behavior
let clientSequenceNumber: number = -1;
for (const message of batch.messages) {
clientSequenceNumber = submitFn(
MessageType.Operation,
// For back-compat (submitFn only works on deserialized content)
message.contents === undefined ? undefined : JSON.parse(message.contents),
true, // batch
message.metadata,
);
}
deltaManager.flush();
return clientSequenceNumber;
};
/**
* Extract last message from the snapshot metadata.
* Uses legacy property if not using explicit schema control, otherwise uses the new property.
* This allows new runtime to make documents not openable for old runtimes, one explicit document schema control is enabled.
* Please see addMetadataToSummary() as well
*/
function lastMessageFromMetadata(
metadata: IContainerRuntimeMetadata | undefined,
): ISummaryMetadataMessage | undefined {
return metadata?.documentSchema?.runtime?.explicitSchemaControl === true
? metadata?.lastMessage
: metadata?.message;
}
/**
* There is some ancient back-compat code that we'd like to instrument
* to understand if/when it is hit.
* We only want to log this once, to avoid spamming telemetry if we are wrong and these cases are hit commonly.
*/
export let getSingleUseLegacyLogCallback = (logger: ITelemetryLoggerExt, type: string) => {
return (codePath: string): void => {
logger.sendTelemetryEvent({
eventName: "LegacyMessageFormat",
details: { codePath, type },
});
// Now that we've logged, prevent future logging (globally).
// eslint-disable-next-line unicorn/consistent-function-scoping
getSingleUseLegacyLogCallback = () => () => {};
};
};
/**
* A {@link TypedMessage} that has unknown content explicitly
* noted as deserialized JSON.
*/
export interface UnknownIncomingTypedMessage extends TypedMessage {
content: OpaqueJsonDeserialized<unknown>;
}
type UnsequencedSignalEnvelope = Omit<ISignalEnvelope, "clientBroadcastSignalSequenceNumber">;
/**
* This object holds the parameters necessary for the {@link loadContainerRuntime} function.
* @legacy @beta
*/
export interface LoadContainerRuntimeParams {
/**
* Context of the container.
*/
context: IContainerContext;
/**
* Mapping from data store types to their corresponding factories
*/
registryEntries: NamedFluidDataStoreRegistryEntries;
/**
* Pass 'true' if loading from an existing snapshot.
*/
existing: boolean;
/**
* Additional options to be passed to the runtime.
* @remarks
* Defaults to `{}`.
*/
runtimeOptions?: IContainerRuntimeOptions;
/**
* runtime services provided with context
*/
containerScope?: FluidObject;
/**
* Promise that resolves to an object which will act as entryPoint for the Container.
*/
provideEntryPoint: (containerRuntime: IContainerRuntime) => Promise<FluidObject>;
/**
* Request handler for the request() method of the container runtime.
* Only relevant for back-compat while we remove the request() method and move fully to entryPoint as the main pattern.
* @deprecated Will be removed once Loader LTS version is "2.0.0-internal.7.0.0". Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
* */
requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise<IResponse>;
/**
* Minimum version of the FF runtime that is required to collaborate on new documents.
* The input should be a string that represents the minimum version of the FF runtime that should be
* supported for collaboration. The format of the string must be in valid semver format.
*
* The inputted version will be used to determine the default configuration for
* {@link IContainerRuntimeOptionsInternal} to ensure compatibility with the specified version.
*
* @example
* minVersionForCollab: "2.0.0"
*
* @privateRemarks
* Used to determine the default configuration for {@link IContainerRuntimeOptionsInternal} that affect the document schema.
* For example, let's say that feature `foo` was added in 2.0 which introduces a new op type. Additionally, option `bar`
* was added to `IContainerRuntimeOptionsInternal` in 2.0 to enable/disable `foo` since clients prior to 2.0 would not
* understand the new op type. If a customer were to set minVersionForCollab to 2.0.0, then `bar` would be set to
* enable `foo` by default. If a customer were to set minVersionForCollab to 1.0.0, then `bar` would be set to
* disable `foo` by default.
*/
minVersionForCollab?: MinimumVersionForCollab;
}
/**
* This is meant to be used by a {@link @fluidframework/container-definitions#IRuntimeFactory} to instantiate a container runtime.
* @param params - An object which specifies all required and optional params necessary to instantiate a runtime.
* @returns A runtime which provides all the functionality necessary to bind with the loader layer via the {@link @fluidframework/container-definitions#IRuntime} interface and provide a runtime environment via the {@link @fluidframework/container-runtime-definitions#IContainerRuntime} interface.
* @legacy @beta
*/
export async function loadContainerRuntime(
params: LoadContainerRuntimeParams,
): Promise<IContainerRuntime & IRuntime> {
return ContainerRuntime.loadRuntime(params);
}
const defaultMaxConsecutiveReconnects = 7;
/**
* These are the ONLY message types that are allowed to be submitted while in staging mode
* (Does not apply to pre-StagingMode batches that are resubmitted, those are not considered to be staged)
*/
function canStageMessageOfType(
type: LocalContainerRuntimeMessage["type"],
): type is
| ContainerMessageType.FluidDataStoreOp
| ContainerMessageType.GC
| ContainerMessageType.DocumentSchemaChange {
return (
// These are user changes coming up from the runtime's DataStores
type === ContainerMessageType.FluidDataStoreOp ||
// GC ops are used to detect issues in the reference graph so all clients can repair their GC state.
// These can be submitted at any time, including while in Staging Mode.
type === ContainerMessageType.GC ||
// These are typically sent shortly after boot and will not be common in Staging Mode, but it's possible.
type === ContainerMessageType.DocumentSchemaChange
);
}
/**
* Represents the runtime of the container. Contains helper functions/state of the container.
* It will define the store level mappings.
*
* @internal
*/
export class ContainerRuntime
extends TypedEventEmitter<IContainerRuntimeEvents>
implements
IContainerRuntimeInternal,
IContainerRuntimeBaseInternal,
// eslint-disable-next-line import-x/no-deprecated
IContainerRuntimeWithResolveHandle_Deprecated,
IRuntime,
IGarbageCollectionRuntime,
ISummarizerRuntime,
ISummarizerInternalsProvider,
// If ContainerRuntime stops being exported from this package, this can
// be updated to implement IFluidRootParentContextPrivate and leave
// submitMessage included.
// IFluidParentContextPrivate is also better than IFluidParentContext
// and is also internal only; so, not usable here.
Omit<IFluidParentContext, "submitMessage" | "submitSignal">,
IProvideFluidHandleContext,
IProvideLayerCompatDetails
{
/**
* Load the stores from a snapshot and returns the runtime.
* @param params - An object housing the runtime properties.
* {@link LoadContainerRuntimeParams} except internal, while still having layer compat obligations.
* @privateRemarks
* Despite this being `@internal`, `@fluidframework/test-utils` uses it in `createTestContainerRuntimeFactory` and assumes multiple versions of the package expose the same API.
*
* Also note that `mixinAttributor` from `@fluid-experimental/attributor` overrides this function:
* that will have to be updated if changing the signature of this function as well.
*
* Assuming these usages are updated appropriately,
* `loadRuntime` could be removed (replaced by `loadRuntime2` which could be renamed back to `loadRuntime`).
*/
public static async loadRuntime(
params: LoadContainerRuntimeParams & {
/**
* Constructor to use to create the ContainerRuntime instance.
* @remarks
* Defaults to {@link ContainerRuntime}.
*/
containerRuntimeCtor?: typeof ContainerRuntime;
},
): Promise<ContainerRuntime> {
return ContainerRuntime.loadRuntime2({
...params,
registry: new FluidDataStoreRegistry(params.registryEntries),
});
}
/**
* Load the stores from a snapshot and returns the runtime.
* @remarks
* Same as {@link ContainerRuntime.loadRuntime},
* but with `registry` instead of `registryEntries` and more `runtimeOptions`.
*/
public static async loadRuntime2(
params: Omit<LoadContainerRuntimeParams, "registryEntries" | "runtimeOptions"> & {
/**
* Mapping from data store types to their corresponding factories.
*/
registry: IFluidDataStoreRegistry;
/**
* Constructor to use to create the ContainerRuntime instance.
* @remarks
* Defaults to {@link ContainerRuntime}.
*/
containerRuntimeCtor?: typeof ContainerRuntime;
/**
* {@link LoadContainerRuntimeParams.runtimeOptions}, except with additional internal only options.
*/
runtimeOptions?: IContainerRuntimeOptionsInternal;
},
): Promise<ContainerRuntime> {
const {
context,
registry,
existing,
requestHandler,
provideEntryPoint,
runtimeOptions = {} satisfies IContainerRuntimeOptionsInternal,
containerScope = {},
containerRuntimeCtor = ContainerRuntime,
minVersionForCollab = defaultMinVersionForCollab,
} = params;
// If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
// back-compat: Remove the TaggedLoggerAdapter fallback once all the host are using loader > 0.45
const backCompatContext: IContainerContext | OldContainerContextWithLogger = context;
const passLogger =
backCompatContext.taggedLogger ??
// eslint-disable-next-line import-x/no-deprecated
new TaggedLoggerAdapter((backCompatContext as OldContainerContextWithLogger).logger);
const logger = createChildLogger({
logger: passLogger,
properties: {
all: {
runtimeVersion: pkgVersion,
},
},
});
const mc = loggerToMonitoringContext(logger);
// Some options require a minimum version of the FF runtime to operate, so the default configs will be generated
// based on the minVersionForCollab.
// For example, if minVersionForCollab is set to "1.0.0", the default configs will ensure compatibility with FF runtime
// 1.0.0 or later. If the minVersionForCollab is set to "2.10.0", the default values will be generated to ensure compatibility
// with FF runtime 2.10.0 or later.
if (!isValidMinVersionForCollab(minVersionForCollab)) {
throw new UsageError(
`Invalid minVersionForCollab: ${minVersionForCollab}. It must be an existing FF version (i.e. 2.22.1).`,
);
}
// We also validate that there is not a mismatch between `minVersionForCollab` and runtime options that
// were manually set.
validateRuntimeOptions(minVersionForCollab, runtimeOptions);
const defaultsAffectingDocSchema = getMinVersionForCollabDefaults(minVersionForCollab);
// The following are the default values for the options that do not affect the DocumentSchema.
const defaultsNotAffectingDocSchema: Omit<
ContainerRuntimeOptionsInternal,
keyof RuntimeOptionsAffectingDocSchema
> = {
summaryOptions: {},
loadSequenceNumberVerification: "close",
maxBatchSizeInBytes: defaultMaxBatchSizeInBytes,
chunkSizeInBytes: defaultChunkSizeInBytes,
};
const defaultConfigs = {
...defaultsAffectingDocSchema,
...defaultsNotAffectingDocSchema,
};
// Here we set each option to its corresponding default config value if it's not provided in runtimeOptions.
// Note: We cannot do a simple object merge of defaultConfigs/runtimeOptions because in most cases we don't want
// a option that is undefined in runtimeOptions to override the default value (except for idCompressor, see below).
const {
summaryOptions = defaultConfigs.summaryOptions,
gcOptions = defaultConfigs.gcOptions,
loadSequenceNumberVerification = defaultConfigs.loadSequenceNumberVerification,