@fluidframework/container-runtime 2.70.0-361248 → 2.70.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 (69) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/container-runtime.test-files.tar +0 -0
  3. package/dist/blobManager/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager/blobManager.js +0 -1
  5. package/dist/blobManager/blobManager.js.map +1 -1
  6. package/dist/containerRuntime.d.ts +25 -6
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +92 -22
  9. package/dist/containerRuntime.js.map +1 -1
  10. package/dist/dataStore.d.ts +1 -1
  11. package/dist/dataStore.d.ts.map +1 -1
  12. package/dist/dataStore.js +7 -8
  13. package/dist/dataStore.js.map +1 -1
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +1 -1
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  18. package/dist/opLifecycle/outbox.js +7 -0
  19. package/dist/opLifecycle/outbox.js.map +1 -1
  20. package/dist/packageVersion.d.ts +1 -1
  21. package/dist/packageVersion.d.ts.map +1 -1
  22. package/dist/packageVersion.js +1 -1
  23. package/dist/packageVersion.js.map +1 -1
  24. package/dist/runtimeLayerCompatState.d.ts +4 -3
  25. package/dist/runtimeLayerCompatState.d.ts.map +1 -1
  26. package/dist/runtimeLayerCompatState.js +4 -35
  27. package/dist/runtimeLayerCompatState.js.map +1 -1
  28. package/dist/storageServiceWithAttachBlobs.d.ts +0 -36
  29. package/dist/storageServiceWithAttachBlobs.d.ts.map +1 -1
  30. package/dist/storageServiceWithAttachBlobs.js +0 -55
  31. package/dist/storageServiceWithAttachBlobs.js.map +1 -1
  32. package/lib/blobManager/blobManager.d.ts.map +1 -1
  33. package/lib/blobManager/blobManager.js +0 -1
  34. package/lib/blobManager/blobManager.js.map +1 -1
  35. package/lib/containerRuntime.d.ts +25 -6
  36. package/lib/containerRuntime.d.ts.map +1 -1
  37. package/lib/containerRuntime.js +92 -22
  38. package/lib/containerRuntime.js.map +1 -1
  39. package/lib/dataStore.d.ts +1 -1
  40. package/lib/dataStore.d.ts.map +1 -1
  41. package/lib/dataStore.js +2 -3
  42. package/lib/dataStore.js.map +1 -1
  43. package/lib/dataStoreContext.d.ts.map +1 -1
  44. package/lib/dataStoreContext.js +1 -1
  45. package/lib/dataStoreContext.js.map +1 -1
  46. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  47. package/lib/opLifecycle/outbox.js +7 -0
  48. package/lib/opLifecycle/outbox.js.map +1 -1
  49. package/lib/packageVersion.d.ts +1 -1
  50. package/lib/packageVersion.d.ts.map +1 -1
  51. package/lib/packageVersion.js +1 -1
  52. package/lib/packageVersion.js.map +1 -1
  53. package/lib/runtimeLayerCompatState.d.ts +4 -3
  54. package/lib/runtimeLayerCompatState.d.ts.map +1 -1
  55. package/lib/runtimeLayerCompatState.js +5 -36
  56. package/lib/runtimeLayerCompatState.js.map +1 -1
  57. package/lib/storageServiceWithAttachBlobs.d.ts +0 -36
  58. package/lib/storageServiceWithAttachBlobs.d.ts.map +1 -1
  59. package/lib/storageServiceWithAttachBlobs.js +0 -55
  60. package/lib/storageServiceWithAttachBlobs.js.map +1 -1
  61. package/package.json +20 -17
  62. package/src/blobManager/blobManager.ts +0 -1
  63. package/src/containerRuntime.ts +144 -37
  64. package/src/dataStore.ts +6 -9
  65. package/src/dataStoreContext.ts +1 -0
  66. package/src/opLifecycle/outbox.ts +10 -0
  67. package/src/packageVersion.ts +1 -1
  68. package/src/runtimeLayerCompatState.ts +23 -39
  69. package/src/storageServiceWithAttachBlobs.ts +0 -92
@@ -454,6 +454,7 @@ export class ContainerRuntime extends TypedEventEmitter {
454
454
  };
455
455
  const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks ?? [], aliases ?? [], internalRuntimeOptions, containerScope, logger, existing, blobManagerLoadInfo, context.storage, createIdCompressorFn, documentSchemaController, featureGatesForTelemetry, provideEntryPoint, semanticVersionToMinimumVersionForCollab(updatedMinVersionForCollab), requestHandler, undefined, // summaryConfiguration
456
456
  recentBatchInfo);
457
+ runtime.sharePendingBlobs();
457
458
  // Initialize the base state of the runtime before it's returned.
458
459
  await runtime.initializeBaseState(context.loader);
459
460
  // Apply stashed ops with a reference sequence number equal to the sequence number of the snapshot,
@@ -621,9 +622,8 @@ export class ContainerRuntime extends TypedEventEmitter {
621
622
  * Enter Staging Mode, such that ops submitted to the ContainerRuntime will not be sent to the ordering service.
622
623
  * To exit Staging Mode, call either discardChanges or commitChanges on the Stage Controls returned from this method.
623
624
  *
624
- * @returns StageControlsExperimental - Controls for exiting Staging Mode.
625
+ * @returns Controls for exiting Staging Mode.
625
626
  */
626
- // eslint-disable-next-line import/no-deprecated
627
627
  this.enterStagingMode = () => {
628
628
  if (this.stageControls !== undefined) {
629
629
  throw new UsageError("Already in staging mode");
@@ -652,7 +652,6 @@ export class ContainerRuntime extends TypedEventEmitter {
652
652
  throw normalizedError;
653
653
  }
654
654
  };
655
- // eslint-disable-next-line import/no-deprecated
656
655
  const stageControls = {
657
656
  discardChanges: () => exitStagingMode(() => {
658
657
  // Pop all staged batches from the PSM and roll them back in LIFO order
@@ -678,6 +677,39 @@ export class ContainerRuntime extends TypedEventEmitter {
678
677
  return this.stageControls;
679
678
  };
680
679
  this.readAndParseBlob = async (id) => readAndParse(this.storage, id);
680
+ /**
681
+ * ContainerRuntime knows about additional restrictions on when blob sharing can be resumed as compared
682
+ * to BlobManager. In particular, it wants to avoid sharing blobs while in readonly state, and it also
683
+ * wants to avoid sharing blobs before connection completes (otherwise it may cause the sharing to happen
684
+ * before processing shared ops).
685
+ *
686
+ * This method can be called safely before those conditions are met. In the background, it will wait until
687
+ * it is safe before initiating sharing. It will close the container on any error.
688
+ */
689
+ this.sharePendingBlobs = () => {
690
+ new Promise((resolve) => {
691
+ // eslint-disable-next-line unicorn/consistent-function-scoping
692
+ const canStartSharing = () => this.connected && this.deltaManager.readOnlyInfo.readonly !== true;
693
+ if (canStartSharing()) {
694
+ resolve();
695
+ return;
696
+ }
697
+ const checkCanShare = (readonly) => {
698
+ if (canStartSharing()) {
699
+ this.deltaManager.off("readonly", checkCanShare);
700
+ this.off("connected", checkCanShare);
701
+ resolve();
702
+ }
703
+ };
704
+ this.deltaManager.on("readonly", checkCanShare);
705
+ this.on("connected", checkCanShare);
706
+ })
707
+ .then(this.blobManager.sharePendingBlobs)
708
+ // It may not be necessary to close the container on failures - this should just mean there's
709
+ // a handle in the container that is stuck pending, which is a scenario that customers need to
710
+ // handle anyway. Starting with this more aggressive/restrictive behavior to be cautious.
711
+ .catch(this.closeFn);
712
+ };
681
713
  // While internal, ContainerRuntime has not been converted to use the new events support.
682
714
  // Recreate the required events (new pattern) with injected, wrapper new emitter.
683
715
  // It is lazily create to avoid listeners (old events) that ultimately go nowhere.
@@ -688,7 +720,7 @@ export class ContainerRuntime extends TypedEventEmitter {
688
720
  eventEmitter.emit("joined", { clientId, canWrite });
689
721
  });
690
722
  this.on("disconnectedFromService", () => eventEmitter.emit("disconnected"));
691
- this.on("connectionTypeChanged", (canWrite) => eventEmitter.emit("connectionTypeChanged", canWrite));
723
+ this.on("operabilityChanged", (canWrite) => eventEmitter.emit("operabilityChanged", canWrite));
692
724
  }
693
725
  else {
694
726
  this.on("connected", (clientId) => {
@@ -698,14 +730,11 @@ export class ContainerRuntime extends TypedEventEmitter {
698
730
  }
699
731
  return eventEmitter;
700
732
  });
701
- const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, pendingLocalState, supportedFeatures, snapshotWithContents, getConnectionState, } = context;
733
+ const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, signalAudience, pendingLocalState, supportedFeatures, snapshotWithContents, getConnectionState, } = context;
702
734
  this.getConnectionState = getConnectionState;
703
735
  // In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
704
736
  this.disposeFn = disposeFn ?? closeFn;
705
737
  this.isSnapshotInstanceOfISnapshot = snapshotWithContents !== undefined;
706
- // Validate that the Loader is compatible with this Runtime.
707
- const maybeLoaderCompatDetailsForRuntime = context;
708
- validateLoaderCompatibility(maybeLoaderCompatDetailsForRuntime.ILayerCompatDetails, this.disposeFn);
709
738
  this.mc = createChildMonitoringContext({
710
739
  logger: this.baseLogger,
711
740
  namespace: "ContainerRuntime",
@@ -715,6 +744,9 @@ export class ContainerRuntime extends TypedEventEmitter {
715
744
  },
716
745
  },
717
746
  });
747
+ // Validate that the Loader is compatible with this Runtime.
748
+ const maybeLoaderCompatDetailsForRuntime = context;
749
+ validateLoaderCompatibility(maybeLoaderCompatDetailsForRuntime.ILayerCompatDetails, this.disposeFn, this.mc.logger);
718
750
  // If we support multiple algorithms in the future, then we would need to manage it here carefully.
719
751
  // We can use runtimeOptions.compressionOptions.compressionAlgorithm, but only if it's in the schema list!
720
752
  // If it's not in the list, then we will need to either use no compression, or fallback to some other (supported by format)
@@ -814,10 +846,11 @@ export class ContainerRuntime extends TypedEventEmitter {
814
846
  this.nextSummaryNumber = loadSummaryNumber + 1;
815
847
  this.messageAtLastSummary = lastMessageFromMetadata(metadata);
816
848
  // Note that we only need to pull the *initial* connected state from the context.
817
- // Later updates come through calls to setConnectionState.
849
+ // Later updates come through calls to setConnectionState/Status.
818
850
  this.canSendOps = connected;
819
851
  this.canSendSignals = this.getConnectionState
820
- ? this.getConnectionState() === ConnectionState.Connected
852
+ ? this.getConnectionState() === ConnectionState.Connected ||
853
+ this.getConnectionState() === ConnectionState.CatchingUp
821
854
  : undefined;
822
855
  this.mc.logger.sendTelemetryEvent({
823
856
  eventName: "GCFeatureMatrix",
@@ -1041,6 +1074,7 @@ export class ContainerRuntime extends TypedEventEmitter {
1041
1074
  oldClientId = clientId;
1042
1075
  });
1043
1076
  }
1077
+ this.signalAudience = signalAudience;
1044
1078
  const closeSummarizerDelayOverride = this.mc.config.getNumber("Fluid.ContainerRuntime.Test.CloseSummarizerDelayOverrideMs");
1045
1079
  this.closeSummarizerDelayMs =
1046
1080
  closeSummarizerDelayOverride ?? defaultCloseSummarizerDelayMs;
@@ -1622,6 +1656,38 @@ export class ContainerRuntime extends TypedEventEmitter {
1622
1656
  }
1623
1657
  }
1624
1658
  setConnectionState(canSendOps, clientId) {
1659
+ this.setConnectionStateToConnectedOrDisconnected(canSendOps, clientId);
1660
+ }
1661
+ setConnectionStatus(status) {
1662
+ switch (status.connectionState) {
1663
+ case ConnectionState.Connected: {
1664
+ this.setConnectionStateToConnectedOrDisconnected(status.canSendOps, status.clientConnectionId);
1665
+ break;
1666
+ }
1667
+ case ConnectionState.Disconnected: {
1668
+ this.setConnectionStateToConnectedOrDisconnected(status.canSendOps, status.priorConnectedClientConnectionId);
1669
+ break;
1670
+ }
1671
+ case ConnectionState.CatchingUp: {
1672
+ assert(this.getConnectionState !== undefined &&
1673
+ this.getConnectionState() === ConnectionState.CatchingUp, 0xc8d /* connection state mismatch between getConnectionState and setConnectionStatus notification */);
1674
+ // Note: Historically when only `setConnectionState` of `IRuntime`
1675
+ // was supported, it was possible to be in `CatchingUp` state and
1676
+ // call through to `setConnectionStateCore` when there is a readonly
1677
+ // change - see `Container`'s `"deltaManager.on("readonly"`. There
1678
+ // would not be a transition of `canSendOps` in that case, but
1679
+ // `channelCollection` and `garbageCollector` would receive early
1680
+ // `setConnectionState` call AND `this` would `emit` "disconnected"
1681
+ // event.
1682
+ this.emitServiceConnectionEvents(
1683
+ /* canSendOpsChanged */ this.canSendOps,
1684
+ /* canSendOps */ false, status.pendingClientConnectionId);
1685
+ break;
1686
+ }
1687
+ // No default
1688
+ }
1689
+ }
1690
+ setConnectionStateToConnectedOrDisconnected(canSendOps, clientId) {
1625
1691
  // Validate we have consistent state
1626
1692
  const currentClientId = this._audience.getSelf()?.clientId;
1627
1693
  assert(clientId === currentClientId, 0x977 /* input clientId does not match Audience */);
@@ -1680,30 +1746,33 @@ export class ContainerRuntime extends TypedEventEmitter {
1680
1746
  * Emits service connection events based on connection state changes.
1681
1747
  *
1682
1748
  * @remarks
1683
- * "connectedToService" is emitted when container connection state transitions to 'Connected' regardless of connection mode.
1749
+ * "connectedToService" is emitted when container connection state transitions to 'CatchingUp' or 'Connected' regardless of connection mode.
1684
1750
  * "disconnectedFromService" excludes false "disconnected" events that happen when readonly client transitions to 'Connected'.
1685
1751
  */
1686
1752
  emitServiceConnectionEvents(canSendOpsChanged, canSendOps, clientId) {
1687
1753
  if (!this.getConnectionState) {
1688
1754
  return;
1689
1755
  }
1690
- const canSendSignals = this.getConnectionState() === ConnectionState.Connected;
1756
+ const connectionState = this.getConnectionState();
1757
+ const canSendSignals = connectionState === ConnectionState.Connected ||
1758
+ connectionState === ConnectionState.CatchingUp;
1691
1759
  const canSendSignalsChanged = this.canSendSignals !== canSendSignals;
1692
1760
  this.canSendSignals = canSendSignals;
1693
1761
  if (canSendSignalsChanged) {
1694
- // If canSendSignals changed, we either transitioned from Connected to Disconnected or CatchingUp to Connected
1762
+ // If canSendSignals changed, we either transitioned from CatchingUp or
1763
+ // Connected to Disconnected or EstablishingConnection to CatchingUp.
1695
1764
  if (canSendSignals) {
1696
- // Emit for CatchingUp to Connected transition
1765
+ // Emit for EstablishingConnection to CatchingUp or Connected transition
1697
1766
  this.emit("connectedToService", clientId, canSendOps);
1698
1767
  }
1699
1768
  else {
1700
- // Emit for Connected to Disconnected transition
1769
+ // Emit for CatchingUp or Connected to Disconnected transition
1701
1770
  this.emit("disconnectedFromService");
1702
1771
  }
1703
1772
  }
1704
1773
  else if (canSendOpsChanged) {
1705
- // If canSendSignals did not change but canSendOps did, then connection type has changed.
1706
- this.emit("connectionTypeChanged", canSendOps);
1774
+ // If canSendSignals did not change but canSendOps did, then operations possible has changed.
1775
+ this.emit("operabilityChanged", canSendOps);
1707
1776
  }
1708
1777
  }
1709
1778
  async notifyOpReplay(message) {
@@ -2094,7 +2163,6 @@ export class ContainerRuntime extends TypedEventEmitter {
2094
2163
  */
2095
2164
  orderSequentially(callback) {
2096
2165
  let checkpoint;
2097
- // eslint-disable-next-line import/no-deprecated
2098
2166
  let stageControls;
2099
2167
  if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback") === true) {
2100
2168
  if (!this.batchRunner.running && !this.inStagingMode) {
@@ -2835,7 +2903,6 @@ export class ContainerRuntime extends TypedEventEmitter {
2835
2903
  // TODO: better typing
2836
2904
  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
2837
2905
  contents, localOpMetadata = undefined) {
2838
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
2839
2906
  this.submit({ type, contents }, localOpMetadata);
2840
2907
  }
2841
2908
  async uploadBlob(blob, signal) {
@@ -3304,7 +3371,9 @@ export class ContainerRuntime extends TypedEventEmitter {
3304
3371
  const getConnectionState = this.getConnectionState;
3305
3372
  if (getConnectionState) {
3306
3373
  const connectionState = getConnectionState();
3307
- if (connectionState === ConnectionState.Connected) {
3374
+ if (connectionState === ConnectionState.Connected ||
3375
+ connectionState === ConnectionState.CatchingUp) {
3376
+ // Note: when CatchingUp, canSendOps will always be false.
3308
3377
  return this.canSendOps ? "joinedForWriting" : "joinedForReading";
3309
3378
  }
3310
3379
  }
@@ -3316,16 +3385,17 @@ export class ContainerRuntime extends TypedEventEmitter {
3316
3385
  acquireExtension(id, factory, ...useContext) {
3317
3386
  let entry = this.extensions.get(id);
3318
3387
  if (entry === undefined) {
3388
+ const audience = this.signalAudience;
3319
3389
  const runtime = {
3320
3390
  getJoinedStatus: this.getJoinedStatus.bind(this),
3321
- getClientId: () => this.clientId,
3391
+ getClientId: audience ? () => audience.getSelf()?.clientId : () => this.clientId,
3322
3392
  events: this.lazyEventsForExtensions.value,
3323
3393
  logger: this.baseLogger,
3324
3394
  submitAddressedSignal: (addressChain, message) => {
3325
3395
  this.submitExtensionSignal(id, addressChain, message);
3326
3396
  },
3327
3397
  getQuorum: this.getQuorum.bind(this),
3328
- getAudience: this.getAudience.bind(this),
3398
+ getAudience: audience ? () => audience : this.getAudience.bind(this),
3329
3399
  supportedFeatures: this.ILayerCompatDetails.supportedFeatures,
3330
3400
  };
3331
3401
  entry = new factory(runtime, ...useContext);