@fluidframework/container-runtime 2.10.0 → 2.12.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.
Files changed (125) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/api-report/container-runtime.legacy.alpha.api.md +17 -15
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/containerRuntime.d.ts +38 -5
  5. package/dist/containerRuntime.d.ts.map +1 -1
  6. package/dist/containerRuntime.js +40 -27
  7. package/dist/containerRuntime.js.map +1 -1
  8. package/dist/dataStoreContext.d.ts +15 -2
  9. package/dist/dataStoreContext.d.ts.map +1 -1
  10. package/dist/dataStoreContext.js +39 -1
  11. package/dist/dataStoreContext.js.map +1 -1
  12. package/dist/dataStoreRegistry.d.ts +1 -0
  13. package/dist/dataStoreRegistry.d.ts.map +1 -1
  14. package/dist/dataStoreRegistry.js +10 -2
  15. package/dist/dataStoreRegistry.js.map +1 -1
  16. package/dist/gc/garbageCollection.d.ts +2 -0
  17. package/dist/gc/garbageCollection.d.ts.map +1 -1
  18. package/dist/gc/garbageCollection.js +40 -11
  19. package/dist/gc/garbageCollection.js.map +1 -1
  20. package/dist/gc/gcSummaryStateTracker.d.ts +0 -7
  21. package/dist/gc/gcSummaryStateTracker.d.ts.map +1 -1
  22. package/dist/gc/gcSummaryStateTracker.js +0 -12
  23. package/dist/gc/gcSummaryStateTracker.js.map +1 -1
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/messageTypes.d.ts +3 -39
  28. package/dist/messageTypes.d.ts.map +1 -1
  29. package/dist/messageTypes.js.map +1 -1
  30. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  31. package/dist/opLifecycle/remoteMessageProcessor.js +1 -5
  32. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  33. package/dist/packageVersion.d.ts +1 -1
  34. package/dist/packageVersion.js +1 -1
  35. package/dist/packageVersion.js.map +1 -1
  36. package/dist/pendingStateManager.d.ts.map +1 -1
  37. package/dist/pendingStateManager.js +2 -3
  38. package/dist/pendingStateManager.js.map +1 -1
  39. package/dist/summary/runWhileConnectedCoordinator.d.ts +2 -1
  40. package/dist/summary/runWhileConnectedCoordinator.d.ts.map +1 -1
  41. package/dist/summary/runWhileConnectedCoordinator.js.map +1 -1
  42. package/dist/summary/runningSummarizer.d.ts +8 -1
  43. package/dist/summary/runningSummarizer.d.ts.map +1 -1
  44. package/dist/summary/runningSummarizer.js +70 -8
  45. package/dist/summary/runningSummarizer.js.map +1 -1
  46. package/dist/summary/summarizer.d.ts +5 -2
  47. package/dist/summary/summarizer.d.ts.map +1 -1
  48. package/dist/summary/summarizer.js +39 -6
  49. package/dist/summary/summarizer.js.map +1 -1
  50. package/dist/summary/summarizerTypes.d.ts +8 -4
  51. package/dist/summary/summarizerTypes.d.ts.map +1 -1
  52. package/dist/summary/summarizerTypes.js.map +1 -1
  53. package/dist/summary/summaryManager.d.ts +5 -2
  54. package/dist/summary/summaryManager.d.ts.map +1 -1
  55. package/dist/summary/summaryManager.js +23 -6
  56. package/dist/summary/summaryManager.js.map +1 -1
  57. package/lib/containerRuntime.d.ts +38 -5
  58. package/lib/containerRuntime.d.ts.map +1 -1
  59. package/lib/containerRuntime.js +40 -27
  60. package/lib/containerRuntime.js.map +1 -1
  61. package/lib/dataStoreContext.d.ts +15 -2
  62. package/lib/dataStoreContext.d.ts.map +1 -1
  63. package/lib/dataStoreContext.js +40 -2
  64. package/lib/dataStoreContext.js.map +1 -1
  65. package/lib/dataStoreRegistry.d.ts +1 -0
  66. package/lib/dataStoreRegistry.d.ts.map +1 -1
  67. package/lib/dataStoreRegistry.js +8 -0
  68. package/lib/dataStoreRegistry.js.map +1 -1
  69. package/lib/gc/garbageCollection.d.ts +2 -0
  70. package/lib/gc/garbageCollection.d.ts.map +1 -1
  71. package/lib/gc/garbageCollection.js +40 -11
  72. package/lib/gc/garbageCollection.js.map +1 -1
  73. package/lib/gc/gcSummaryStateTracker.d.ts +0 -7
  74. package/lib/gc/gcSummaryStateTracker.d.ts.map +1 -1
  75. package/lib/gc/gcSummaryStateTracker.js +0 -12
  76. package/lib/gc/gcSummaryStateTracker.js.map +1 -1
  77. package/lib/index.d.ts +2 -2
  78. package/lib/index.d.ts.map +1 -1
  79. package/lib/index.js.map +1 -1
  80. package/lib/messageTypes.d.ts +3 -39
  81. package/lib/messageTypes.d.ts.map +1 -1
  82. package/lib/messageTypes.js.map +1 -1
  83. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  84. package/lib/opLifecycle/remoteMessageProcessor.js +1 -5
  85. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  86. package/lib/packageVersion.d.ts +1 -1
  87. package/lib/packageVersion.js +1 -1
  88. package/lib/packageVersion.js.map +1 -1
  89. package/lib/pendingStateManager.d.ts.map +1 -1
  90. package/lib/pendingStateManager.js +2 -3
  91. package/lib/pendingStateManager.js.map +1 -1
  92. package/lib/summary/runWhileConnectedCoordinator.d.ts +2 -1
  93. package/lib/summary/runWhileConnectedCoordinator.d.ts.map +1 -1
  94. package/lib/summary/runWhileConnectedCoordinator.js.map +1 -1
  95. package/lib/summary/runningSummarizer.d.ts +8 -1
  96. package/lib/summary/runningSummarizer.d.ts.map +1 -1
  97. package/lib/summary/runningSummarizer.js +70 -8
  98. package/lib/summary/runningSummarizer.js.map +1 -1
  99. package/lib/summary/summarizer.d.ts +5 -2
  100. package/lib/summary/summarizer.d.ts.map +1 -1
  101. package/lib/summary/summarizer.js +39 -6
  102. package/lib/summary/summarizer.js.map +1 -1
  103. package/lib/summary/summarizerTypes.d.ts +8 -4
  104. package/lib/summary/summarizerTypes.d.ts.map +1 -1
  105. package/lib/summary/summarizerTypes.js.map +1 -1
  106. package/lib/summary/summaryManager.d.ts +5 -2
  107. package/lib/summary/summaryManager.d.ts.map +1 -1
  108. package/lib/summary/summaryManager.js +23 -6
  109. package/lib/summary/summaryManager.js.map +1 -1
  110. package/package.json +21 -73
  111. package/src/containerRuntime.ts +94 -44
  112. package/src/dataStoreContext.ts +57 -1
  113. package/src/dataStoreRegistry.ts +10 -0
  114. package/src/gc/garbageCollection.ts +41 -11
  115. package/src/gc/gcSummaryStateTracker.ts +0 -13
  116. package/src/index.ts +1 -3
  117. package/src/messageTypes.ts +3 -50
  118. package/src/opLifecycle/remoteMessageProcessor.ts +1 -6
  119. package/src/packageVersion.ts +1 -1
  120. package/src/pendingStateManager.ts +2 -3
  121. package/src/summary/runWhileConnectedCoordinator.ts +2 -5
  122. package/src/summary/runningSummarizer.ts +82 -11
  123. package/src/summary/summarizer.ts +49 -10
  124. package/src/summary/summarizerTypes.ts +11 -4
  125. package/src/summary/summaryManager.ts +30 -10
@@ -218,7 +218,6 @@ import {
218
218
  ISubmitSummaryOptions,
219
219
  ISummarizeResults,
220
220
  ISummarizer,
221
- ISummarizerEvents,
222
221
  ISummarizerInternalsProvider,
223
222
  ISummarizerRuntime,
224
223
  ISummaryMetadataMessage,
@@ -475,6 +474,8 @@ export interface IContainerRuntimeOptions {
475
474
  * send all operations to the driver layer, while in TurnBased the operations will be buffered
476
475
  * and then sent them as a single batch at the end of the turn.
477
476
  * By default, flush mode is TurnBased.
477
+ *
478
+ * @deprecated Only the default value TurnBased is supported. This option will be removed in the future.
478
479
  */
479
480
  readonly flushMode?: FlushMode;
480
481
  /**
@@ -516,9 +517,10 @@ export interface IContainerRuntimeOptions {
516
517
  /**
517
518
  * If enabled, the runtime will group messages within a batch into a single
518
519
  * message to be sent to the service.
519
- * The grouping an ungrouping of such messages is handled by the "OpGroupingManager".
520
+ * The grouping and ungrouping of such messages is handled by the "OpGroupingManager".
520
521
  *
521
522
  * By default, the feature is enabled.
523
+ * @deprecated The ability to disable Grouped Batching is deprecated and will be removed in v2.20.0. This feature is required for the proper functioning of the Fluid Framework.
522
524
  */
523
525
  readonly enableGroupedBatching?: boolean;
524
526
 
@@ -532,6 +534,31 @@ export interface IContainerRuntimeOptions {
532
534
  readonly explicitSchemaControl?: boolean;
533
535
  }
534
536
 
537
+ /**
538
+ * Internal extension of @see IContainerRuntimeOptions
539
+ *
540
+ * These options are not available to consumers when creating a new container runtime,
541
+ * but we do need to expose them for internal use, e.g. when configuring the container runtime
542
+ * to ensure compability with older versions.
543
+ *
544
+ * @internal
545
+ */
546
+ export interface IContainerRuntimeOptionsInternal extends IContainerRuntimeOptions {
547
+ /**
548
+ * Sets the flush mode for the runtime. In Immediate flush mode the runtime will immediately
549
+ * send all operations to the driver layer, while in TurnBased the operations will be buffered
550
+ * and then sent them as a single batch at the end of the turn.
551
+ * By default, flush mode is TurnBased.
552
+ */
553
+ readonly flushMode?: FlushMode;
554
+
555
+ /**
556
+ * Allows Grouped Batching to be disabled by setting to false (default is true).
557
+ * In that case, batched messages will be sent individually (but still all at the same time).
558
+ */
559
+ readonly enableGroupedBatching?: boolean;
560
+ }
561
+
535
562
  /**
536
563
  * Error responses when requesting a deleted object will have this header set to true
537
564
  * @legacy
@@ -836,11 +863,15 @@ export async function loadContainerRuntime(
836
863
  /**
837
864
  * Represents the runtime of the container. Contains helper functions/state of the container.
838
865
  * It will define the store level mappings.
866
+ *
867
+ * @deprecated To be removed from the Legacy-Alpha API in version 2.20.0.
868
+ * Use the loadContainerRuntime function and interfaces IContainerRuntime / IRuntime instead.
869
+ *
839
870
  * @legacy
840
871
  * @alpha
841
872
  */
842
873
  export class ContainerRuntime
843
- extends TypedEventEmitter<IContainerRuntimeEvents & ISummarizerEvents>
874
+ extends TypedEventEmitter<IContainerRuntimeEvents>
844
875
  implements
845
876
  IContainerRuntime,
846
877
  IRuntime,
@@ -867,7 +898,7 @@ export class ContainerRuntime
867
898
  context: IContainerContext;
868
899
  registryEntries: NamedFluidDataStoreRegistryEntries;
869
900
  existing: boolean;
870
- runtimeOptions?: IContainerRuntimeOptions;
901
+ runtimeOptions?: IContainerRuntimeOptions; // May also include options from IContainerRuntimeOptionsInternal
871
902
  containerScope?: FluidObject;
872
903
  containerRuntimeCtor?: typeof ContainerRuntime;
873
904
  /** @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 */
@@ -914,7 +945,7 @@ export class ContainerRuntime
914
945
  chunkSizeInBytes = defaultChunkSizeInBytes,
915
946
  enableGroupedBatching = true,
916
947
  explicitSchemaControl = false,
917
- } = runtimeOptions;
948
+ }: IContainerRuntimeOptionsInternal = runtimeOptions;
918
949
 
919
950
  const registry = new FluidDataStoreRegistry(registryEntries);
920
951
 
@@ -1093,6 +1124,21 @@ export class ContainerRuntime
1093
1124
 
1094
1125
  const featureGatesForTelemetry: Record<string, boolean | number | undefined> = {};
1095
1126
 
1127
+ // Make sure we've got all the options including internal ones
1128
+ const internalRuntimeOptions: Readonly<Required<IContainerRuntimeOptionsInternal>> = {
1129
+ summaryOptions,
1130
+ gcOptions,
1131
+ loadSequenceNumberVerification,
1132
+ flushMode,
1133
+ compressionOptions,
1134
+ maxBatchSizeInBytes,
1135
+ chunkSizeInBytes,
1136
+ // Requires<> drops undefined from IdCompressorType
1137
+ enableRuntimeIdCompressor: enableRuntimeIdCompressor as "on" | "delayed",
1138
+ enableGroupedBatching,
1139
+ explicitSchemaControl,
1140
+ };
1141
+
1096
1142
  const runtime = new containerRuntimeCtor(
1097
1143
  context,
1098
1144
  registry,
@@ -1100,19 +1146,7 @@ export class ContainerRuntime
1100
1146
  electedSummarizerData,
1101
1147
  chunks ?? [],
1102
1148
  aliases ?? [],
1103
- {
1104
- summaryOptions,
1105
- gcOptions,
1106
- loadSequenceNumberVerification,
1107
- flushMode,
1108
- compressionOptions,
1109
- maxBatchSizeInBytes,
1110
- chunkSizeInBytes,
1111
- // Requires<> drops undefined from IdCompressorType
1112
- enableRuntimeIdCompressor: enableRuntimeIdCompressor as "on" | "delayed",
1113
- enableGroupedBatching,
1114
- explicitSchemaControl,
1115
- },
1149
+ internalRuntimeOptions,
1116
1150
  containerScope,
1117
1151
  logger,
1118
1152
  existing,
@@ -1474,6 +1508,11 @@ export class ContainerRuntime
1474
1508
  expiry: { policy: "absolute", durationMs: 60000 },
1475
1509
  });
1476
1510
 
1511
+ /**
1512
+ * The options to apply to this ContainerRuntime instance (including internal options hidden from the public API)
1513
+ */
1514
+ private readonly runtimeOptions: Readonly<Required<IContainerRuntimeOptionsInternal>>;
1515
+
1477
1516
  /***/
1478
1517
  protected constructor(
1479
1518
  context: IContainerContext,
@@ -1482,7 +1521,10 @@ export class ContainerRuntime
1482
1521
  electedSummarizerData: ISerializedElection | undefined,
1483
1522
  chunks: [string, string[]][],
1484
1523
  dataStoreAliasMap: [string, string][],
1485
- private readonly runtimeOptions: Readonly<Required<IContainerRuntimeOptions>>,
1524
+ runtimeOptions: Readonly<
1525
+ Required<Omit<IContainerRuntimeOptions, "flushMode" | "enableGroupedBatching">> &
1526
+ IContainerRuntimeOptions // Let flushMode and enabledGroupedBatching be optional now since they're soon to be removed
1527
+ >,
1486
1528
  private readonly containerScope: FluidObject,
1487
1529
  // Create a custom ITelemetryBaseLogger to output telemetry events.
1488
1530
  public readonly baseLogger: ITelemetryBaseLogger,
@@ -1527,6 +1569,12 @@ export class ContainerRuntime
1527
1569
  snapshotWithContents,
1528
1570
  } = context;
1529
1571
 
1572
+ // Backfill in defaults for the internal runtimeOptions, since they may not be present on the provided runtimeOptions object
1573
+ this.runtimeOptions = {
1574
+ flushMode: defaultFlushMode,
1575
+ enableGroupedBatching: true,
1576
+ ...runtimeOptions,
1577
+ };
1530
1578
  this.logger = createChildLogger({ logger: this.baseLogger });
1531
1579
  this.mc = createChildMonitoringContext({
1532
1580
  logger: this.logger,
@@ -1697,14 +1745,15 @@ export class ContainerRuntime
1697
1745
  this.defaultMaxConsecutiveReconnects;
1698
1746
 
1699
1747
  if (
1700
- runtimeOptions.flushMode === (FlushModeExperimental.Async as unknown as FlushMode) &&
1748
+ this.runtimeOptions.flushMode ===
1749
+ (FlushModeExperimental.Async as unknown as FlushMode) &&
1701
1750
  supportedFeatures?.get("referenceSequenceNumbers") !== true
1702
1751
  ) {
1703
1752
  // The loader does not support reference sequence numbers, falling back on FlushMode.TurnBased
1704
1753
  this.mc.logger.sendErrorEvent({ eventName: "FlushModeFallback" });
1705
1754
  this._flushMode = FlushMode.TurnBased;
1706
1755
  } else {
1707
- this._flushMode = runtimeOptions.flushMode;
1756
+ this._flushMode = this.runtimeOptions.flushMode;
1708
1757
  }
1709
1758
  this.offlineEnabled =
1710
1759
  this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ?? false;
@@ -2015,9 +2064,19 @@ export class ContainerRuntime
2015
2064
  initialDelayMs: this.initialSummarizerDelayMs,
2016
2065
  },
2017
2066
  );
2018
- this.summaryManager.on("summarize", (eventProps) => {
2019
- this.emit("summarize", eventProps);
2067
+ // Forward events from SummaryManager
2068
+ [
2069
+ "summarize",
2070
+ "summarizeAllAttemptsFailed",
2071
+ "summarizerStop",
2072
+ "summarizerStart",
2073
+ "summarizerStartupFailed",
2074
+ ].forEach((eventName) => {
2075
+ this.summaryManager?.on(eventName, (...args: any[]) => {
2076
+ this.emit(eventName, ...args);
2077
+ });
2020
2078
  });
2079
+
2021
2080
  this.summaryManager.start();
2022
2081
  }
2023
2082
  }
@@ -2669,20 +2728,21 @@ export class ContainerRuntime
2669
2728
 
2670
2729
  this._connected = connected;
2671
2730
 
2672
- if (!connected) {
2673
- this._signalTracking.signalsLost = 0;
2674
- this._signalTracking.signalsOutOfOrder = 0;
2675
- this._signalTracking.signalTimestamp = 0;
2676
- this._signalTracking.signalsSentSinceLastLatencyMeasurement = 0;
2677
- this._signalTracking.totalSignalsSentInLatencyWindow = 0;
2678
- this._signalTracking.roundTripSignalSequenceNumber = undefined;
2679
- this._signalTracking.trackingSignalSequenceNumber = undefined;
2680
- this._signalTracking.minimumTrackingSignalSequenceNumber = undefined;
2681
- } else {
2731
+ if (connected) {
2682
2732
  assert(
2683
2733
  this.attachState === AttachState.Attached,
2684
2734
  0x3cd /* Connection is possible only if container exists in storage */,
2685
2735
  );
2736
+ if (changeOfState) {
2737
+ this._signalTracking.signalsLost = 0;
2738
+ this._signalTracking.signalsOutOfOrder = 0;
2739
+ this._signalTracking.signalTimestamp = 0;
2740
+ this._signalTracking.signalsSentSinceLastLatencyMeasurement = 0;
2741
+ this._signalTracking.totalSignalsSentInLatencyWindow = 0;
2742
+ this._signalTracking.roundTripSignalSequenceNumber = undefined;
2743
+ this._signalTracking.trackingSignalSequenceNumber = undefined;
2744
+ this._signalTracking.minimumTrackingSignalSequenceNumber = undefined;
2745
+ }
2686
2746
  }
2687
2747
 
2688
2748
  // Fail while disconnected
@@ -3227,16 +3287,7 @@ export class ContainerRuntime
3227
3287
  };
3228
3288
 
3229
3289
  // Only collect signal telemetry for broadcast messages sent by the current client.
3230
- if (
3231
- message.clientId === this.clientId &&
3232
- // jason-ha: This `connected` check seems incorrect. Signals that come through
3233
- // here must have been received while connected to service and there is no need
3234
- // to avoid processing when connection has dropped. Because container runtime's
3235
- // `connected` (and `_connected`) state also reflects some ops state, it may
3236
- // easily be false when newly connected and signal tracking may very well
3237
- // complain lost signals (that were simply skipped per this check).
3238
- this.connected
3239
- ) {
3290
+ if (message.clientId === this.clientId) {
3240
3291
  this.processSignalForTelemetry(envelope);
3241
3292
  }
3242
3293
 
@@ -4621,7 +4672,6 @@ export class ContainerRuntime
4621
4672
  this.channelCollection.rollback(type, contents, localOpMetadata);
4622
4673
  break;
4623
4674
  default:
4624
- // Don't check message.compatDetails because this is for rolling back a local op so the type will be known
4625
4675
  throw new Error(`Can't rollback ${type}`);
4626
4676
  }
4627
4677
  }
@@ -55,6 +55,7 @@ import {
55
55
  IInboundSignalMessage,
56
56
  type IPendingMessagesState,
57
57
  type IRuntimeMessageCollection,
58
+ type IFluidDataStoreFactory,
58
59
  } from "@fluidframework/runtime-definitions/internal";
59
60
  import {
60
61
  addBlobToSummary,
@@ -65,6 +66,7 @@ import {
65
66
  LoggingError,
66
67
  MonitoringContext,
67
68
  ThresholdCounter,
69
+ UsageError,
68
70
  createChildMonitoringContext,
69
71
  extractSafePropertiesFromMessage,
70
72
  generateStack,
@@ -496,7 +498,7 @@ export abstract class FluidDataStoreContext
496
498
  this.rejectDeferredRealize("No registry for package", lastPkg, packages);
497
499
  }
498
500
  lastPkg = pkg;
499
- entry = await registry.get(pkg);
501
+ entry = registry.getSync?.(pkg) ?? (await registry.get(pkg));
500
502
  if (!entry) {
501
503
  this.rejectDeferredRealize(
502
504
  "Registry does not contain entry for the package",
@@ -517,6 +519,42 @@ export abstract class FluidDataStoreContext
517
519
  return factory;
518
520
  }
519
521
 
522
+ createChildDataStore<T extends IFluidDataStoreFactory>(
523
+ childFactory: T,
524
+ ): ReturnType<Exclude<T["createDataStore"], undefined>> {
525
+ const maybe = this.registry?.getSync?.(childFactory.type);
526
+
527
+ const isUndefined = maybe === undefined;
528
+ const diffInstance = maybe?.IFluidDataStoreFactory !== childFactory;
529
+
530
+ if (isUndefined || diffInstance) {
531
+ throw new UsageError(
532
+ "The provided factory instance must be synchronously available as a child of this datastore",
533
+ { isUndefined, diffInstance },
534
+ );
535
+ }
536
+ if (childFactory?.createDataStore === undefined) {
537
+ throw new UsageError("createDataStore must exist on the provided factory", {
538
+ noCreateDataStore: true,
539
+ });
540
+ }
541
+
542
+ const context = this._containerRuntime.createDetachedDataStore([
543
+ ...this.packagePath,
544
+ childFactory.type,
545
+ ]);
546
+ assert(
547
+ context instanceof LocalDetachedFluidDataStoreContext,
548
+ 0xa89 /* must be a LocalDetachedFluidDataStoreContext */,
549
+ );
550
+
551
+ const created = childFactory.createDataStore(context) as ReturnType<
552
+ Exclude<T["createDataStore"], undefined>
553
+ >;
554
+ context.unsafe_AttachRuntimeSync(created.runtime);
555
+ return created;
556
+ }
557
+
520
558
  private async realizeCore(existing: boolean) {
521
559
  const details = await this.getInitialSnapshotDetails();
522
560
  // Base snapshot is the baseline where pending ops are applied to.
@@ -1428,6 +1466,24 @@ export class LocalDetachedFluidDataStoreContext
1428
1466
  return this.channelToDataStoreFn(await this.channelP);
1429
1467
  }
1430
1468
 
1469
+ /**
1470
+ * This method provides a synchronous path for binding a runtime to the context.
1471
+ *
1472
+ * Due to its synchronous nature, it is unable to validate that the runtime
1473
+ * represents a datastore which is instantiable by remote clients. This could
1474
+ * happen if the runtime's package path does not return a factory when looked up
1475
+ * in the container runtime's registry, or if the runtime's entrypoint is not
1476
+ * properly initialized. As both of these validation's are asynchronous to preform.
1477
+ *
1478
+ * If used incorrectly, this function can result in permanent data corruption.
1479
+ */
1480
+ public unsafe_AttachRuntimeSync(channel: IFluidDataStoreChannel) {
1481
+ this.channelP = Promise.resolve(channel);
1482
+ this.processPendingOps(channel);
1483
+ this.completeBindingRuntime(channel);
1484
+ return this.channelToDataStoreFn(channel);
1485
+ }
1486
+
1431
1487
  public async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
1432
1488
  if (this.detachedRuntimeCreation) {
1433
1489
  throw new Error(
@@ -3,6 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import { isPromiseLike } from "@fluidframework/core-utils/internal";
6
7
  import {
7
8
  FluidDataStoreRegistryEntry,
8
9
  IFluidDataStoreRegistry,
@@ -40,4 +41,13 @@ export class FluidDataStoreRegistry implements IFluidDataStoreRegistry {
40
41
 
41
42
  return undefined;
42
43
  }
44
+
45
+ public getSync(name: string): FluidDataStoreRegistryEntry | undefined {
46
+ const entry = this.map.get(name);
47
+ if (!isPromiseLike(entry)) {
48
+ return entry;
49
+ }
50
+
51
+ return undefined;
52
+ }
43
53
  }
@@ -338,6 +338,43 @@ export class GarbageCollector implements IGarbageCollector {
338
338
  });
339
339
  }
340
340
 
341
+ /** API for ensuring the correct auto-recovery mitigations */
342
+ private readonly autoRecovery = (() => {
343
+ // This uses a hidden state machine for forcing fullGC as part of autorecovery,
344
+ // to regenerate the GC data for each node.
345
+ //
346
+ // Once fullGC has been requested, we need to wait until GC has run and the summary has been acked before clearing the state.
347
+ //
348
+ // States:
349
+ // - undefined: No need to run fullGC now.
350
+ // - "requested": FullGC requested, but GC has not yet run. Keep using fullGC until back to undefined.
351
+ // - "ran": FullGC ran, but the following summary has not yet been acked. Keep using fullGC until back to undefined.
352
+ //
353
+ // Transitions:
354
+ // - autoRecovery.requestFullGCOnNextRun :: [anything] --> "requested"
355
+ // - autoRecovery.onCompletedGCRun :: "requested" --> "ran"
356
+ // - autoRecovery.onSummaryAck :: "ran" --> undefined
357
+ let state: "requested" | "ran" | undefined;
358
+ return {
359
+ requestFullGCOnNextRun: () => {
360
+ state = "requested";
361
+ },
362
+ onCompletedGCRun: () => {
363
+ if (state === "requested") {
364
+ state = "ran";
365
+ }
366
+ },
367
+ onSummaryAck: () => {
368
+ if (state === "ran") {
369
+ state = undefined;
370
+ }
371
+ },
372
+ useFullGC: () => {
373
+ return state !== undefined;
374
+ },
375
+ };
376
+ })();
377
+
341
378
  /**
342
379
  * Called during container initialization. Initializes the tombstone and deleted nodes state from the base snapshot.
343
380
  * Also, initializes the GC state including unreferenced nodes tracking if a current reference timestamp exists.
@@ -460,9 +497,7 @@ export class GarbageCollector implements IGarbageCollector {
460
497
  telemetryContext?: ITelemetryContext,
461
498
  ): Promise<IGCStats | undefined> {
462
499
  const fullGC =
463
- options.fullGC ??
464
- (this.configs.runFullGC === true ||
465
- this.summaryStateTracker.autoRecovery.fullGCRequested());
500
+ options.fullGC ?? (this.configs.runFullGC === true || this.autoRecovery.useFullGC());
466
501
 
467
502
  // Add the options that are used to run GC to the telemetry context.
468
503
  telemetryContext?.setMultiple("fluid_GC", "Options", {
@@ -521,6 +556,7 @@ export class GarbageCollector implements IGarbageCollector {
521
556
  await this.telemetryTracker.logPendingEvents(logger);
522
557
  // Update the state of summary state tracker from this run's stats.
523
558
  this.summaryStateTracker.updateStateFromGCRunStats(gcStats);
559
+ this.autoRecovery.onCompletedGCRun();
524
560
  this.newReferencesSinceLastRun.clear();
525
561
  this.completedRuns++;
526
562
 
@@ -709,13 +745,9 @@ export class GarbageCollector implements IGarbageCollector {
709
745
  deletedNodeIds: sweepReadyDSAndBlobs,
710
746
  };
711
747
 
712
- // Its fine for older clients to ignore this op because it doesn't have any functional impact. This op
713
- // is an optimization to ensure that all clients are in sync when it comes to deleted nodes to prevent their
714
- // accidental usage. The clients will sync without the delete op too but it may take longer.
715
748
  const containerGCMessage: ContainerRuntimeGCMessage = {
716
749
  type: ContainerMessageType.GC,
717
750
  contents,
718
- compatDetails: { behavior: "Ignore" }, // DEPRECATED: For temporary back compat only
719
751
  };
720
752
  this.submitMessage(containerGCMessage);
721
753
  return;
@@ -857,6 +889,7 @@ export class GarbageCollector implements IGarbageCollector {
857
889
  * Called to refresh the latest summary state. This happens when either a pending summary is acked.
858
890
  */
859
891
  public async refreshLatestSummary(result: IRefreshSummaryResult): Promise<void> {
892
+ this.autoRecovery.onSummaryAck();
860
893
  return this.summaryStateTracker.refreshLatestSummary(result);
861
894
  }
862
895
 
@@ -891,7 +924,7 @@ export class GarbageCollector implements IGarbageCollector {
891
924
 
892
925
  // In case the cause of the TombstoneLoaded event is incorrect GC Data (i.e. the object is actually reachable),
893
926
  // do fullGC on the next run to get a chance to repair (in the likely case the bug is not deterministic)
894
- this.summaryStateTracker.autoRecovery.requestFullGCOnNextRun();
927
+ this.autoRecovery.requestFullGCOnNextRun();
895
928
  break;
896
929
  }
897
930
  default:
@@ -1036,15 +1069,12 @@ export class GarbageCollector implements IGarbageCollector {
1036
1069
  return;
1037
1070
  }
1038
1071
 
1039
- // Use compat behavior "Ignore" since this is an optimization to opportunistically protect
1040
- // objects from deletion, so it's fine for older clients to ignore this op.
1041
1072
  const containerGCMessage: ContainerRuntimeGCMessage = {
1042
1073
  type: ContainerMessageType.GC,
1043
1074
  contents: {
1044
1075
  type: GarbageCollectionMessageType.TombstoneLoaded,
1045
1076
  nodePath,
1046
1077
  },
1047
- compatDetails: { behavior: "Ignore" }, // DEPRECATED: For temporary back compat only
1048
1078
  };
1049
1079
  this.submitMessage(containerGCMessage);
1050
1080
  }
@@ -50,18 +50,6 @@ export class GCSummaryStateTracker {
50
50
  // to unreferenced or vice-versa.
51
51
  public updatedDSCountSinceLastSummary: number = 0;
52
52
 
53
- /** API for ensuring the correct auto-recovery mitigations */
54
- public autoRecovery = {
55
- requestFullGCOnNextRun: () => {
56
- this.fullGCModeForAutoRecovery = true;
57
- },
58
- fullGCRequested: () => {
59
- return this.fullGCModeForAutoRecovery;
60
- },
61
- };
62
- /** If true, the next GC run will do fullGC mode to regenerate the GC data for each node */
63
- private fullGCModeForAutoRecovery: boolean = false;
64
-
65
53
  constructor(
66
54
  // Tells whether GC should run or not.
67
55
  private readonly configs: Pick<
@@ -235,7 +223,6 @@ export class GCSummaryStateTracker {
235
223
  this.latestSummaryData = this.pendingSummaryData;
236
224
  this.pendingSummaryData = undefined;
237
225
  this.updatedDSCountSinceLastSummary = 0;
238
- this.fullGCModeForAutoRecovery = false;
239
226
  }
240
227
 
241
228
  /**
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export {
10
10
  ISummaryConfigurationDisableSummarizer,
11
11
  ISummaryConfigurationDisableHeuristics,
12
12
  IContainerRuntimeOptions,
13
+ IContainerRuntimeOptionsInternal,
13
14
  loadContainerRuntime,
14
15
  LoadContainerRuntimeParams,
15
16
  agentSchedulerId,
@@ -26,9 +27,6 @@ export {
26
27
  } from "./containerRuntime.js";
27
28
  export {
28
29
  ContainerMessageType,
29
- IContainerRuntimeMessageCompatDetails,
30
- CompatModeBehavior,
31
- RecentlyAddedContainerRuntimeMessageDetails,
32
30
  UnknownContainerRuntimeMessage,
33
31
  } from "./messageTypes.js";
34
32
  export { IBlobManagerLoadInfo } from "./blobManager/index.js";
@@ -58,29 +58,6 @@ export enum ContainerMessageType {
58
58
  GC = "GC",
59
59
  }
60
60
 
61
- /**
62
- * How should an older client handle an unrecognized remote op type?
63
- *
64
- * @deprecated The utility of a mechanism to handle unknown messages is outweighed by the nuance required to get it right.
65
- * @internal
66
- */
67
- export type CompatModeBehavior =
68
- /** Ignore the op. It won't be persisted if this client summarizes */
69
- | "Ignore"
70
- /** Fail processing immediately. (The container will close) */
71
- | "FailToProcess";
72
-
73
- /**
74
- * All the info an older client would need to know how to handle an unrecognized remote op type
75
- *
76
- * @deprecated The utility of a mechanism to handle unknown messages is outweighed by the nuance required to get it right.
77
- * @internal
78
- */
79
- export interface IContainerRuntimeMessageCompatDetails {
80
- /** How should an older client handle an unrecognized remote op type? */
81
- behavior: CompatModeBehavior;
82
- }
83
-
84
61
  /**
85
62
  * The unpacked runtime message / details to be handled or dispatched by the ContainerRuntime.
86
63
  * Message type are differentiated via a `type` string and contain different contents depending on their type.
@@ -88,27 +65,11 @@ export interface IContainerRuntimeMessageCompatDetails {
88
65
  * IMPORTANT: when creating one to be serialized, set the properties in the order they appear here.
89
66
  * This way stringified values can be compared.
90
67
  */
91
- type TypedContainerRuntimeMessage<
92
- TType extends ContainerMessageType,
93
- TContents,
94
- TUSedCompatDetails extends boolean = false,
95
- > = {
68
+ interface TypedContainerRuntimeMessage<TType extends ContainerMessageType, TContents> {
96
69
  /** Type of the op, within the ContainerRuntime's domain */
97
70
  type: TType;
98
71
  /** Domain-specific contents, interpreted according to the type */
99
72
  contents: TContents;
100
- } & (TUSedCompatDetails extends true
101
- ? Partial<RecentlyAddedContainerRuntimeMessageDetails>
102
- : { compatDetails?: undefined });
103
-
104
- /**
105
- * Additional details expected for any recently added message.
106
- * @deprecated The utility of a mechanism to handle unknown messages is outweighed by the nuance required to get it right.
107
- * @internal
108
- */
109
- export interface RecentlyAddedContainerRuntimeMessageDetails {
110
- /** Info describing how to handle this op in case the type is unrecognized (default: fail to process) */
111
- compatDetails: IContainerRuntimeMessageCompatDetails;
112
73
  }
113
74
 
114
75
  export type ContainerRuntimeDataStoreOpMessage = TypedContainerRuntimeMessage<
@@ -145,8 +106,7 @@ export type ContainerRuntimeIdAllocationMessage = TypedContainerRuntimeMessage<
145
106
  >;
146
107
  export type ContainerRuntimeGCMessage = TypedContainerRuntimeMessage<
147
108
  ContainerMessageType.GC,
148
- GarbageCollectionMessage,
149
- true // TUsedCompatDetails
109
+ GarbageCollectionMessage
150
110
  >;
151
111
  export type ContainerRuntimeDocumentSchemaMessage = TypedContainerRuntimeMessage<
152
112
  ContainerMessageType.DocumentSchemaChange,
@@ -157,8 +117,7 @@ export type ContainerRuntimeDocumentSchemaMessage = TypedContainerRuntimeMessage
157
117
  * Represents an unrecognized TypedContainerRuntimeMessage, e.g. a message from a future version of the container runtime.
158
118
  * @internal
159
119
  */
160
- export interface UnknownContainerRuntimeMessage
161
- extends Partial<RecentlyAddedContainerRuntimeMessageDetails> {
120
+ export interface UnknownContainerRuntimeMessage {
162
121
  /** Invalid type of the op, within the ContainerRuntime's domain. This value should never exist at runtime.
163
122
  * This is useful for type narrowing but should never be used as an actual message type at runtime.
164
123
  * Actual value will not be "__unknown...", but the type `Exclude<string, ContainerMessageType>` is not supported.
@@ -222,9 +181,3 @@ export type InboundSequencedContainerRuntimeMessage = Omit<
222
181
  "type" | "contents"
223
182
  > &
224
183
  InboundContainerRuntimeMessage;
225
-
226
- /** A [loose] InboundSequencedContainerRuntimeMessage that is recent and may contain compat details.
227
- * It exists solely to to provide access to those details.
228
- */
229
- export type InboundSequencedRecentlyAddedContainerRuntimeMessage = ISequencedDocumentMessage &
230
- Partial<RecentlyAddedContainerRuntimeMessageDetails>;
@@ -13,7 +13,6 @@ import {
13
13
  ContainerMessageType,
14
14
  type InboundContainerRuntimeMessage,
15
15
  type InboundSequencedContainerRuntimeMessage,
16
- type InboundSequencedRecentlyAddedContainerRuntimeMessage,
17
16
  } from "../messageTypes.js";
18
17
  import { asBatchMetadata } from "../metadata.js";
19
18
 
@@ -259,7 +258,7 @@ export function ensureContentsDeserialized(mutableMessage: ISequencedDocumentMes
259
258
  *
260
259
  * The return type illustrates the assumption that the message param
261
260
  * becomes a InboundSequencedContainerRuntimeMessage by the time the function returns
262
- * (but there is no runtime validation of the 'type' or 'compatDetails' values).
261
+ * (but there is no runtime validation of the 'type').
263
262
  */
264
263
  function unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRuntimeMessage {
265
264
  // We assume the contents is an InboundContainerRuntimeMessage (the message is "packed")
@@ -270,10 +269,6 @@ function unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRu
270
269
 
271
270
  messageUnpacked.type = contents.type;
272
271
  messageUnpacked.contents = contents.contents;
273
- if ("compatDetails" in contents) {
274
- (messageUnpacked as InboundSequencedRecentlyAddedContainerRuntimeMessage).compatDetails =
275
- contents.compatDetails;
276
- }
277
272
  return messageUnpacked;
278
273
  }
279
274
 
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.10.0";
9
+ export const pkgVersion = "2.12.0";
@@ -105,9 +105,9 @@ function isEmptyBatchPendingMessage(message: IPendingMessageFromStash): boolean
105
105
  function buildPendingMessageContent(message: InboundSequencedContainerRuntimeMessage): string {
106
106
  // IMPORTANT: Order matters here, this must match the order of the properties used
107
107
  // when submitting the message.
108
- const { type, contents, compatDetails }: InboundContainerRuntimeMessage = message;
108
+ const { type, contents }: InboundContainerRuntimeMessage = message;
109
109
  // Any properties that are not defined, won't be emitted by stringify.
110
- return JSON.stringify({ type, contents, compatDetails });
110
+ return JSON.stringify({ type, contents });
111
111
  }
112
112
 
113
113
  function typesOfKeys<T extends object>(obj: T): Record<keyof T, string> {
@@ -126,7 +126,6 @@ function scrubAndStringify(
126
126
  // For these known/expected keys, we can either drill in (for contents)
127
127
  // or just use the value as-is (since it's not personal info)
128
128
  scrubbed.contents = message.contents && typesOfKeys(message.contents);
129
- scrubbed.compatDetails = message.compatDetails;
130
129
  scrubbed.type = message.type;
131
130
 
132
131
  return JSON.stringify(scrubbed);