@fluidframework/container-runtime 2.51.0 → 2.53.0-350190

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 (103) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/api-report/container-runtime.legacy.alpha.api.md +1 -2
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/blobManager/blobManager.d.ts +15 -7
  5. package/dist/blobManager/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager/blobManager.js +72 -186
  7. package/dist/blobManager/blobManager.js.map +1 -1
  8. package/dist/containerCompatibility.d.ts +34 -0
  9. package/dist/containerCompatibility.d.ts.map +1 -0
  10. package/dist/containerCompatibility.js +125 -0
  11. package/dist/containerCompatibility.js.map +1 -0
  12. package/dist/containerRuntime.d.ts +27 -15
  13. package/dist/containerRuntime.d.ts.map +1 -1
  14. package/dist/containerRuntime.js +175 -136
  15. package/dist/containerRuntime.js.map +1 -1
  16. package/dist/dataStoreContext.d.ts +6 -6
  17. package/dist/dataStoreContext.d.ts.map +1 -1
  18. package/dist/dataStoreContext.js.map +1 -1
  19. package/dist/gc/garbageCollection.d.ts.map +1 -1
  20. package/dist/gc/garbageCollection.js +5 -0
  21. package/dist/gc/garbageCollection.js.map +1 -1
  22. package/dist/index.d.ts +5 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/metadata.d.ts +3 -2
  26. package/dist/metadata.d.ts.map +1 -1
  27. package/dist/metadata.js +7 -1
  28. package/dist/metadata.js.map +1 -1
  29. package/dist/packageVersion.d.ts +1 -1
  30. package/dist/packageVersion.d.ts.map +1 -1
  31. package/dist/packageVersion.js +1 -1
  32. package/dist/packageVersion.js.map +1 -1
  33. package/dist/storageServiceWithAttachBlobs.d.ts +40 -5
  34. package/dist/storageServiceWithAttachBlobs.d.ts.map +1 -1
  35. package/dist/storageServiceWithAttachBlobs.js +56 -5
  36. package/dist/storageServiceWithAttachBlobs.js.map +1 -1
  37. package/dist/summary/documentSchema.d.ts +1 -1
  38. package/dist/summary/documentSchema.d.ts.map +1 -1
  39. package/dist/summary/documentSchema.js.map +1 -1
  40. package/dist/summary/summaryFormat.d.ts +3 -3
  41. package/dist/summary/summaryFormat.d.ts.map +1 -1
  42. package/dist/summary/summaryFormat.js.map +1 -1
  43. package/lib/blobManager/blobManager.d.ts +15 -7
  44. package/lib/blobManager/blobManager.d.ts.map +1 -1
  45. package/lib/blobManager/blobManager.js +39 -153
  46. package/lib/blobManager/blobManager.js.map +1 -1
  47. package/lib/containerCompatibility.d.ts +34 -0
  48. package/lib/containerCompatibility.d.ts.map +1 -0
  49. package/lib/containerCompatibility.js +120 -0
  50. package/lib/containerCompatibility.js.map +1 -0
  51. package/lib/containerRuntime.d.ts +27 -15
  52. package/lib/containerRuntime.d.ts.map +1 -1
  53. package/lib/containerRuntime.js +103 -64
  54. package/lib/containerRuntime.js.map +1 -1
  55. package/lib/dataStoreContext.d.ts +6 -6
  56. package/lib/dataStoreContext.d.ts.map +1 -1
  57. package/lib/dataStoreContext.js +1 -1
  58. package/lib/dataStoreContext.js.map +1 -1
  59. package/lib/gc/garbageCollection.d.ts.map +1 -1
  60. package/lib/gc/garbageCollection.js +5 -0
  61. package/lib/gc/garbageCollection.js.map +1 -1
  62. package/lib/index.d.ts +5 -1
  63. package/lib/index.d.ts.map +1 -1
  64. package/lib/index.js.map +1 -1
  65. package/lib/metadata.d.ts +3 -2
  66. package/lib/metadata.d.ts.map +1 -1
  67. package/lib/metadata.js +5 -0
  68. package/lib/metadata.js.map +1 -1
  69. package/lib/packageVersion.d.ts +1 -1
  70. package/lib/packageVersion.d.ts.map +1 -1
  71. package/lib/packageVersion.js +1 -1
  72. package/lib/packageVersion.js.map +1 -1
  73. package/lib/storageServiceWithAttachBlobs.d.ts +40 -5
  74. package/lib/storageServiceWithAttachBlobs.d.ts.map +1 -1
  75. package/lib/storageServiceWithAttachBlobs.js +56 -5
  76. package/lib/storageServiceWithAttachBlobs.js.map +1 -1
  77. package/lib/summary/documentSchema.d.ts +1 -1
  78. package/lib/summary/documentSchema.d.ts.map +1 -1
  79. package/lib/summary/documentSchema.js.map +1 -1
  80. package/lib/summary/summaryFormat.d.ts +3 -3
  81. package/lib/summary/summaryFormat.d.ts.map +1 -1
  82. package/lib/summary/summaryFormat.js.map +1 -1
  83. package/package.json +20 -20
  84. package/src/blobManager/blobManager.ts +53 -195
  85. package/src/containerCompatibility.ts +176 -0
  86. package/src/containerRuntime.ts +157 -122
  87. package/src/dataStoreContext.ts +13 -5
  88. package/src/gc/garbageCollection.ts +6 -0
  89. package/src/index.ts +6 -1
  90. package/src/metadata.ts +10 -2
  91. package/src/packageVersion.ts +1 -1
  92. package/src/storageServiceWithAttachBlobs.ts +92 -10
  93. package/src/summary/documentSchema.ts +1 -1
  94. package/src/summary/summaryFormat.ts +2 -2
  95. package/dist/compatUtils.d.ts +0 -106
  96. package/dist/compatUtils.d.ts.map +0 -1
  97. package/dist/compatUtils.js +0 -251
  98. package/dist/compatUtils.js.map +0 -1
  99. package/lib/compatUtils.d.ts +0 -106
  100. package/lib/compatUtils.d.ts.map +0 -1
  101. package/lib/compatUtils.js +0 -242
  102. package/lib/compatUtils.js.map +0 -1
  103. package/src/compatUtils.ts +0 -365
@@ -4,13 +4,14 @@
4
4
  */
5
5
  import { createEmitter, Trace, TypedEventEmitter } from "@fluid-internal/client-utils";
6
6
  import { AttachState } from "@fluidframework/container-definitions";
7
- import { isIDeltaManagerFull } from "@fluidframework/container-definitions/internal";
7
+ import { ConnectionState, isIDeltaManagerFull, } from "@fluidframework/container-definitions/internal";
8
8
  import { assert, Deferred, Lazy, LazyPromise, PromiseCache, delay, fail, unreachableCase, } from "@fluidframework/core-utils/internal";
9
9
  import { SummaryType } from "@fluidframework/driver-definitions";
10
10
  import { FetchSource, MessageType } from "@fluidframework/driver-definitions/internal";
11
11
  import { readAndParse } from "@fluidframework/driver-utils/internal";
12
12
  import { createIdCompressor, createSessionId, deserializeIdCompressor, } from "@fluidframework/id-compressor/internal";
13
13
  import { FlushMode, FlushModeExperimental, channelsTreeName, gcTreeKey, } from "@fluidframework/runtime-definitions/internal";
14
+ import { defaultMinVersionForCollab, isValidMinVersionForCollab, } from "@fluidframework/runtime-utils/internal";
14
15
  import { GCDataBuilder, RequestParser, RuntimeHeaders, TelemetryContext, addBlobToSummary, addSummarizeResultToSummary, calculateStats, create404Response, exceptionToResponse, seqFromTree, } from "@fluidframework/runtime-utils/internal";
15
16
  import { DataCorruptionError, DataProcessingError, extractSafePropertiesFromMessage, GenericError, LoggingError, PerformanceEvent,
16
17
  // eslint-disable-next-line import/no-deprecated
@@ -20,9 +21,9 @@ import { v4 as uuid } from "uuid";
20
21
  import { BindBatchTracker } from "./batchTracker.js";
21
22
  import { BlobManager, blobManagerBasePath, blobsTreeName, isBlobPath, loadBlobManagerLoadInfo, } from "./blobManager/index.js";
22
23
  import { ChannelCollection, getSummaryForDatastores, wrapContext, } from "./channelCollection.js";
23
- import { defaultMinVersionForCollab, getMinVersionForCollabDefaults, isValidMinVersionForCollab, validateRuntimeOptions, } from "./compatUtils.js";
24
24
  import { CompressionAlgorithms, disabledCompressionConfig } from "./compressionDefinitions.js";
25
25
  import { ReportOpPerfTelemetry } from "./connectionTelemetry.js";
26
+ import { getMinVersionForCollabDefaults, validateRuntimeOptions, } from "./containerCompatibility.js";
26
27
  import { ContainerFluidHandleContext } from "./containerHandleContext.js";
27
28
  import { channelToDataStore } from "./dataStore.js";
28
29
  import { FluidDataStoreRegistry } from "./dataStoreRegistry.js";
@@ -181,6 +182,12 @@ export let getSingleUseLegacyLogCallback = (logger, type) => {
181
182
  getSingleUseLegacyLogCallback = () => () => { };
182
183
  };
183
184
  };
185
+ /**
186
+ * Does nothing helper to apply unverified branding to a value.
187
+ */
188
+ function markUnverified(value) {
189
+ return value;
190
+ }
184
191
  /**
185
192
  * This is meant to be used by a {@link @fluidframework/container-definitions#IRuntimeFactory} to instantiate a container runtime.
186
193
  * @param params - An object which specifies all required and optional params necessary to instantiate a runtime.
@@ -441,13 +448,6 @@ export class ContainerRuntime extends TypedEventEmitter {
441
448
  };
442
449
  const runtime = new containerRuntimeCtor(context, registry, metadata, electedSummarizerData, chunks ?? [], aliases ?? [], internalRuntimeOptions, containerScope, logger, existing, blobManagerLoadInfo, context.storage, createIdCompressorFn, documentSchemaController, featureGatesForTelemetry, provideEntryPoint, updatedMinVersionForCollab, requestHandler, undefined, // summaryConfiguration
443
450
  recentBatchInfo);
444
- runtime.blobManager.stashedBlobsUploadP.then(() => {
445
- // make sure we didn't reconnect before the promise resolved
446
- if (runtime.delayConnectClientId !== undefined && !runtime.disposed) {
447
- runtime.delayConnectClientId = undefined;
448
- runtime.setConnectionStateCore(true, runtime.delayConnectClientId);
449
- }
450
- }, (error) => runtime.closeFn(error));
451
451
  // Initialize the base state of the runtime before it's returned.
452
452
  await runtime.initializeBaseState(context.loader);
453
453
  // Apply stashed ops with a reference sequence number equal to the sequence number of the snapshot,
@@ -591,7 +591,6 @@ export class ContainerRuntime extends TypedEventEmitter {
591
591
  this.minVersionForCollab = minVersionForCollab;
592
592
  this.requestHandler = requestHandler;
593
593
  this.summaryConfiguration = summaryConfiguration;
594
- this.imminentClosure = false;
595
594
  this.isReadOnly = () => this.deltaManager.readOnlyInfo.readonly === true;
596
595
  // We accumulate Id compressor Ops while Id compressor is not loaded yet (only for "delayed" mode)
597
596
  // Once it loads, it will process all such ops and we will stop accumulating further ops - ops will be processes as they come in.
@@ -678,16 +677,28 @@ export class ContainerRuntime extends TypedEventEmitter {
678
677
  // It is lazily create to avoid listeners (old events) that ultimately go nowhere.
679
678
  this.lazyEventsForExtensions = new Lazy(() => {
680
679
  const eventEmitter = createEmitter();
681
- this.on("connected", (clientId) => eventEmitter.emit("connected", clientId));
682
- this.on("disconnected", () => eventEmitter.emit("disconnected"));
680
+ if (this.getConnectionState) {
681
+ this.on("connectedToService", (clientId, canWrite) => {
682
+ eventEmitter.emit("joined", { clientId, canWrite });
683
+ });
684
+ this.on("disconnectedFromService", () => eventEmitter.emit("disconnected"));
685
+ this.on("connectionTypeChanged", (canWrite) => eventEmitter.emit("connectionTypeChanged", canWrite));
686
+ }
687
+ else {
688
+ this.on("connected", (clientId) => {
689
+ eventEmitter.emit("joined", { clientId, canWrite: true });
690
+ });
691
+ this.on("disconnected", () => eventEmitter.emit("disconnected"));
692
+ }
683
693
  return eventEmitter;
684
694
  });
685
- const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, pendingLocalState, supportedFeatures, snapshotWithContents, } = context;
695
+ const { options, clientDetails, connected, baseSnapshot, submitFn, submitBatchFn, submitSummaryFn, submitSignalFn, disposeFn, closeFn, deltaManager, quorum, audience, pendingLocalState, supportedFeatures, snapshotWithContents, getConnectionState, } = context;
696
+ this.getConnectionState = getConnectionState;
686
697
  // In old loaders without dispose functionality, closeFn is equivalent but will also switch container to readonly mode
687
698
  this.disposeFn = disposeFn ?? closeFn;
688
699
  // Validate that the Loader is compatible with this Runtime.
689
- const maybeloaderCompatDetailsForRuntime = context;
690
- validateLoaderCompatibility(maybeloaderCompatDetailsForRuntime.ILayerCompatDetails, this.disposeFn);
700
+ const maybeLoaderCompatDetailsForRuntime = context;
701
+ validateLoaderCompatibility(maybeLoaderCompatDetailsForRuntime.ILayerCompatDetails, this.disposeFn);
691
702
  this.mc = createChildMonitoringContext({
692
703
  logger: this.baseLogger,
693
704
  namespace: "ContainerRuntime",
@@ -798,6 +809,9 @@ export class ContainerRuntime extends TypedEventEmitter {
798
809
  // Note that we only need to pull the *initial* connected state from the context.
799
810
  // Later updates come through calls to setConnectionState.
800
811
  this.canSendOps = connected;
812
+ this.canSendSignals = this.getConnectionState
813
+ ? this.getConnectionState() === ConnectionState.Connected
814
+ : undefined;
801
815
  this.mc.logger.sendTelemetryEvent({
802
816
  eventName: "GCFeatureMatrix",
803
817
  metadataValue: JSON.stringify(metadata?.gcFeatureMatrix),
@@ -846,7 +860,7 @@ export class ContainerRuntime extends TypedEventEmitter {
846
860
  this.mc.config.getNumber(maxConsecutiveReconnectsKey) ?? defaultMaxConsecutiveReconnects;
847
861
  // If the context has ILayerCompatDetails, it supports referenceSequenceNumbers since that features
848
862
  // predates ILayerCompatDetails.
849
- const referenceSequenceNumbersSupported = maybeloaderCompatDetailsForRuntime.ILayerCompatDetails === undefined
863
+ const referenceSequenceNumbersSupported = maybeLoaderCompatDetailsForRuntime.ILayerCompatDetails === undefined
850
864
  ? supportedFeatures?.get("referenceSequenceNumbers") === true
851
865
  : true;
852
866
  if (runtimeOptions.flushMode === FlushModeExperimental.Async &&
@@ -1607,24 +1621,6 @@ export class ContainerRuntime extends TypedEventEmitter {
1607
1621
  if (canSendOps && this.sessionSchema.idCompressorMode === "delayed") {
1608
1622
  this.loadIdCompressor();
1609
1623
  }
1610
- if (canSendOps === false && this.delayConnectClientId !== undefined) {
1611
- this.delayConnectClientId = undefined;
1612
- this.mc.logger.sendTelemetryEvent({
1613
- eventName: "UnsuccessfulConnectedTransition",
1614
- });
1615
- // Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
1616
- return;
1617
- }
1618
- // If there are stashed blobs in the pending state, we need to delay
1619
- // propagation of the "connected" event until we have uploaded them to
1620
- // ensure we don't submit ops referencing a blob that has not been uploaded
1621
- const connecting = canSendOps && !this.canSendOps;
1622
- if (connecting && this.blobManager.hasPendingStashedUploads()) {
1623
- assert(!this.delayConnectClientId, 0x791 /* Connect event delay must be canceled before subsequent connect event */);
1624
- assert(!!clientId, 0x792 /* Must have clientId when connecting */);
1625
- this.delayConnectClientId = clientId;
1626
- return;
1627
- }
1628
1624
  this.setConnectionStateCore(canSendOps, clientId);
1629
1625
  }
1630
1626
  /**
@@ -1633,7 +1629,6 @@ export class ContainerRuntime extends TypedEventEmitter {
1633
1629
  * @remarks The connection state from container context used here when raising connected events.
1634
1630
  */
1635
1631
  setConnectionStateCore(canSendOps, clientId) {
1636
- assert(!this.delayConnectClientId, 0x394 /* connect event delay must be cleared before propagating connect event */);
1637
1632
  this.verifyNotClosed();
1638
1633
  // There might be no change of state due to Container calling this API after loading runtime.
1639
1634
  const canSendOpsChanged = this.canSendOps !== canSendOps;
@@ -1668,7 +1663,40 @@ export class ContainerRuntime extends TypedEventEmitter {
1668
1663
  }
1669
1664
  this.channelCollection.setConnectionState(canSendOps, clientId);
1670
1665
  this.garbageCollector.setConnectionState(canSendOps, clientId);
1666
+ // Emit "connected" and "disconnected" events based on ability to send ops
1671
1667
  raiseConnectedEvent(this.mc.logger, this, this.connected /* canSendOps */, clientId);
1668
+ // Emit "connectedToService" and "disconnectedFromService" events based on service connection status
1669
+ this.emitServiceConnectionEvents(canSendOpsChanged, canSendOps, clientId);
1670
+ }
1671
+ /**
1672
+ * Emits service connection events based on connection state changes.
1673
+ *
1674
+ * @remarks
1675
+ * "connectedToService" is emitted when container connection state transitions to 'Connected' regardless of connection mode.
1676
+ * "disconnectedFromService" excludes false "disconnected" events that happen when readonly client transitions to 'Connected'.
1677
+ */
1678
+ emitServiceConnectionEvents(canSendOpsChanged, canSendOps, clientId) {
1679
+ if (!this.getConnectionState) {
1680
+ return;
1681
+ }
1682
+ const canSendSignals = this.getConnectionState() === ConnectionState.Connected;
1683
+ const canSendSignalsChanged = this.canSendSignals !== canSendSignals;
1684
+ this.canSendSignals = canSendSignals;
1685
+ if (canSendSignalsChanged) {
1686
+ // If canSendSignals changed, we either transitioned from Connected to Disconnected or CatchingUp to Connected
1687
+ if (canSendSignals) {
1688
+ // Emit for CatchingUp to Connected transition
1689
+ this.emit("connectedToService", clientId, canSendOps);
1690
+ }
1691
+ else {
1692
+ // Emit for Connected to Disconnected transition
1693
+ this.emit("disconnectedFromService");
1694
+ }
1695
+ }
1696
+ else if (canSendOpsChanged) {
1697
+ // If canSendSignals did not change but canSendOps did, then connection type has changed.
1698
+ this.emit("connectionTypeChanged", canSendOps);
1699
+ }
1672
1700
  }
1673
1701
  async notifyOpReplay(message) {
1674
1702
  await this.pendingStateManager.applyStashedOpsAt(message.sequenceNumber);
@@ -1978,12 +2006,12 @@ export class ContainerRuntime extends TypedEventEmitter {
1978
2006
  }
1979
2007
  processSignal(message, local) {
1980
2008
  const envelope = message.content;
1981
- const transformed = {
2009
+ const transformed = markUnverified({
1982
2010
  clientId: message.clientId,
1983
2011
  content: envelope.contents.content,
1984
2012
  type: envelope.contents.type,
1985
2013
  targetClientId: message.targetClientId,
1986
- };
2014
+ });
1987
2015
  // Only collect signal telemetry for broadcast messages sent by the current client.
1988
2016
  if (message.clientId === this.clientId) {
1989
2017
  this.signalTelemetryManager.trackReceivedSignal(envelope, this.mc.logger, this.consecutiveReconnects);
@@ -2002,12 +2030,14 @@ export class ContainerRuntime extends TypedEventEmitter {
2002
2030
  // Due to a mismatch between different layers in terms of
2003
2031
  // what is the interface of passing signals, we need to adjust
2004
2032
  // the signal envelope before sending it to the datastores to be processed
2005
- const envelope = {
2006
- address,
2007
- contents: signalMessage.content,
2033
+ const channelSignalMessage = {
2034
+ ...signalMessage,
2035
+ content: {
2036
+ address,
2037
+ contents: signalMessage.content,
2038
+ },
2008
2039
  };
2009
- signalMessage.content = envelope;
2010
- this.channelCollection.processSignal(signalMessage, local);
2040
+ this.channelCollection.processSignal(channelSignalMessage, local);
2011
2041
  return;
2012
2042
  }
2013
2043
  const addresses = address.split("/");
@@ -2157,7 +2187,7 @@ export class ContainerRuntime extends TypedEventEmitter {
2157
2187
  shouldSendOps() {
2158
2188
  // Note that the real (non-proxy) delta manager is needed here to get the readonly info. This is because
2159
2189
  // container runtime's ability to send ops depend on the actual readonly state of the delta manager.
2160
- return (this.connected && !this.innerDeltaManager.readOnlyInfo.readonly && !this.imminentClosure);
2190
+ return this.connected && !this.innerDeltaManager.readOnlyInfo.readonly;
2161
2191
  }
2162
2192
  getQuorum() {
2163
2193
  return this._quorum;
@@ -3185,39 +3215,35 @@ export class ContainerRuntime extends TypedEventEmitter {
3185
3215
  }
3186
3216
  getPendingLocalState(props) {
3187
3217
  this.verifyNotClosed();
3218
+ if (props?.notifyImminentClosure) {
3219
+ throw new UsageError("notifyImminentClosure is no longer supported in ContainerRuntime");
3220
+ }
3188
3221
  if (this.batchRunner.running) {
3189
3222
  throw new UsageError("can't get state while manually accumulating a batch");
3190
3223
  }
3191
- this.imminentClosure ||= props?.notifyImminentClosure ?? false;
3192
- const getSyncState = (pendingAttachmentBlobs) => {
3224
+ // Flush pending batch.
3225
+ // getPendingLocalState() is only exposed through Container.getPendingLocalState(), so it's safe
3226
+ // to close current batch.
3227
+ this.flush();
3228
+ return PerformanceEvent.timedExec(this.mc.logger, {
3229
+ eventName: "getPendingLocalState",
3230
+ }, (event) => {
3193
3231
  const pending = this.pendingStateManager.getLocalState(props?.snapshotSequenceNumber);
3194
3232
  const sessionExpiryTimerStarted = props?.sessionExpiryTimerStarted ?? this.garbageCollector.sessionExpiryTimerStarted;
3195
3233
  const pendingIdCompressorState = this._idCompressor?.serialize(true);
3196
- return {
3234
+ const pendingAttachmentBlobs = this.blobManager.getPendingBlobs();
3235
+ const pendingRuntimeState = {
3197
3236
  pending,
3198
3237
  pendingIdCompressorState,
3199
3238
  pendingAttachmentBlobs,
3200
3239
  sessionExpiryTimerStarted,
3201
3240
  };
3202
- };
3203
- const perfEvent = {
3204
- eventName: "getPendingLocalState",
3205
- notifyImminentClosure: props?.notifyImminentClosure,
3206
- };
3207
- const logAndReturnPendingState = (event, pendingState) => {
3208
3241
  event.end({
3209
- attachmentBlobsSize: Object.keys(pendingState?.pendingAttachmentBlobs ?? {}).length,
3210
- pendingOpsSize: pendingState?.pending?.pendingStates.length,
3242
+ attachmentBlobsSize: Object.keys(pendingAttachmentBlobs ?? {}).length,
3243
+ pendingOpsSize: pendingRuntimeState?.pending?.pendingStates.length,
3211
3244
  });
3212
- return pendingState;
3213
- };
3214
- // Flush pending batch.
3215
- // getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
3216
- // to close current batch.
3217
- this.flush();
3218
- return props?.notifyImminentClosure === true
3219
- ? PerformanceEvent.timedExecAsync(this.mc.logger, perfEvent, async (event) => logAndReturnPendingState(event, getSyncState(await this.blobManager.attachAndGetPendingBlobs(props?.stopBlobAttachingSignal))))
3220
- : PerformanceEvent.timedExec(this.mc.logger, perfEvent, (event) => logAndReturnPendingState(event, getSyncState()));
3245
+ return pendingRuntimeState;
3246
+ });
3221
3247
  }
3222
3248
  summarizeOnDemand(options) {
3223
3249
  if (this._summarizer !== undefined) {
@@ -3247,11 +3273,24 @@ export class ContainerRuntime extends TypedEventEmitter {
3247
3273
  return this.summaryManager.enqueueSummarize(options);
3248
3274
  }
3249
3275
  }
3276
+ getJoinedStatus() {
3277
+ const getConnectionState = this.getConnectionState;
3278
+ if (getConnectionState) {
3279
+ const connectionState = getConnectionState();
3280
+ if (connectionState === ConnectionState.Connected) {
3281
+ return this.canSendOps ? "joinedForWriting" : "joinedForReading";
3282
+ }
3283
+ }
3284
+ else if (this.canSendOps) {
3285
+ return "joinedForWriting";
3286
+ }
3287
+ return "disconnected";
3288
+ }
3250
3289
  acquireExtension(id, factory, ...useContext) {
3251
3290
  let entry = this.extensions.get(id);
3252
3291
  if (entry === undefined) {
3253
3292
  const runtime = {
3254
- isConnected: () => this.connected,
3293
+ getJoinedStatus: this.getJoinedStatus.bind(this),
3255
3294
  getClientId: () => this.clientId,
3256
3295
  events: this.lazyEventsForExtensions.value,
3257
3296
  logger: this.baseLogger,