@fluidframework/container-loader 2.0.0-internal.6.4.0 → 2.0.0-internal.7.0.1

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 (88) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/dist/catchUpMonitor.d.ts +2 -2
  3. package/dist/catchUpMonitor.d.ts.map +1 -1
  4. package/dist/connectionManager.d.ts.map +1 -1
  5. package/dist/connectionManager.js +89 -67
  6. package/dist/connectionManager.js.map +1 -1
  7. package/dist/connectionState.js +1 -1
  8. package/dist/connectionState.js.map +1 -1
  9. package/dist/connectionStateHandler.js +9 -9
  10. package/dist/connectionStateHandler.js.map +1 -1
  11. package/dist/container.d.ts +6 -1
  12. package/dist/container.d.ts.map +1 -1
  13. package/dist/container.js +215 -215
  14. package/dist/container.js.map +1 -1
  15. package/dist/containerContext.js +16 -16
  16. package/dist/containerContext.js.map +1 -1
  17. package/dist/containerStorageAdapter.d.ts.map +1 -1
  18. package/dist/containerStorageAdapter.js +6 -8
  19. package/dist/containerStorageAdapter.js.map +1 -1
  20. package/dist/contracts.d.ts +2 -1
  21. package/dist/contracts.d.ts.map +1 -1
  22. package/dist/contracts.js +1 -1
  23. package/dist/contracts.js.map +1 -1
  24. package/dist/debugLogger.js +4 -4
  25. package/dist/debugLogger.js.map +1 -1
  26. package/dist/deltaManager.d.ts.map +1 -1
  27. package/dist/deltaManager.js +86 -89
  28. package/dist/deltaManager.js.map +1 -1
  29. package/dist/deltaQueue.js +14 -14
  30. package/dist/deltaQueue.js.map +1 -1
  31. package/dist/loader.d.ts +2 -5
  32. package/dist/loader.d.ts.map +1 -1
  33. package/dist/loader.js +19 -84
  34. package/dist/loader.js.map +1 -1
  35. package/dist/packageVersion.d.ts +1 -1
  36. package/dist/packageVersion.js +1 -1
  37. package/dist/packageVersion.js.map +1 -1
  38. package/dist/protocol.d.ts +1 -2
  39. package/dist/protocol.d.ts.map +1 -1
  40. package/dist/protocol.js +3 -5
  41. package/dist/protocol.js.map +1 -1
  42. package/dist/tsdoc-metadata.json +1 -1
  43. package/lib/catchUpMonitor.d.ts +2 -2
  44. package/lib/catchUpMonitor.d.ts.map +1 -1
  45. package/lib/connectionManager.d.ts.map +1 -1
  46. package/lib/connectionManager.js +92 -68
  47. package/lib/connectionManager.js.map +1 -1
  48. package/lib/connectionStateHandler.js +9 -9
  49. package/lib/connectionStateHandler.js.map +1 -1
  50. package/lib/container.d.ts +6 -1
  51. package/lib/container.d.ts.map +1 -1
  52. package/lib/container.js +216 -216
  53. package/lib/container.js.map +1 -1
  54. package/lib/containerContext.js +16 -16
  55. package/lib/containerContext.js.map +1 -1
  56. package/lib/containerStorageAdapter.d.ts.map +1 -1
  57. package/lib/containerStorageAdapter.js +6 -8
  58. package/lib/containerStorageAdapter.js.map +1 -1
  59. package/lib/contracts.d.ts +2 -1
  60. package/lib/contracts.d.ts.map +1 -1
  61. package/lib/contracts.js.map +1 -1
  62. package/lib/debugLogger.js +4 -4
  63. package/lib/debugLogger.js.map +1 -1
  64. package/lib/deltaManager.d.ts.map +1 -1
  65. package/lib/deltaManager.js +86 -89
  66. package/lib/deltaManager.js.map +1 -1
  67. package/lib/deltaQueue.js +14 -14
  68. package/lib/deltaQueue.js.map +1 -1
  69. package/lib/loader.d.ts +2 -5
  70. package/lib/loader.d.ts.map +1 -1
  71. package/lib/loader.js +19 -84
  72. package/lib/loader.js.map +1 -1
  73. package/lib/packageVersion.d.ts +1 -1
  74. package/lib/packageVersion.js +1 -1
  75. package/lib/packageVersion.js.map +1 -1
  76. package/lib/protocol.d.ts +1 -2
  77. package/lib/protocol.d.ts.map +1 -1
  78. package/lib/protocol.js +1 -3
  79. package/lib/protocol.js.map +1 -1
  80. package/package.json +24 -18
  81. package/src/connectionManager.ts +37 -5
  82. package/src/container.ts +15 -15
  83. package/src/containerStorageAdapter.ts +0 -6
  84. package/src/contracts.ts +5 -1
  85. package/src/deltaManager.ts +6 -8
  86. package/src/loader.ts +23 -91
  87. package/src/packageVersion.ts +1 -1
  88. package/src/protocol.ts +2 -6
package/lib/container.js CHANGED
@@ -25,7 +25,7 @@ import { initQuorumValuesFromCodeDetails } from "./quorum";
25
25
  import { NoopHeuristic } from "./noopHeuristic";
26
26
  import { ConnectionManager } from "./connectionManager";
27
27
  import { ConnectionState } from "./connectionState";
28
- import { OnlyValidTermValue, ProtocolHandler, protocolHandlerShouldProcessSignal, } from "./protocol";
28
+ import { ProtocolHandler, protocolHandlerShouldProcessSignal, } from "./protocol";
29
29
  const detachedContainerRefSeqNumber = 0;
30
30
  const dirtyContainerEvent = "dirty";
31
31
  const savedContainerEvent = "saved";
@@ -121,208 +121,6 @@ export async function ReportIfTooLong(logger, eventName, action) {
121
121
  }
122
122
  const summarizerClientType = "summarizer";
123
123
  export class Container extends EventEmitterWithErrorHandling {
124
- /**
125
- * @internal
126
- */
127
- constructor(createProps, loadProps) {
128
- super((name, error) => {
129
- this.mc.logger.sendErrorEvent({
130
- eventName: "ContainerEventHandlerException",
131
- name: typeof name === "string" ? name : undefined,
132
- }, error);
133
- });
134
- /**
135
- * Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
136
- *
137
- * States are allowed to progress to further states:
138
- * "loading" - "loaded" - "closing" - "disposing" - "closed" - "disposed"
139
- *
140
- * For example, moving from "closed" to "disposing" is not allowed since it is an earlier state.
141
- *
142
- * loading: Container has been created, but is not yet in normal/loaded state
143
- * loaded: Container is in normal/loaded state
144
- * closing: Container has started closing process (for re-entrancy prevention)
145
- * disposing: Container has started disposing process (for re-entrancy prevention)
146
- * closed: Container has closed
147
- * disposed: Container has been disposed
148
- */
149
- this._lifecycleState = "loading";
150
- this._attachState = AttachState.Detached;
151
- /** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
152
- this.inboundQueuePausedFromInit = true;
153
- this.firstConnection = true;
154
- this.connectionTransitionTimes = [];
155
- this.attachStarted = false;
156
- this._dirtyContainer = false;
157
- this.savedOps = [];
158
- this.clientsWhoShouldHaveLeft = new Set();
159
- this.setAutoReconnectTime = performance.now();
160
- this._lifecycleEvents = new TypedEventEmitter();
161
- this._disposed = false;
162
- this.getAbsoluteUrl = async (relativeUrl) => {
163
- if (this.resolvedUrl === undefined) {
164
- return undefined;
165
- }
166
- return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, getPackageName(this._loadedCodeDetails));
167
- };
168
- this.updateDirtyContainerState = (dirty) => {
169
- if (this._dirtyContainer === dirty) {
170
- return;
171
- }
172
- this._dirtyContainer = dirty;
173
- this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
174
- };
175
- const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
176
- this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
177
- const pendingLocalState = loadProps?.pendingLocalState;
178
- this._clientId = pendingLocalState?.clientId;
179
- this._canReconnect = canReconnect ?? true;
180
- this.clientDetailsOverride = clientDetailsOverride;
181
- this.urlResolver = urlResolver;
182
- this.serviceFactory = documentServiceFactory;
183
- this.codeLoader = codeLoader;
184
- // Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
185
- // all clients that were loaded from the same loader (including summarizer clients).
186
- // Tracking alternative ways to handle this in AB#4129.
187
- this.options = { ...options };
188
- this.scope = scope;
189
- this.detachedBlobStorage = detachedBlobStorage;
190
- this.protocolHandlerBuilder =
191
- protocolHandlerBuilder ??
192
- ((attributes, quorumSnapshot, sendProposal) => new ProtocolHandler(attributes, quorumSnapshot, sendProposal, new Audience(), (clientId) => this.clientsWhoShouldHaveLeft.has(clientId)));
193
- // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
194
- this.clone = async (_loadProps, createParamOverrides) => {
195
- return Container.load(_loadProps, {
196
- ...createProps,
197
- ...createParamOverrides,
198
- });
199
- };
200
- // Create logger for data stores to use
201
- const type = this.client.details.type;
202
- const interactive = this.client.details.capabilities.interactive;
203
- const clientType = `${interactive ? "interactive" : "noninteractive"}${type !== undefined && type !== "" ? `/${type}` : ""}`;
204
- // Need to use the property getter for docId because for detached flow we don't have the docId initially.
205
- // We assign the id later so property getter is used.
206
- this.subLogger = createChildLogger({
207
- logger: subLogger,
208
- properties: {
209
- all: {
210
- clientType,
211
- containerId: uuid(),
212
- docId: () => this.resolvedUrl?.id,
213
- containerAttachState: () => this._attachState,
214
- containerLifecycleState: () => this._lifecycleState,
215
- containerConnectionState: () => ConnectionState[this.connectionState],
216
- serializedContainer: pendingLocalState !== undefined,
217
- },
218
- // we need to be judicious with our logging here to avoid generating too much data
219
- // all data logged here should be broadly applicable, and not specific to a
220
- // specific error or class of errors
221
- error: {
222
- // load information to associate errors with the specific load point
223
- dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
224
- dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
225
- dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
226
- containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
227
- containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
228
- // message information to associate errors with the specific execution state
229
- // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
230
- dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
231
- dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
232
- dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId === null
233
- ? "null"
234
- : this.deltaManager?.lastMessage?.clientId,
235
- dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
236
- connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
237
- },
238
- },
239
- });
240
- // Prefix all events in this file with container-loader
241
- this.mc = createChildMonitoringContext({ logger: this.subLogger, namespace: "Container" });
242
- this._deltaManager = this.createDeltaManager();
243
- this.connectionStateHandler = createConnectionStateHandler({
244
- logger: this.mc.logger,
245
- connectionStateChanged: (value, oldState, reason) => {
246
- if (value === ConnectionState.Connected) {
247
- this._clientId = this.connectionStateHandler.pendingClientId;
248
- }
249
- this.logConnectionStateChangeTelemetry(value, oldState, reason);
250
- if (this._lifecycleState === "loaded") {
251
- this.propagateConnectionState(false /* initial transition */, value === ConnectionState.Disconnected
252
- ? reason
253
- : undefined /* disconnectedReason */);
254
- }
255
- },
256
- shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
257
- maxClientLeaveWaitTime: options.maxClientLeaveWaitTime,
258
- logConnectionIssue: (eventName, category, details) => {
259
- const mode = this.connectionMode;
260
- // We get here when socket does not receive any ops on "write" connection, including
261
- // its own join op.
262
- // Report issues only if we already loaded container - op processing is paused while container is loading,
263
- // so we always time-out processing of join op in cases where fetching snapshot takes a minute.
264
- // It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
265
- this._deltaManager.logConnectionIssue({
266
- eventName,
267
- mode,
268
- category: this._lifecycleState === "loading" ? "generic" : category,
269
- duration: performance.now() -
270
- this.connectionTransitionTimes[ConnectionState.CatchingUp],
271
- ...(details === undefined ? {} : { details: JSON.stringify(details) }),
272
- });
273
- // If this is "write" connection, it took too long to receive join op. But in most cases that's due
274
- // to very slow op fetches and we will eventually get there.
275
- // For "read" connections, we get here due to self join signal not arriving on time. We will need to
276
- // better understand when and why it may happen.
277
- // For now, attempt to recover by reconnecting. In future, maybe we can query relay service for
278
- // current state of audience.
279
- // Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
280
- // to call this.applyForConnectedState("addMemberEvent") for "read" connections)
281
- if (mode === "read") {
282
- const reason = { text: "NoJoinSignal" };
283
- this.disconnectInternal(reason);
284
- this.connectInternal({ reason, fetchOpsFromStorage: false });
285
- }
286
- },
287
- clientShouldHaveLeft: (clientId) => {
288
- this.clientsWhoShouldHaveLeft.add(clientId);
289
- },
290
- }, this.deltaManager, pendingLocalState?.clientId);
291
- this.on(savedContainerEvent, () => {
292
- this.connectionStateHandler.containerSaved();
293
- });
294
- // We expose our storage publicly, so it's possible others may call uploadSummaryWithContext() with a
295
- // non-combined summary tree (in particular, ContainerRuntime.submitSummary). We'll intercept those calls
296
- // using this callback and fix them up.
297
- const addProtocolSummaryIfMissing = (summaryTree) => isCombinedAppAndProtocolSummary(summaryTree) === true
298
- ? summaryTree
299
- : combineAppAndProtocolSummary(summaryTree, this.captureProtocolSummary());
300
- // Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
301
- // Even if not forced on via this flag, combined summaries may still be enabled by service policy.
302
- const forceEnableSummarizeProtocolTree = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
303
- options.summarizeProtocolTree;
304
- this.storageAdapter = new ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
305
- const isDomAvailable = typeof document === "object" &&
306
- document !== null &&
307
- typeof document.addEventListener === "function" &&
308
- document.addEventListener !== null;
309
- // keep track of last time page was visible for telemetry (on interactive clients only)
310
- if (isDomAvailable && interactive) {
311
- this.lastVisible = document.hidden ? performance.now() : undefined;
312
- this.visibilityEventHandler = () => {
313
- if (document.hidden) {
314
- this.lastVisible = performance.now();
315
- }
316
- else {
317
- // settimeout so this will hopefully fire after disconnect event if being hidden caused it
318
- setTimeout(() => {
319
- this.lastVisible = undefined;
320
- }, 0);
321
- }
322
- };
323
- document.addEventListener("visibilitychange", this.visibilityEventHandler);
324
- }
325
- }
326
124
  /**
327
125
  * Load an existing container.
328
126
  * @internal
@@ -417,6 +215,10 @@ export class Container extends EventEmitterWithErrorHandling {
417
215
  get connectionMode() {
418
216
  return this._deltaManager.connectionManager.connectionMode;
419
217
  }
218
+ /**
219
+ * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
220
+ */
221
+ // eslint-disable-next-line import/no-deprecated
420
222
  get IFluidRouter() {
421
223
  return this;
422
224
  }
@@ -532,6 +334,208 @@ export class Container extends EventEmitterWithErrorHandling {
532
334
  this._lifecycleEvents.once("disposed", disposedHandler);
533
335
  });
534
336
  }
337
+ /**
338
+ * @internal
339
+ */
340
+ constructor(createProps, loadProps) {
341
+ super((name, error) => {
342
+ this.mc.logger.sendErrorEvent({
343
+ eventName: "ContainerEventHandlerException",
344
+ name: typeof name === "string" ? name : undefined,
345
+ }, error);
346
+ });
347
+ /**
348
+ * Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
349
+ *
350
+ * States are allowed to progress to further states:
351
+ * "loading" - "loaded" - "closing" - "disposing" - "closed" - "disposed"
352
+ *
353
+ * For example, moving from "closed" to "disposing" is not allowed since it is an earlier state.
354
+ *
355
+ * loading: Container has been created, but is not yet in normal/loaded state
356
+ * loaded: Container is in normal/loaded state
357
+ * closing: Container has started closing process (for re-entrancy prevention)
358
+ * disposing: Container has started disposing process (for re-entrancy prevention)
359
+ * closed: Container has closed
360
+ * disposed: Container has been disposed
361
+ */
362
+ this._lifecycleState = "loading";
363
+ this._attachState = AttachState.Detached;
364
+ /** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
365
+ this.inboundQueuePausedFromInit = true;
366
+ this.firstConnection = true;
367
+ this.connectionTransitionTimes = [];
368
+ this.attachStarted = false;
369
+ this._dirtyContainer = false;
370
+ this.savedOps = [];
371
+ this.clientsWhoShouldHaveLeft = new Set();
372
+ this.setAutoReconnectTime = performance.now();
373
+ this._lifecycleEvents = new TypedEventEmitter();
374
+ this._disposed = false;
375
+ this.getAbsoluteUrl = async (relativeUrl) => {
376
+ if (this.resolvedUrl === undefined) {
377
+ return undefined;
378
+ }
379
+ return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, getPackageName(this._loadedCodeDetails));
380
+ };
381
+ this.updateDirtyContainerState = (dirty) => {
382
+ if (this._dirtyContainer === dirty) {
383
+ return;
384
+ }
385
+ this._dirtyContainer = dirty;
386
+ this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
387
+ };
388
+ const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
389
+ this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
390
+ const pendingLocalState = loadProps?.pendingLocalState;
391
+ this._clientId = pendingLocalState?.clientId;
392
+ this._canReconnect = canReconnect ?? true;
393
+ this.clientDetailsOverride = clientDetailsOverride;
394
+ this.urlResolver = urlResolver;
395
+ this.serviceFactory = documentServiceFactory;
396
+ this.codeLoader = codeLoader;
397
+ // Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
398
+ // all clients that were loaded from the same loader (including summarizer clients).
399
+ // Tracking alternative ways to handle this in AB#4129.
400
+ this.options = { ...options };
401
+ this.scope = scope;
402
+ this.detachedBlobStorage = detachedBlobStorage;
403
+ this.protocolHandlerBuilder =
404
+ protocolHandlerBuilder ??
405
+ ((attributes, quorumSnapshot, sendProposal) => new ProtocolHandler(attributes, quorumSnapshot, sendProposal, new Audience(), (clientId) => this.clientsWhoShouldHaveLeft.has(clientId)));
406
+ // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
407
+ this.clone = async (_loadProps, createParamOverrides) => {
408
+ return Container.load(_loadProps, {
409
+ ...createProps,
410
+ ...createParamOverrides,
411
+ });
412
+ };
413
+ // Create logger for data stores to use
414
+ const type = this.client.details.type;
415
+ const interactive = this.client.details.capabilities.interactive;
416
+ const clientType = `${interactive ? "interactive" : "noninteractive"}${type !== undefined && type !== "" ? `/${type}` : ""}`;
417
+ // Need to use the property getter for docId because for detached flow we don't have the docId initially.
418
+ // We assign the id later so property getter is used.
419
+ this.subLogger = createChildLogger({
420
+ logger: subLogger,
421
+ properties: {
422
+ all: {
423
+ clientType,
424
+ containerId: uuid(),
425
+ docId: () => this.resolvedUrl?.id,
426
+ containerAttachState: () => this._attachState,
427
+ containerLifecycleState: () => this._lifecycleState,
428
+ containerConnectionState: () => ConnectionState[this.connectionState],
429
+ serializedContainer: pendingLocalState !== undefined,
430
+ },
431
+ // we need to be judicious with our logging here to avoid generating too much data
432
+ // all data logged here should be broadly applicable, and not specific to a
433
+ // specific error or class of errors
434
+ error: {
435
+ // load information to associate errors with the specific load point
436
+ dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
437
+ dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
438
+ dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
439
+ containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
440
+ containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
441
+ // message information to associate errors with the specific execution state
442
+ // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
443
+ dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
444
+ dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
445
+ dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId === null
446
+ ? "null"
447
+ : this.deltaManager?.lastMessage?.clientId,
448
+ dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
449
+ connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
450
+ },
451
+ },
452
+ });
453
+ // Prefix all events in this file with container-loader
454
+ this.mc = createChildMonitoringContext({ logger: this.subLogger, namespace: "Container" });
455
+ this._deltaManager = this.createDeltaManager();
456
+ this.connectionStateHandler = createConnectionStateHandler({
457
+ logger: this.mc.logger,
458
+ connectionStateChanged: (value, oldState, reason) => {
459
+ if (value === ConnectionState.Connected) {
460
+ this._clientId = this.connectionStateHandler.pendingClientId;
461
+ }
462
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
463
+ if (this._lifecycleState === "loaded") {
464
+ this.propagateConnectionState(false /* initial transition */, value === ConnectionState.Disconnected
465
+ ? reason
466
+ : undefined /* disconnectedReason */);
467
+ }
468
+ },
469
+ shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
470
+ maxClientLeaveWaitTime: options.maxClientLeaveWaitTime,
471
+ logConnectionIssue: (eventName, category, details) => {
472
+ const mode = this.connectionMode;
473
+ // We get here when socket does not receive any ops on "write" connection, including
474
+ // its own join op.
475
+ // Report issues only if we already loaded container - op processing is paused while container is loading,
476
+ // so we always time-out processing of join op in cases where fetching snapshot takes a minute.
477
+ // It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
478
+ this._deltaManager.logConnectionIssue({
479
+ eventName,
480
+ mode,
481
+ category: this._lifecycleState === "loading" ? "generic" : category,
482
+ duration: performance.now() -
483
+ this.connectionTransitionTimes[ConnectionState.CatchingUp],
484
+ ...(details === undefined ? {} : { details: JSON.stringify(details) }),
485
+ });
486
+ // If this is "write" connection, it took too long to receive join op. But in most cases that's due
487
+ // to very slow op fetches and we will eventually get there.
488
+ // For "read" connections, we get here due to self join signal not arriving on time. We will need to
489
+ // better understand when and why it may happen.
490
+ // For now, attempt to recover by reconnecting. In future, maybe we can query relay service for
491
+ // current state of audience.
492
+ // Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
493
+ // to call this.applyForConnectedState("addMemberEvent") for "read" connections)
494
+ if (mode === "read") {
495
+ const reason = { text: "NoJoinSignal" };
496
+ this.disconnectInternal(reason);
497
+ this.connectInternal({ reason, fetchOpsFromStorage: false });
498
+ }
499
+ },
500
+ clientShouldHaveLeft: (clientId) => {
501
+ this.clientsWhoShouldHaveLeft.add(clientId);
502
+ },
503
+ }, this.deltaManager, pendingLocalState?.clientId);
504
+ this.on(savedContainerEvent, () => {
505
+ this.connectionStateHandler.containerSaved();
506
+ });
507
+ // We expose our storage publicly, so it's possible others may call uploadSummaryWithContext() with a
508
+ // non-combined summary tree (in particular, ContainerRuntime.submitSummary). We'll intercept those calls
509
+ // using this callback and fix them up.
510
+ const addProtocolSummaryIfMissing = (summaryTree) => isCombinedAppAndProtocolSummary(summaryTree) === true
511
+ ? summaryTree
512
+ : combineAppAndProtocolSummary(summaryTree, this.captureProtocolSummary());
513
+ // Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
514
+ // Even if not forced on via this flag, combined summaries may still be enabled by service policy.
515
+ const forceEnableSummarizeProtocolTree = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
516
+ options.summarizeProtocolTree;
517
+ this.storageAdapter = new ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
518
+ const isDomAvailable = typeof document === "object" &&
519
+ document !== null &&
520
+ typeof document.addEventListener === "function" &&
521
+ document.addEventListener !== null;
522
+ // keep track of last time page was visible for telemetry (on interactive clients only)
523
+ if (isDomAvailable && interactive) {
524
+ this.lastVisible = document.hidden ? performance.now() : undefined;
525
+ this.visibilityEventHandler = () => {
526
+ if (document.hidden) {
527
+ this.lastVisible = performance.now();
528
+ }
529
+ else {
530
+ // settimeout so this will hopefully fire after disconnect event if being hidden caused it
531
+ setTimeout(() => {
532
+ this.lastVisible = undefined;
533
+ }, 0);
534
+ }
535
+ };
536
+ document.addEventListener("visibilitychange", this.visibilityEventHandler);
537
+ }
538
+ }
535
539
  /**
536
540
  * Retrieves the quorum associated with the document
537
541
  */
@@ -666,7 +670,6 @@ export class Container extends EventEmitterWithErrorHandling {
666
670
  snapshotBlobs: this.baseSnapshotBlobs,
667
671
  savedOps: this.savedOps,
668
672
  url: this.resolvedUrl.url,
669
- term: OnlyValidTermValue,
670
673
  // no need to save this if there is no pending runtime state
671
674
  clientId: pendingRuntimeState !== undefined ? this.clientId : undefined,
672
675
  };
@@ -788,6 +791,9 @@ export class Container extends EventEmitterWithErrorHandling {
788
791
  }
789
792
  }, { start: true, end: true, cancel: "generic" });
790
793
  }
794
+ /**
795
+ * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
796
+ */
791
797
  async request(path) {
792
798
  return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
793
799
  }
@@ -938,18 +944,14 @@ export class Container extends EventEmitterWithErrorHandling {
938
944
  async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState, loadToSequenceNumber) {
939
945
  const timings = { phase1: performance.now() };
940
946
  this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
941
- // Ideally we always connect as "read" by default.
942
- // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
943
- // We should not rely on it by (one of them will address the issue, but we need to address both)
944
- // 1) switching create new flow to one where we create file by posting snapshot
945
- // 2) Fixing quorum workflows (have retry logic)
946
- // That all said, "read" does not work with memorylicious workflows (that opens two simultaneous
947
- // connections to same file) in two ways:
948
- // A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
949
- // B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
947
+ // Except in cases where it has stashed ops or requested by feature gate, the container will connect in "read" mode
948
+ const mode = this.mc.config.getBoolean("Fluid.Container.ForceWriteConnection") === true ||
949
+ (pendingLocalState?.savedOps.length ?? 0) > 0
950
+ ? "write"
951
+ : "read";
950
952
  const connectionArgs = {
951
953
  reason: { text: "DocumentOpen" },
952
- mode: "write",
954
+ mode,
953
955
  fetchOpsFromStorage: false,
954
956
  };
955
957
  // Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
@@ -1114,7 +1116,6 @@ export class Container extends EventEmitterWithErrorHandling {
1114
1116
  async createDetached(codeDetails) {
1115
1117
  const attributes = {
1116
1118
  sequenceNumber: detachedContainerRefSeqNumber,
1117
- term: OnlyValidTermValue,
1118
1119
  minimumSequenceNumber: 0,
1119
1120
  };
1120
1121
  await this.attachDeltaManagerOpHandler(attributes);
@@ -1154,7 +1155,6 @@ export class Container extends EventEmitterWithErrorHandling {
1154
1155
  return {
1155
1156
  minimumSequenceNumber: 0,
1156
1157
  sequenceNumber: 0,
1157
- term: OnlyValidTermValue,
1158
1158
  };
1159
1159
  }
1160
1160
  // Backward compatibility: old docs would have ".attributes" instead of "attributes"