@fluidframework/container-loader 2.0.0-dev-rc.5.0.0.263932 → 2.0.0-dev-rc.5.0.0.267932

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 (171) hide show
  1. package/api-report/container-loader.api.md +6 -4
  2. package/dist/attachment.d.ts +1 -1
  3. package/dist/attachment.d.ts.map +1 -1
  4. package/dist/attachment.js.map +1 -1
  5. package/dist/audience.d.ts +3 -2
  6. package/dist/audience.d.ts.map +1 -1
  7. package/dist/audience.js.map +1 -1
  8. package/dist/catchUpMonitor.js.map +1 -1
  9. package/dist/connectionManager.d.ts +4 -3
  10. package/dist/connectionManager.d.ts.map +1 -1
  11. package/dist/connectionManager.js +7 -8
  12. package/dist/connectionManager.js.map +1 -1
  13. package/dist/connectionStateHandler.d.ts +4 -2
  14. package/dist/connectionStateHandler.d.ts.map +1 -1
  15. package/dist/connectionStateHandler.js +25 -15
  16. package/dist/connectionStateHandler.js.map +1 -1
  17. package/dist/container.d.ts +2 -2
  18. package/dist/container.d.ts.map +1 -1
  19. package/dist/container.js +86 -69
  20. package/dist/container.js.map +1 -1
  21. package/dist/containerContext.d.ts +2 -2
  22. package/dist/containerContext.d.ts.map +1 -1
  23. package/dist/containerContext.js.map +1 -1
  24. package/dist/containerStorageAdapter.d.ts +2 -2
  25. package/dist/containerStorageAdapter.d.ts.map +1 -1
  26. package/dist/containerStorageAdapter.js +7 -2
  27. package/dist/containerStorageAdapter.js.map +1 -1
  28. package/dist/contracts.d.ts +2 -2
  29. package/dist/contracts.d.ts.map +1 -1
  30. package/dist/contracts.js.map +1 -1
  31. package/dist/debugLogger.d.ts.map +1 -1
  32. package/dist/debugLogger.js.map +1 -1
  33. package/dist/deltaManager.d.ts +6 -5
  34. package/dist/deltaManager.d.ts.map +1 -1
  35. package/dist/deltaManager.js +15 -16
  36. package/dist/deltaManager.js.map +1 -1
  37. package/dist/deltaQueue.js.map +1 -1
  38. package/dist/disposal.js.map +1 -1
  39. package/dist/loadPaused.js.map +1 -1
  40. package/dist/loader.d.ts +8 -1
  41. package/dist/loader.d.ts.map +1 -1
  42. package/dist/loader.js.map +1 -1
  43. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -1
  44. package/dist/memoryBlobStorage.d.ts +9 -0
  45. package/dist/memoryBlobStorage.d.ts.map +1 -0
  46. package/dist/memoryBlobStorage.js +58 -0
  47. package/dist/memoryBlobStorage.js.map +1 -0
  48. package/dist/noopHeuristic.d.ts +1 -1
  49. package/dist/noopHeuristic.d.ts.map +1 -1
  50. package/dist/noopHeuristic.js.map +1 -1
  51. package/dist/packageVersion.d.ts +1 -1
  52. package/dist/packageVersion.js +1 -1
  53. package/dist/packageVersion.js.map +1 -1
  54. package/dist/protocol.d.ts +4 -3
  55. package/dist/protocol.d.ts.map +1 -1
  56. package/dist/protocol.js +4 -4
  57. package/dist/protocol.js.map +1 -1
  58. package/dist/protocolTreeDocumentStorageService.d.ts +5 -5
  59. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  60. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  61. package/dist/quorum.d.ts +1 -1
  62. package/dist/quorum.d.ts.map +1 -1
  63. package/dist/quorum.js.map +1 -1
  64. package/dist/retriableDocumentStorageService.d.ts +2 -2
  65. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  66. package/dist/retriableDocumentStorageService.js.map +1 -1
  67. package/dist/serializedStateManager.d.ts +5 -3
  68. package/dist/serializedStateManager.d.ts.map +1 -1
  69. package/dist/serializedStateManager.js +6 -2
  70. package/dist/serializedStateManager.js.map +1 -1
  71. package/dist/utils.d.ts +2 -1
  72. package/dist/utils.d.ts.map +1 -1
  73. package/dist/utils.js +7 -7
  74. package/dist/utils.js.map +1 -1
  75. package/lib/attachment.d.ts +1 -1
  76. package/lib/attachment.d.ts.map +1 -1
  77. package/lib/attachment.js.map +1 -1
  78. package/lib/audience.d.ts +3 -2
  79. package/lib/audience.d.ts.map +1 -1
  80. package/lib/audience.js.map +1 -1
  81. package/lib/catchUpMonitor.js.map +1 -1
  82. package/lib/connectionManager.d.ts +4 -3
  83. package/lib/connectionManager.d.ts.map +1 -1
  84. package/lib/connectionManager.js +5 -6
  85. package/lib/connectionManager.js.map +1 -1
  86. package/lib/connectionStateHandler.d.ts +4 -2
  87. package/lib/connectionStateHandler.d.ts.map +1 -1
  88. package/lib/connectionStateHandler.js +26 -16
  89. package/lib/connectionStateHandler.js.map +1 -1
  90. package/lib/container.d.ts +2 -2
  91. package/lib/container.d.ts.map +1 -1
  92. package/lib/container.js +28 -11
  93. package/lib/container.js.map +1 -1
  94. package/lib/containerContext.d.ts +2 -2
  95. package/lib/containerContext.d.ts.map +1 -1
  96. package/lib/containerContext.js.map +1 -1
  97. package/lib/containerStorageAdapter.d.ts +2 -2
  98. package/lib/containerStorageAdapter.d.ts.map +1 -1
  99. package/lib/containerStorageAdapter.js +7 -2
  100. package/lib/containerStorageAdapter.js.map +1 -1
  101. package/lib/contracts.d.ts +2 -2
  102. package/lib/contracts.d.ts.map +1 -1
  103. package/lib/contracts.js.map +1 -1
  104. package/lib/debugLogger.d.ts.map +1 -1
  105. package/lib/debugLogger.js.map +1 -1
  106. package/lib/deltaManager.d.ts +6 -5
  107. package/lib/deltaManager.d.ts.map +1 -1
  108. package/lib/deltaManager.js +8 -9
  109. package/lib/deltaManager.js.map +1 -1
  110. package/lib/deltaQueue.js.map +1 -1
  111. package/lib/disposal.js.map +1 -1
  112. package/lib/loadPaused.js.map +1 -1
  113. package/lib/loader.d.ts +8 -1
  114. package/lib/loader.d.ts.map +1 -1
  115. package/lib/loader.js.map +1 -1
  116. package/lib/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -1
  117. package/lib/memoryBlobStorage.d.ts +9 -0
  118. package/lib/memoryBlobStorage.d.ts.map +1 -0
  119. package/lib/memoryBlobStorage.js +52 -0
  120. package/lib/memoryBlobStorage.js.map +1 -0
  121. package/lib/noopHeuristic.d.ts +1 -1
  122. package/lib/noopHeuristic.d.ts.map +1 -1
  123. package/lib/noopHeuristic.js.map +1 -1
  124. package/lib/packageVersion.d.ts +1 -1
  125. package/lib/packageVersion.js +1 -1
  126. package/lib/packageVersion.js.map +1 -1
  127. package/lib/protocol.d.ts +4 -3
  128. package/lib/protocol.d.ts.map +1 -1
  129. package/lib/protocol.js +1 -1
  130. package/lib/protocol.js.map +1 -1
  131. package/lib/protocolTreeDocumentStorageService.d.ts +5 -5
  132. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  133. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  134. package/lib/quorum.d.ts +1 -1
  135. package/lib/quorum.d.ts.map +1 -1
  136. package/lib/quorum.js.map +1 -1
  137. package/lib/retriableDocumentStorageService.d.ts +2 -2
  138. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  139. package/lib/retriableDocumentStorageService.js.map +1 -1
  140. package/lib/serializedStateManager.d.ts +5 -3
  141. package/lib/serializedStateManager.d.ts.map +1 -1
  142. package/lib/serializedStateManager.js +6 -2
  143. package/lib/serializedStateManager.js.map +1 -1
  144. package/lib/tsdoc-metadata.json +1 -1
  145. package/lib/utils.d.ts +2 -1
  146. package/lib/utils.d.ts.map +1 -1
  147. package/lib/utils.js +2 -2
  148. package/lib/utils.js.map +1 -1
  149. package/package.json +16 -26
  150. package/src/attachment.ts +3 -1
  151. package/src/audience.ts +3 -6
  152. package/src/catchUpMonitor.ts +1 -1
  153. package/src/connectionManager.ts +18 -21
  154. package/src/connectionStateHandler.ts +35 -23
  155. package/src/container.ts +51 -27
  156. package/src/containerContext.ts +6 -3
  157. package/src/containerStorageAdapter.ts +7 -6
  158. package/src/contracts.ts +7 -5
  159. package/src/debugLogger.ts +2 -3
  160. package/src/deltaManager.ts +8 -8
  161. package/src/loadPaused.ts +1 -1
  162. package/src/loader.ts +9 -1
  163. package/src/memoryBlobStorage.ts +80 -0
  164. package/src/noopHeuristic.ts +1 -1
  165. package/src/packageVersion.ts +1 -1
  166. package/src/protocol.ts +7 -8
  167. package/src/protocolTreeDocumentStorageService.ts +1 -1
  168. package/src/quorum.ts +1 -1
  169. package/src/retriableDocumentStorageService.ts +3 -6
  170. package/src/serializedStateManager.ts +14 -9
  171. package/src/utils.ts +6 -7
@@ -6,13 +6,13 @@
6
6
  import { IDeltaManager } from "@fluidframework/container-definitions/internal";
7
7
  import { ITelemetryBaseProperties } from "@fluidframework/core-interfaces";
8
8
  import { assert, Timer } from "@fluidframework/core-utils/internal";
9
+ import { IClient, ISequencedClient } from "@fluidframework/driver-definitions";
9
10
  import { IAnyDriverError } from "@fluidframework/driver-definitions/internal";
10
- import { IClient, ISequencedClient } from "@fluidframework/protocol-definitions";
11
11
  import {
12
12
  type TelemetryEventCategory,
13
13
  ITelemetryLoggerExt,
14
+ MonitoringContext,
14
15
  PerformanceEvent,
15
- loggerToMonitoringContext,
16
16
  } from "@fluidframework/telemetry-utils/internal";
17
17
 
18
18
  import { CatchUpMonitor, ICatchUpMonitor } from "./catchUpMonitor.js";
@@ -31,6 +31,7 @@ const JoinSignalTimeoutMs = 10000;
31
31
  /** Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler */
32
32
  export interface IConnectionStateHandlerInputs {
33
33
  logger: ITelemetryLoggerExt;
34
+ mc: MonitoringContext;
34
35
  /** Log to telemetry any change in state, included to Connecting */
35
36
  connectionStateChanged: (
36
37
  value: ConnectionState,
@@ -92,10 +93,10 @@ export function createConnectionStateHandler(
92
93
  deltaManager: IDeltaManager<any, any>,
93
94
  clientId?: string,
94
95
  ) {
95
- const mc = loggerToMonitoringContext(inputs.logger);
96
+ const config = inputs.mc.config;
96
97
  return createConnectionStateHandlerCore(
97
- mc.config.getBoolean("Fluid.Container.DisableCatchUpBeforeDeclaringConnected") !== true, // connectedRaisedWhenCaughtUp
98
- mc.config.getBoolean("Fluid.Container.DisableJoinSignalWait") !== true, // readClientsWaitForJoinSignal
98
+ config.getBoolean("Fluid.Container.DisableCatchUpBeforeDeclaringConnected") !== true, // connectedRaisedWhenCaughtUp
99
+ config.getBoolean("Fluid.Container.DisableJoinSignalWait") !== true, // readClientsWaitForJoinSignal
99
100
  inputs,
100
101
  deltaManager,
101
102
  clientId,
@@ -189,6 +190,9 @@ class ConnectionStateHandlerPassThrough
189
190
  public get logger() {
190
191
  return this.inputs.logger;
191
192
  }
193
+ public get mc() {
194
+ return this.inputs.mc;
195
+ }
192
196
  public connectionStateChanged(
193
197
  value: ConnectionState,
194
198
  oldState: ConnectionState,
@@ -469,18 +473,6 @@ class ConnectionStateHandler implements IConnectionStateHandler {
469
473
  });
470
474
  }
471
475
  this.applyForConnectedState("addMemberEvent");
472
- } else if (clientId === this.clientId) {
473
- // If we see our clientId and it's not also our pending ID, it's our own join op
474
- // being replayed, so start the timer in case our previous client is still in quorum
475
- assert(
476
- !this.waitingForLeaveOp,
477
- 0x5d2 /* Unexpected join op with current clientId while waiting */,
478
- );
479
- assert(
480
- this.connectionState !== ConnectionState.Connected,
481
- 0x5d3 /* Unexpected join op with current clientId while connected */,
482
- );
483
- this.prevClientLeftTimer.restart();
484
476
  }
485
477
  }
486
478
 
@@ -528,7 +520,7 @@ class ConnectionStateHandler implements IConnectionStateHandler {
528
520
 
529
521
  private receivedRemoveMemberEvent(clientId: string) {
530
522
  // If the client which has left was us, then finish the timer.
531
- if (this.clientId === clientId) {
523
+ if (this.clientId === clientId && this.waitingForLeaveOp) {
532
524
  this.prevClientLeftTimer.clear();
533
525
  this.applyForConnectedState("removeMemberEvent");
534
526
  }
@@ -660,9 +652,6 @@ class ConnectionStateHandler implements IConnectionStateHandler {
660
652
  }
661
653
  this._clientId = this.pendingClientId;
662
654
  } else if (value === ConnectionState.Disconnected) {
663
- // Clear pending state immediately to prepare for reconnect
664
- this._pendingClientId = undefined;
665
-
666
655
  if (this.joinTimer.hasTimer) {
667
656
  this.stopjoinTimer();
668
657
  }
@@ -691,8 +680,15 @@ class ConnectionStateHandler implements IConnectionStateHandler {
691
680
  }
692
681
  }
693
682
 
694
- // Report transition before we propagate event across layers
683
+ // Report transition
695
684
  this.handler.connectionStateChanged(this._connectionState, oldState, reason);
685
+
686
+ // Clear pending state immediately to prepare for reconnect
687
+ // Do it after calling connectionStateChanged() above, such that our telemetry contains pendingClientId on disconnect events
688
+ // and we can pair attempts to connect with disconnects (that's the only ID we have if connection did not move far enough before being disconnected)
689
+ if (value === ConnectionState.Disconnected) {
690
+ this._pendingClientId = undefined;
691
+ }
696
692
  }
697
693
 
698
694
  protected get membership(): IMembership | undefined {
@@ -737,8 +733,24 @@ class ConnectionStateHandler implements IConnectionStateHandler {
737
733
  this.receivedAddMemberEvent(this.pendingClientId!);
738
734
  }
739
735
 
736
+ assert(
737
+ !this.waitingForLeaveOp,
738
+ "leave timer can't be set as we have not had access to quorum",
739
+ );
740
+
741
+ // This check is required for scenario of loading container from pending state, and ensuring there is no way
742
+ // old clientId is still in the quorum (very unlikely, but you never know)
740
743
  // if we have a clientId from a previous container we need to wait for its leave message
741
- if (this.clientId !== undefined && this.hasMember(this.clientId)) {
744
+ // This mimicks check in setConnectionState()
745
+ // Note that we are not consulting this.handler.shouldClientJoinWrite() here
746
+ // It could produce wrong results for stashed ops were never sent to Loader yet, and if this check
747
+ // makes determination only on that (and not uses "dirty" events), then it can produce wrong result.
748
+ // In most cases it does not matter, as this client already left quorum. But in really unfortunate case,
749
+ // we might wait even if we could avoid such wait.
750
+ if (
751
+ this._clientId !== undefined &&
752
+ protocol.quorum?.getMember(this._clientId) !== undefined
753
+ ) {
742
754
  this.prevClientLeftTimer.restart();
743
755
  }
744
756
  }
package/src/container.ts CHANGED
@@ -37,6 +37,16 @@ import {
37
37
  } from "@fluidframework/core-interfaces";
38
38
  import { type ISignalEnvelope } from "@fluidframework/core-interfaces/internal";
39
39
  import { assert, isPromiseLike, unreachableCase } from "@fluidframework/core-utils/internal";
40
+ import {
41
+ IClient,
42
+ IClientDetails,
43
+ IQuorumClients,
44
+ ISequencedClient,
45
+ ISequencedDocumentMessage,
46
+ ISignalMessage,
47
+ ISummaryTree,
48
+ SummaryType,
49
+ } from "@fluidframework/driver-definitions";
40
50
  import {
41
51
  IDocumentService,
42
52
  IDocumentServiceFactory,
@@ -45,6 +55,15 @@ import {
45
55
  ISnapshot,
46
56
  IThrottlingWarning,
47
57
  IUrlResolver,
58
+ ICommittedProposal,
59
+ IDocumentAttributes,
60
+ IDocumentMessage,
61
+ IQuorumProposals,
62
+ ISequencedProposal,
63
+ ISnapshotTree,
64
+ ISummaryContent,
65
+ IVersion,
66
+ MessageType,
48
67
  } from "@fluidframework/driver-definitions/internal";
49
68
  import {
50
69
  getSnapshotTree,
@@ -57,25 +76,6 @@ import {
57
76
  runWithRetry,
58
77
  } from "@fluidframework/driver-utils/internal";
59
78
  import { IQuorumSnapshot } from "@fluidframework/protocol-base";
60
- import {
61
- IClient,
62
- IClientDetails,
63
- ICommittedProposal,
64
- IDocumentAttributes,
65
- IDocumentMessage,
66
- IQuorumClients,
67
- IQuorumProposals,
68
- ISequencedClient,
69
- ISequencedDocumentMessage,
70
- ISequencedProposal,
71
- ISignalMessage,
72
- ISnapshotTree,
73
- ISummaryContent,
74
- ISummaryTree,
75
- IVersion,
76
- MessageType,
77
- SummaryType,
78
- } from "@fluidframework/protocol-definitions";
79
79
  import {
80
80
  type TelemetryEventCategory,
81
81
  ITelemetryLoggerExt,
@@ -92,6 +92,7 @@ import {
92
92
  normalizeError,
93
93
  raiseConnectedEvent,
94
94
  wrapError,
95
+ loggerToMonitoringContext,
95
96
  } from "@fluidframework/telemetry-utils/internal";
96
97
  import structuredClone from "@ungap/structured-clone";
97
98
  import { v4 as uuid } from "uuid";
@@ -111,7 +112,13 @@ import {
111
112
  getPackageName,
112
113
  } from "./contracts.js";
113
114
  import { DeltaManager, IConnectionArgs } from "./deltaManager.js";
115
+ // eslint-disable-next-line import/no-deprecated
114
116
  import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader.js";
117
+ import {
118
+ serializeMemoryDetachedBlobStorage,
119
+ createMemoryDetachedBlobStorage,
120
+ tryInitializeMemoryDetachedBlobStorage,
121
+ } from "./memoryBlobStorage.js";
115
122
  import { NoopHeuristic } from "./noopHeuristic.js";
116
123
  import { pkgVersion } from "./packageVersion.js";
117
124
  import {
@@ -218,6 +225,7 @@ export interface IContainerCreateProps {
218
225
  /**
219
226
  * Blobs storage for detached containers.
220
227
  */
228
+ // eslint-disable-next-line import/no-deprecated
221
229
  readonly detachedBlobStorage?: IDetachedBlobStorage;
222
230
 
223
231
  /**
@@ -465,7 +473,8 @@ export class Container
465
473
  private readonly options: ILoaderOptions;
466
474
  private readonly scope: FluidObject;
467
475
  private readonly subLogger: ITelemetryLoggerExt;
468
- private readonly detachedBlobStorage: IDetachedBlobStorage | undefined;
476
+ // eslint-disable-next-line import/no-deprecated
477
+ private readonly detachedBlobStorage: IDetachedBlobStorage;
469
478
  private readonly protocolHandlerBuilder: ProtocolHandlerBuilder;
470
479
  private readonly client: IClient;
471
480
 
@@ -517,6 +526,15 @@ export class Container
517
526
  0x969 /* not connected yet */,
518
527
  );
519
528
 
529
+ // Track membership changes and update connection state accordingly
530
+ // We do this call here, instead of doing it in initializeProtocolState() due to pendingLocalState.
531
+ // When we load from stashed state, we let connectionStateHandler know about clientId from previous container instance.
532
+ // But we will play trailing ops from snapshot, including potentially playing join & leave ops for that same clientId!
533
+ // In other words, if connectionStateHandler has access to Quorum early in load sequence, it will see events (in stashed ops mode)
534
+ // in the order that is not possible in real life, that it may not expect.
535
+ // Ideally, we should supply pendingLocalState?.clientId here as well, not in constructor, but it does not matter (at least today)
536
+ this.connectionStateHandler.initProtocol(this.protocolHandler);
537
+
520
538
  // Propagate current connection state through the system.
521
539
  const readonly = this.readOnlyInfo.readonly ?? false;
522
540
  // This call does not look like needed any more, with delaying all connection-related events past loaded phase.
@@ -767,7 +785,7 @@ export class Container
767
785
  // Tracking alternative ways to handle this in AB#4129.
768
786
  this.options = { ...options };
769
787
  this.scope = scope;
770
- this.detachedBlobStorage = detachedBlobStorage;
788
+ this.detachedBlobStorage = detachedBlobStorage ?? createMemoryDetachedBlobStorage();
771
789
  this.protocolHandlerBuilder =
772
790
  protocolHandlerBuilder ??
773
791
  ((
@@ -856,6 +874,9 @@ export class Container
856
874
  this.connectionStateHandler = createConnectionStateHandler(
857
875
  {
858
876
  logger: this.mc.logger,
877
+ // WARNING: logger on this context should not including getters like containerConnectionState above (on this.subLogger),
878
+ // as that will result in attempt to dereference this.connectionStateHandler from this call while it's still undefined.
879
+ mc: loggerToMonitoringContext(subLogger),
859
880
  connectionStateChanged: (value, oldState, reason) => {
860
881
  this.logConnectionStateChangeTelemetry(value, oldState, reason);
861
882
  if (this.loaded) {
@@ -944,7 +965,7 @@ export class Container
944
965
  options.summarizeProtocolTree;
945
966
 
946
967
  this.storageAdapter = new ContainerStorageAdapter(
947
- detachedBlobStorage,
968
+ this.detachedBlobStorage,
948
969
  this.mc.logger,
949
970
  pendingLocalState?.snapshotBlobs,
950
971
  pendingLocalState?.loadedGroupIdSnapshots,
@@ -1208,7 +1229,8 @@ export class Container
1208
1229
  baseSnapshot,
1209
1230
  snapshotBlobs,
1210
1231
  pendingRuntimeState,
1211
- hasAttachmentBlobs: !!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
1232
+ hasAttachmentBlobs: this.detachedBlobStorage.size > 0,
1233
+ attachmentBlobs: serializeMemoryDetachedBlobStorage(this.detachedBlobStorage),
1212
1234
  };
1213
1235
  return JSON.stringify(detachedContainerState);
1214
1236
  }
@@ -1328,6 +1350,7 @@ export class Container
1328
1350
  this.serializedStateManager.setInitialSnapshot(snapshotWithBlobs);
1329
1351
 
1330
1352
  if (!this.closed) {
1353
+ this.detachedBlobStorage.dispose?.();
1331
1354
  this.handleDeltaConnectionArg(attachProps?.deltaConnection, {
1332
1355
  fetchOpsFromStorage: false,
1333
1356
  reason: { text: "createDetached" },
@@ -1760,11 +1783,15 @@ export class Container
1760
1783
  baseSnapshot,
1761
1784
  snapshotBlobs,
1762
1785
  hasAttachmentBlobs,
1786
+ attachmentBlobs,
1763
1787
  pendingRuntimeState,
1764
1788
  }: IPendingDetachedContainerState) {
1765
1789
  if (hasAttachmentBlobs) {
1790
+ if (attachmentBlobs !== undefined) {
1791
+ tryInitializeMemoryDetachedBlobStorage(this.detachedBlobStorage, attachmentBlobs);
1792
+ }
1766
1793
  assert(
1767
- !!this.detachedBlobStorage && this.detachedBlobStorage.size > 0,
1794
+ this.detachedBlobStorage.size > 0,
1768
1795
  0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */,
1769
1796
  );
1770
1797
  }
@@ -1853,9 +1880,6 @@ export class Container
1853
1880
  protocolLogger.sendErrorEvent(error);
1854
1881
  });
1855
1882
 
1856
- // Track membership changes and update connection state accordingly
1857
- this.connectionStateHandler.initProtocol(protocol);
1858
-
1859
1883
  protocol.quorum.on("addProposal", (proposal: ISequencedProposal) => {
1860
1884
  if (proposal.key === "code" || proposal.key === "code2") {
1861
1885
  this.emit("codeDetailsProposed", proposal.value, proposal);
@@ -17,17 +17,20 @@ import {
17
17
  } from "@fluidframework/container-definitions/internal";
18
18
  import { type FluidObject } from "@fluidframework/core-interfaces";
19
19
  import { type ISignalEnvelope } from "@fluidframework/core-interfaces/internal";
20
- import { IDocumentStorageService, ISnapshot } from "@fluidframework/driver-definitions/internal";
21
20
  import {
22
21
  IClientDetails,
23
- IDocumentMessage,
24
22
  IQuorumClients,
25
23
  ISequencedDocumentMessage,
24
+ } from "@fluidframework/driver-definitions";
25
+ import {
26
+ IDocumentStorageService,
27
+ ISnapshot,
28
+ IDocumentMessage,
26
29
  ISnapshotTree,
27
30
  ISummaryContent,
28
31
  IVersion,
29
32
  MessageType,
30
- } from "@fluidframework/protocol-definitions";
33
+ } from "@fluidframework/driver-definitions/internal";
31
34
  import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
32
35
 
33
36
  /**
@@ -7,6 +7,7 @@ import { bufferToString, stringToBuffer } from "@fluid-internal/client-utils";
7
7
  import { ISnapshotTreeWithBlobContents } from "@fluidframework/container-definitions/internal";
8
8
  import { IDisposable } from "@fluidframework/core-interfaces";
9
9
  import { assert } from "@fluidframework/core-utils/internal";
10
+ import { ISummaryHandle, ISummaryTree } from "@fluidframework/driver-definitions";
10
11
  import {
11
12
  FetchSource,
12
13
  IDocumentService,
@@ -15,17 +16,14 @@ import {
15
16
  ISnapshot,
16
17
  ISnapshotFetchOptions,
17
18
  ISummaryContext,
18
- } from "@fluidframework/driver-definitions/internal";
19
- import { UsageError } from "@fluidframework/driver-utils/internal";
20
- import {
21
19
  ICreateBlobResponse,
22
20
  ISnapshotTree,
23
- ISummaryHandle,
24
- ISummaryTree,
25
21
  IVersion,
26
- } from "@fluidframework/protocol-definitions";
22
+ } from "@fluidframework/driver-definitions/internal";
23
+ import { UsageError } from "@fluidframework/driver-utils/internal";
27
24
  import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal";
28
25
 
26
+ // eslint-disable-next-line import/no-deprecated
29
27
  import { IDetachedBlobStorage } from "./loader.js";
30
28
  import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService.js";
31
29
  import { RetriableDocumentStorageService } from "./retriableDocumentStorageService.js";
@@ -79,6 +77,7 @@ export class ContainerStorageAdapter
79
77
  * @param forceEnableSummarizeProtocolTree - Enforce uploading a protocol summary regardless of the service's policy
80
78
  */
81
79
  public constructor(
80
+ // eslint-disable-next-line import/no-deprecated
82
81
  detachedBlobStorage: IDetachedBlobStorage | undefined,
83
82
  private readonly logger: ITelemetryLoggerExt,
84
83
  /**
@@ -233,6 +232,7 @@ export class ContainerStorageAdapter
233
232
  */
234
233
  class BlobOnlyStorage implements IDocumentStorageService {
235
234
  constructor(
235
+ // eslint-disable-next-line import/no-deprecated
236
236
  private readonly detachedStorage: IDetachedBlobStorage | undefined,
237
237
  private readonly logger: ITelemetryLoggerExt,
238
238
  ) {}
@@ -245,6 +245,7 @@ class BlobOnlyStorage implements IDocumentStorageService {
245
245
  return this.verifyStorage().readBlob(blobId);
246
246
  }
247
247
 
248
+ // eslint-disable-next-line import/no-deprecated
248
249
  private verifyStorage(): IDetachedBlobStorage {
249
250
  if (this.detachedStorage === undefined) {
250
251
  throw new UsageError("Real storage calls not allowed in Unattached container");
package/src/contracts.ts CHANGED
@@ -12,16 +12,18 @@ import {
12
12
  IConnectionDetails,
13
13
  } from "@fluidframework/container-definitions/internal";
14
14
  import { IErrorBase, ITelemetryBaseProperties } from "@fluidframework/core-interfaces";
15
- import { IContainerPackageInfo } from "@fluidframework/driver-definitions/internal";
16
15
  import {
17
16
  ConnectionMode,
18
- IClientConfiguration,
19
17
  IClientDetails,
20
- IDocumentMessage,
21
18
  ISequencedDocumentMessage,
22
- ISignalClient,
23
19
  ISignalMessage,
24
- } from "@fluidframework/protocol-definitions";
20
+ } from "@fluidframework/driver-definitions";
21
+ import {
22
+ IContainerPackageInfo,
23
+ IClientConfiguration,
24
+ IDocumentMessage,
25
+ ISignalClient,
26
+ } from "@fluidframework/driver-definitions/internal";
25
27
 
26
28
  export enum ReconnectMode {
27
29
  Never = "Never",
@@ -16,13 +16,12 @@ import {
16
16
  eventNamespaceSeparator,
17
17
  formatTick,
18
18
  } from "@fluidframework/telemetry-utils/internal";
19
-
20
19
  // This import style is necessary to ensure the emitted JS code works in both CJS and ESM.
21
20
  import debugPkg from "debug";
22
- const { debug: registerDebug } = debugPkg;
23
-
24
21
  import type { IDebugger } from "debug";
25
22
 
23
+ const { debug: registerDebug } = debugPkg;
24
+
26
25
  /**
27
26
  * Implementation of debug logger
28
27
  */
@@ -3,8 +3,8 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import { ICriticalContainerError } from "@fluidframework/container-definitions";
6
7
  import {
7
- ICriticalContainerError,
8
8
  IDeltaManager,
9
9
  IDeltaManagerEvents,
10
10
  IDeltaQueue,
@@ -16,23 +16,23 @@ import {
16
16
  } from "@fluidframework/core-interfaces";
17
17
  import { IThrottlingWarning } from "@fluidframework/core-interfaces/internal";
18
18
  import { assert } from "@fluidframework/core-utils/internal";
19
+ import {
20
+ ConnectionMode,
21
+ ISequencedDocumentMessage,
22
+ ISignalMessage,
23
+ } from "@fluidframework/driver-definitions";
19
24
  import {
20
25
  IDocumentDeltaStorageService,
21
26
  IDocumentService,
22
27
  DriverErrorTypes,
28
+ IDocumentMessage,
29
+ MessageType,
23
30
  } from "@fluidframework/driver-definitions/internal";
24
31
  import {
25
32
  MessageType2,
26
33
  NonRetryableError,
27
34
  isRuntimeMessage,
28
35
  } from "@fluidframework/driver-utils/internal";
29
- import {
30
- ConnectionMode,
31
- IDocumentMessage,
32
- ISequencedDocumentMessage,
33
- ISignalMessage,
34
- MessageType,
35
- } from "@fluidframework/protocol-definitions";
36
36
  import {
37
37
  type ITelemetryErrorEventExt,
38
38
  type ITelemetryGenericEventExt,
package/src/loadPaused.ts CHANGED
@@ -5,8 +5,8 @@
5
5
 
6
6
  import { ILoader, LoaderHeader } from "@fluidframework/container-definitions/internal";
7
7
  import { IRequest } from "@fluidframework/core-interfaces";
8
- import { GenericError } from "@fluidframework/telemetry-utils/internal";
9
8
  import type { IErrorBase } from "@fluidframework/core-interfaces";
9
+ import { GenericError } from "@fluidframework/telemetry-utils/internal";
10
10
 
11
11
  /* eslint-disable jsdoc/check-indentation */
12
12
 
package/src/loader.ts CHANGED
@@ -19,13 +19,13 @@ import {
19
19
  IRequest,
20
20
  ITelemetryBaseLogger,
21
21
  } from "@fluidframework/core-interfaces";
22
+ import { IClientDetails } from "@fluidframework/driver-definitions";
22
23
  import {
23
24
  IDocumentServiceFactory,
24
25
  IDocumentStorageService,
25
26
  IResolvedUrl,
26
27
  IUrlResolver,
27
28
  } from "@fluidframework/driver-definitions/internal";
28
- import { IClientDetails } from "@fluidframework/protocol-definitions";
29
29
  import {
30
30
  ITelemetryLoggerExt,
31
31
  MonitoringContext,
@@ -227,6 +227,7 @@ export interface ILoaderServices {
227
227
 
228
228
  /**
229
229
  * Blobs storage for detached containers.
230
+ * @deprecated - IDetachedBlobStorage will be removed in a future release without a replacement. Blobs created while detached will be stored in memory to align with attached container behavior. AB#8049
230
231
  */
231
232
  readonly detachedBlobStorage?: IDetachedBlobStorage;
232
233
 
@@ -241,6 +242,8 @@ export interface ILoaderServices {
241
242
  * Subset of IDocumentStorageService which only supports createBlob() and readBlob(). This is used to support
242
243
  * blobs in detached containers.
243
244
  * @alpha
245
+ *
246
+ * @deprecated - IDetachedBlobStorage will be removed in a future release without a replacement. Blobs created while detached will be stored in memory to align with attached container behavior. AB#8049
244
247
  */
245
248
  export type IDetachedBlobStorage = Pick<IDocumentStorageService, "createBlob" | "readBlob"> & {
246
249
  size: number;
@@ -248,6 +251,11 @@ export type IDetachedBlobStorage = Pick<IDocumentStorageService, "createBlob" |
248
251
  * Return an array of all blob IDs present in storage
249
252
  */
250
253
  getBlobIds(): string[];
254
+
255
+ /**
256
+ * After the container is attached, the detached blob storage is no longer needed and will be disposed.
257
+ */
258
+ dispose?(): void;
251
259
  };
252
260
 
253
261
  /**
@@ -0,0 +1,80 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { bufferToString, stringToBuffer } from "@fluid-internal/client-utils";
7
+ import { assert, isObject } from "@fluidframework/core-utils/internal";
8
+ import type { ICreateBlobResponse } from "@fluidframework/driver-definitions/internal";
9
+
10
+ // eslint-disable-next-line import/no-deprecated
11
+ import type { IDetachedBlobStorage } from "./loader.js";
12
+
13
+ const MemoryDetachedBlobStorageIdentifier = Symbol();
14
+
15
+ // eslint-disable-next-line import/no-deprecated
16
+ interface MemoryDetachedBlobStorage extends IDetachedBlobStorage {
17
+ [MemoryDetachedBlobStorageIdentifier]: typeof MemoryDetachedBlobStorageIdentifier;
18
+ initialize(attachmentBlobs: string[]): void;
19
+ serialize(): string | undefined;
20
+ }
21
+
22
+ function isMemoryDetachedBlobStorage(
23
+ // eslint-disable-next-line import/no-deprecated
24
+ detachedStorage: IDetachedBlobStorage,
25
+ ): detachedStorage is MemoryDetachedBlobStorage {
26
+ return (
27
+ isObject(detachedStorage) &&
28
+ MemoryDetachedBlobStorageIdentifier in detachedStorage &&
29
+ detachedStorage[MemoryDetachedBlobStorageIdentifier] === MemoryDetachedBlobStorageIdentifier
30
+ );
31
+ }
32
+
33
+ export function serializeMemoryDetachedBlobStorage(
34
+ // eslint-disable-next-line import/no-deprecated
35
+ detachedStorage: IDetachedBlobStorage,
36
+ ): string | undefined {
37
+ if (detachedStorage.size > 0 && isMemoryDetachedBlobStorage(detachedStorage)) {
38
+ return detachedStorage.serialize();
39
+ }
40
+ }
41
+
42
+ export function tryInitializeMemoryDetachedBlobStorage(
43
+ // eslint-disable-next-line import/no-deprecated
44
+ detachedStorage: IDetachedBlobStorage,
45
+ attachmentBlobs: string,
46
+ ) {
47
+ if (!isMemoryDetachedBlobStorage(detachedStorage)) {
48
+ throw new Error(
49
+ "DetachedBlobStorage was not provided to the loader during serialize so cannot be provided during rehydrate.",
50
+ );
51
+ }
52
+
53
+ assert(detachedStorage.size === 0, "Blob storage already initialized");
54
+ const maybeAttachmentBlobs = JSON.parse(attachmentBlobs);
55
+ assert(Array.isArray(maybeAttachmentBlobs), "Invalid attachmentBlobs");
56
+
57
+ detachedStorage.initialize(maybeAttachmentBlobs);
58
+ }
59
+
60
+ // eslint-disable-next-line import/no-deprecated
61
+ export function createMemoryDetachedBlobStorage(): IDetachedBlobStorage {
62
+ const blobs: ArrayBufferLike[] = [];
63
+ const storage: MemoryDetachedBlobStorage = {
64
+ [MemoryDetachedBlobStorageIdentifier]: MemoryDetachedBlobStorageIdentifier,
65
+ createBlob: async (file: ArrayBufferLike): Promise<ICreateBlobResponse> => ({
66
+ id: `${blobs.push(file) - 1}`,
67
+ }),
68
+ readBlob: async (id: string): Promise<ArrayBufferLike> =>
69
+ blobs[Number(id)] ?? Promise.reject(new Error(`Blob not found: ${id}`)),
70
+ get size() {
71
+ return blobs.length;
72
+ },
73
+ getBlobIds: (): string[] => blobs.map((_, i) => `${i}`),
74
+ dispose: () => blobs.splice(0),
75
+ serialize: () => JSON.stringify(blobs.map((b) => bufferToString(b, "utf-8"))),
76
+ initialize: (attachmentBlobs: string[]) =>
77
+ blobs.push(...attachmentBlobs.map((maybeBlob) => stringToBuffer(maybeBlob, "utf-8"))),
78
+ };
79
+ return storage;
80
+ }
@@ -6,8 +6,8 @@
6
6
  import { TypedEventEmitter } from "@fluid-internal/client-utils";
7
7
  import { IEvent } from "@fluidframework/core-interfaces";
8
8
  import { assert, Timer } from "@fluidframework/core-utils/internal";
9
+ import { ISequencedDocumentMessage } from "@fluidframework/driver-definitions";
9
10
  import { isRuntimeMessage } from "@fluidframework/driver-utils/internal";
10
- import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
11
11
 
12
12
  const defaultNoopTimeFrequency = 2000;
13
13
  const defaultNoopCountFrequency = 50;
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.0.0-dev-rc.5.0.0.263932";
9
+ export const pkgVersion = "2.0.0-dev-rc.5.0.0.267932";
package/src/protocol.ts CHANGED
@@ -4,20 +4,19 @@
4
4
  */
5
5
 
6
6
  import { IAudienceOwner } from "@fluidframework/container-definitions/internal";
7
+ import { ISequencedDocumentMessage, ISignalMessage } from "@fluidframework/driver-definitions";
8
+ import {
9
+ IDocumentAttributes,
10
+ IProcessMessageResult,
11
+ ISignalClient,
12
+ MessageType,
13
+ } from "@fluidframework/driver-definitions/internal";
7
14
  import { canBeCoalescedByService } from "@fluidframework/driver-utils/internal";
8
15
  import {
9
16
  IProtocolHandler as IBaseProtocolHandler,
10
17
  IQuorumSnapshot,
11
18
  ProtocolOpHandler,
12
19
  } from "@fluidframework/protocol-base";
13
- import {
14
- IDocumentAttributes,
15
- IProcessMessageResult,
16
- ISequencedDocumentMessage,
17
- ISignalClient,
18
- ISignalMessage,
19
- MessageType,
20
- } from "@fluidframework/protocol-definitions";
21
20
 
22
21
  // ADO: #1986: Start using enum from protocol-base.
23
22
  export enum SignalType {
@@ -4,11 +4,11 @@
4
4
  */
5
5
 
6
6
  import { IDisposable } from "@fluidframework/core-interfaces";
7
+ import { ISummaryTree } from "@fluidframework/driver-definitions";
7
8
  import {
8
9
  IDocumentStorageService,
9
10
  ISummaryContext,
10
11
  } from "@fluidframework/driver-definitions/internal";
11
- import { ISummaryTree } from "@fluidframework/protocol-definitions";
12
12
 
13
13
  /**
14
14
  * A storage service wrapper whose sole job is to intercept calls to uploadSummaryWithContext and ensure they include
package/src/quorum.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { IFluidCodeDetails } from "@fluidframework/container-definitions/internal";
7
- import { ICommittedProposal } from "@fluidframework/protocol-definitions";
7
+ import { ICommittedProposal } from "@fluidframework/driver-definitions/internal";
8
8
 
9
9
  export function initQuorumValuesFromCodeDetails(
10
10
  source: IFluidCodeDetails,