@fluidframework/container-loader 2.0.0-internal.2.1.2 → 2.0.0-internal.2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.eslintrc.js +1 -1
  2. package/README.md +21 -11
  3. package/dist/audience.d.ts.map +1 -1
  4. package/dist/audience.js +6 -1
  5. package/dist/audience.js.map +1 -1
  6. package/dist/collabWindowTracker.js +5 -4
  7. package/dist/collabWindowTracker.js.map +1 -1
  8. package/dist/connectionManager.d.ts.map +1 -1
  9. package/dist/connectionManager.js +13 -2
  10. package/dist/connectionManager.js.map +1 -1
  11. package/dist/connectionStateHandler.d.ts +20 -11
  12. package/dist/connectionStateHandler.d.ts.map +1 -1
  13. package/dist/connectionStateHandler.js +65 -36
  14. package/dist/connectionStateHandler.js.map +1 -1
  15. package/dist/container.d.ts +8 -0
  16. package/dist/container.d.ts.map +1 -1
  17. package/dist/container.js +23 -7
  18. package/dist/container.js.map +1 -1
  19. package/dist/containerContext.d.ts.map +1 -1
  20. package/dist/containerContext.js +5 -1
  21. package/dist/containerContext.js.map +1 -1
  22. package/dist/contracts.d.ts +8 -0
  23. package/dist/contracts.d.ts.map +1 -1
  24. package/dist/contracts.js.map +1 -1
  25. package/dist/deltaManager.d.ts +1 -1
  26. package/dist/deltaManager.d.ts.map +1 -1
  27. package/dist/deltaManager.js +4 -3
  28. package/dist/deltaManager.js.map +1 -1
  29. package/dist/packageVersion.d.ts +1 -1
  30. package/dist/packageVersion.js +1 -1
  31. package/dist/packageVersion.js.map +1 -1
  32. package/lib/audience.d.ts.map +1 -1
  33. package/lib/audience.js +6 -1
  34. package/lib/audience.js.map +1 -1
  35. package/lib/collabWindowTracker.js +5 -4
  36. package/lib/collabWindowTracker.js.map +1 -1
  37. package/lib/connectionManager.d.ts.map +1 -1
  38. package/lib/connectionManager.js +13 -2
  39. package/lib/connectionManager.js.map +1 -1
  40. package/lib/connectionStateHandler.d.ts +20 -11
  41. package/lib/connectionStateHandler.d.ts.map +1 -1
  42. package/lib/connectionStateHandler.js +65 -36
  43. package/lib/connectionStateHandler.js.map +1 -1
  44. package/lib/container.d.ts +8 -0
  45. package/lib/container.d.ts.map +1 -1
  46. package/lib/container.js +22 -7
  47. package/lib/container.js.map +1 -1
  48. package/lib/containerContext.d.ts.map +1 -1
  49. package/lib/containerContext.js +5 -1
  50. package/lib/containerContext.js.map +1 -1
  51. package/lib/contracts.d.ts +8 -0
  52. package/lib/contracts.d.ts.map +1 -1
  53. package/lib/contracts.js.map +1 -1
  54. package/lib/deltaManager.d.ts +1 -1
  55. package/lib/deltaManager.d.ts.map +1 -1
  56. package/lib/deltaManager.js +4 -3
  57. package/lib/deltaManager.js.map +1 -1
  58. package/lib/packageVersion.d.ts +1 -1
  59. package/lib/packageVersion.js +1 -1
  60. package/lib/packageVersion.js.map +1 -1
  61. package/package.json +19 -14
  62. package/prettier.config.cjs +8 -0
  63. package/src/audience.ts +6 -1
  64. package/src/collabWindowTracker.ts +5 -5
  65. package/src/connectionManager.ts +15 -3
  66. package/src/connectionStateHandler.ts +87 -39
  67. package/src/container.ts +26 -6
  68. package/src/containerContext.ts +8 -2
  69. package/src/contracts.ts +8 -0
  70. package/src/deltaManager.ts +4 -3
  71. package/src/packageVersion.ts +1 -1
@@ -7,14 +7,20 @@ import { ITelemetryLogger, ITelemetryProperties } from "@fluidframework/common-d
7
7
  import { assert, Timer } from "@fluidframework/common-utils";
8
8
  import { IConnectionDetails, IDeltaManager } from "@fluidframework/container-definitions";
9
9
  import { ILocalSequencedClient } from "@fluidframework/protocol-base";
10
- import { ConnectionMode } from "@fluidframework/protocol-definitions";
10
+ import { ISequencedClient, IClient } from "@fluidframework/protocol-definitions";
11
11
  import { PerformanceEvent, loggerToMonitoringContext } from "@fluidframework/telemetry-utils";
12
12
  import { ConnectionState } from "./connectionState";
13
13
  import { CatchUpMonitor, ICatchUpMonitor } from "./catchUpMonitor";
14
14
  import { IProtocolHandler } from "./protocol";
15
15
 
16
+ // Based on recent data, it looks like majority of cases where we get stuck are due to really slow or
17
+ // timing out ops fetches. So attempt recovery infrequently. Also fetch uses 30 second timeout, so
18
+ // if retrying fixes the problem, we should not see these events.
16
19
  const JoinOpTimeoutMs = 45000;
17
20
 
21
+ // Timeout waiting for "self" join signal, before giving up
22
+ const JoinSignalTimeoutMs = 5000;
23
+
18
24
  /** Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler */
19
25
  export interface IConnectionStateHandlerInputs {
20
26
  logger: ITelemetryLogger;
@@ -39,7 +45,7 @@ export interface IConnectionStateHandler {
39
45
  containerSaved(): void;
40
46
  dispose(): void;
41
47
  initProtocol(protocol: IProtocolHandler): void;
42
- receivedConnectEvent(connectionMode: ConnectionMode, details: IConnectionDetails): void;
48
+ receivedConnectEvent(details: IConnectionDetails): void;
43
49
  receivedDisconnectEvent(reason: string): void;
44
50
  }
45
51
 
@@ -50,7 +56,8 @@ export function createConnectionStateHandler(
50
56
  ) {
51
57
  const mc = loggerToMonitoringContext(inputs.logger);
52
58
  return createConnectionStateHandlerCore(
53
- mc.config.getBoolean("Fluid.Container.CatchUpBeforeDeclaringConnected") === true,
59
+ mc.config.getBoolean("Fluid.Container.CatchUpBeforeDeclaringConnected") === true, // connectedRaisedWhenCaughtUp
60
+ mc.config.getBoolean("Fluid.Container.DisableJoinSignalWait") !== true, // readClientsWaitForJoinSignal
54
61
  inputs,
55
62
  deltaManager,
56
63
  clientId,
@@ -58,20 +65,34 @@ export function createConnectionStateHandler(
58
65
  }
59
66
 
60
67
  export function createConnectionStateHandlerCore(
61
- wait: boolean,
68
+ connectedRaisedWhenCaughtUp: boolean,
69
+ readClientsWaitForJoinSignal: boolean,
62
70
  inputs: IConnectionStateHandlerInputs,
63
71
  deltaManager: IDeltaManager<any, any>,
64
72
  clientId?: string,
65
73
  ) {
66
- if (!wait) {
67
- return new ConnectionStateHandler(inputs, clientId);
74
+ if (!connectedRaisedWhenCaughtUp) {
75
+ return new ConnectionStateHandler(inputs, readClientsWaitForJoinSignal, clientId);
68
76
  }
69
77
  return new ConnectionStateCatchup(
70
78
  inputs,
71
- (handler: IConnectionStateHandlerInputs) => new ConnectionStateHandler(handler, clientId),
79
+ (handler: IConnectionStateHandlerInputs) => new ConnectionStateHandler(
80
+ handler,
81
+ readClientsWaitForJoinSignal,
82
+ clientId),
72
83
  deltaManager);
73
84
  }
74
85
 
86
+ /**
87
+ * Helper internal interface to abstract away Audience & Quorum
88
+ */
89
+ interface IMembership {
90
+ on(
91
+ eventName: "addMember" | "removeMember",
92
+ listener: (clientId: string, details: IClient | ISequencedClient) => void);
93
+ getMember(clientId: string): undefined | unknown;
94
+ }
95
+
75
96
  /**
76
97
  * Class that can be used as a base class for building IConnectionStateHandler adapters / pipeline.
77
98
  * It implements both ends of communication interfaces and passes data back and forward
@@ -97,8 +118,8 @@ class ConnectionStateHandlerPassThrough implements IConnectionStateHandler, ICon
97
118
  public initProtocol(protocol: IProtocolHandler) { return this.pimpl.initProtocol(protocol); }
98
119
  public receivedDisconnectEvent(reason: string) { return this.pimpl.receivedDisconnectEvent(reason); }
99
120
 
100
- public receivedConnectEvent(connectionMode: ConnectionMode, details: IConnectionDetails) {
101
- return this.pimpl.receivedConnectEvent(connectionMode, details);
121
+ public receivedConnectEvent(details: IConnectionDetails) {
122
+ return this.pimpl.receivedConnectEvent(details);
102
123
  }
103
124
 
104
125
  /**
@@ -201,8 +222,8 @@ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
201
222
  *
202
223
  * For (a) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.
203
224
  *
204
- * For (b) we log telemetry if it takes too long, but still only transition to Connected when the Join op is processed
205
- * and we are added to the Quorum.
225
+ * For (b) we log telemetry if it takes too long, but still only transition to Connected when the Join op/signal is
226
+ * processed.
206
227
  *
207
228
  * For (c) this is optional behavior, controlled by the parameters of receivedConnectEvent
208
229
  */
@@ -212,6 +233,8 @@ class ConnectionStateHandler implements IConnectionStateHandler {
212
233
  private readonly prevClientLeftTimer: Timer;
213
234
  private readonly joinOpTimer: Timer;
214
235
  private protocol?: IProtocolHandler;
236
+ private connection?: IConnectionDetails;
237
+ private _clientId?: string;
215
238
 
216
239
  private waitEvent: PerformanceEvent | undefined;
217
240
 
@@ -229,8 +252,10 @@ class ConnectionStateHandler implements IConnectionStateHandler {
229
252
 
230
253
  constructor(
231
254
  private readonly handler: IConnectionStateHandlerInputs,
232
- private _clientId?: string,
255
+ private readonly readClientsWaitForJoinSignal: boolean,
256
+ clientIdFromPausedSession?: string,
233
257
  ) {
258
+ this._clientId = clientIdFromPausedSession;
234
259
  this.prevClientLeftTimer = new Timer(
235
260
  // Default is 5 min for which we are going to wait for its own "leave" message. This is same as
236
261
  // the max time on server after which leave op is sent.
@@ -242,11 +267,8 @@ class ConnectionStateHandler implements IConnectionStateHandler {
242
267
  },
243
268
  );
244
269
 
245
- // Based on recent data, it looks like majority of cases where we get stuck are due to really slow or
246
- // timing out ops fetches. So attempt recovery infrequently. Also fetch uses 30 second timeout, so
247
- // if retrying fixes the problem, we should not see these events.
248
270
  this.joinOpTimer = new Timer(
249
- JoinOpTimeoutMs,
271
+ 0, // default value is not used - startJoinOpTimer() explicitly provides timeout
250
272
  () => {
251
273
  // I've observed timer firing within couple ms from disconnect event, looks like
252
274
  // queued timer callback is not cancelled if timer is cancelled while callback sits in the queue.
@@ -266,7 +288,10 @@ class ConnectionStateHandler implements IConnectionStateHandler {
266
288
 
267
289
  private startJoinOpTimer() {
268
290
  assert(!this.joinOpTimer.hasTimer, 0x234 /* "has joinOpTimer" */);
269
- this.joinOpTimer.start();
291
+ assert(this.connection !== undefined, 0x4b3 /* have connection */);
292
+ this.joinOpTimer.start(
293
+ this.connection.mode === "write" ? JoinOpTimeoutMs : JoinSignalTimeoutMs,
294
+ );
270
295
  }
271
296
 
272
297
  private stopJoinOpTimer() {
@@ -297,8 +322,8 @@ class ConnectionStateHandler implements IConnectionStateHandler {
297
322
  if (clientId === this.pendingClientId) {
298
323
  if (this.joinOpTimer.hasTimer) {
299
324
  this.stopJoinOpTimer();
300
- } else {
301
- // timer has already fired, meaning it took too long to get join on.
325
+ } else if (this.shouldWaitForJoinSignal()) {
326
+ // timer has already fired, meaning it took too long to get join op/signal.
302
327
  // Record how long it actually took to recover.
303
328
  this.handler.logConnectionIssue("ReceivedJoinOp");
304
329
  }
@@ -333,9 +358,11 @@ class ConnectionStateHandler implements IConnectionStateHandler {
333
358
  this.setConnectionState(ConnectionState.Connected);
334
359
  } else {
335
360
  // Adding this event temporarily so that we can get help debugging if something goes wrong.
361
+ // We may not see any ops due to being disconnected all that time - that's not an error!
362
+ const error = source === "timeout" && this.connectionState !== ConnectionState.Disconnected;
336
363
  this.handler.logger.sendTelemetryEvent({
337
364
  eventName: "connectedStateRejected",
338
- category: source === "timeout" ? "error" : "generic",
365
+ category: error ? "error" : "generic",
339
366
  details: JSON.stringify({
340
367
  source,
341
368
  pendingClientId: this.pendingClientId,
@@ -356,29 +383,35 @@ class ConnectionStateHandler implements IConnectionStateHandler {
356
383
  }
357
384
 
358
385
  public receivedDisconnectEvent(reason: string) {
386
+ this.connection = undefined;
359
387
  this.setConnectionState(ConnectionState.Disconnected, reason);
360
388
  }
361
389
 
390
+ private shouldWaitForJoinSignal() {
391
+ assert(this.connection !== undefined, 0x4b4 /* all callers call here with active connection */);
392
+ return this.connection.mode === "write" || this.readClientsWaitForJoinSignal;
393
+ }
394
+
362
395
  /**
363
396
  * The "connect" event indicates the connection to the Relay Service is live.
364
397
  * However, some additional conditions must be met before we can fully transition to
365
398
  * "Connected" state. This function handles that interim period, known as "Connecting" state.
366
- * @param connectionMode - Read or Write connection
367
399
  * @param details - Connection details returned from the Relay Service
368
400
  * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.
369
401
  * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for
370
402
  */
371
403
  public receivedConnectEvent(
372
- connectionMode: ConnectionMode,
373
404
  details: IConnectionDetails,
374
405
  ) {
406
+ this.connection = details;
407
+
375
408
  const oldState = this._connectionState;
376
409
  this._connectionState = ConnectionState.CatchingUp;
377
410
 
378
- const writeConnection = connectionMode === "write";
379
-
380
411
  // The following checks are wrong. They are only valid if user has write access to a file.
381
412
  // If user lost such access mid-session, user will not be able to get "write" connection.
413
+ //
414
+ // const writeConnection = details.mode === "write";
382
415
  // assert(!this.handler.shouldClientJoinWrite() || writeConnection,
383
416
  // 0x30a /* shouldClientJoinWrite should imply this is a writeConnection */);
384
417
  // assert(!this.waitingForLeaveOp || writeConnection,
@@ -395,16 +428,14 @@ class ConnectionStateHandler implements IConnectionStateHandler {
395
428
  // IMPORTANT: Report telemetry after we set _pendingClientId, but before transitioning to Connected state
396
429
  this.handler.connectionStateChanged(ConnectionState.CatchingUp, oldState);
397
430
 
398
- // For write connections, this pending clientId could be in the quorum already (i.e. join op already processed).
399
- // We are fetching ops from storage in parallel to connecting to Relay Service,
400
- // and given async processes, it's possible that we have already processed our own join message before
401
- // connection was fully established.
402
- // If protocol is not initialized yet, we expect it will process the join op after it's initialized.
403
- const waitingForJoinOp = writeConnection && !this.hasMember(this._pendingClientId);
404
-
405
- if (waitingForJoinOp) {
406
- // Previous client left, and we are waiting for our own join op. When it is processed we'll join the quorum
407
- // and attempt to transition to Connected state via receivedAddMemberEvent.
431
+ // Check if we need to wait for join op/signal, and if we need to wait for leave op from previous connection.
432
+ // Pending clientId could have joined already (i.e. join op/signal already processed):
433
+ // We are fetching ops from storage in parallel to connecting to Relay Service,
434
+ // and given async processes, it's possible that we have already processed our own join message before
435
+ // connection was fully established.
436
+ if (!this.hasMember(this._pendingClientId) && this.shouldWaitForJoinSignal()) {
437
+ // We are waiting for our own join op / signal. When it is processed
438
+ // we'll attempt to transition to Connected state via receivedAddMemberEvent() flow.
408
439
  this.startJoinOpTimer();
409
440
  } else if (!this.waitingForLeaveOp) {
410
441
  // We're not waiting for Join or Leave op (if read-only connection those don't even apply),
@@ -426,6 +457,10 @@ class ConnectionStateHandler implements IConnectionStateHandler {
426
457
 
427
458
  const oldState = this._connectionState;
428
459
  this._connectionState = value;
460
+
461
+ // This is the only place in code that deals with quorum. The rest works with audience
462
+ // The code below ensures that we do not send ops until we know that old "write" client's disconnect
463
+ // produced (and sequenced) leave op
429
464
  let client: ILocalSequencedClient | undefined;
430
465
  if (this._clientId !== undefined) {
431
466
  client = this.protocol?.quorum?.getMember(this._clientId);
@@ -476,23 +511,36 @@ class ConnectionStateHandler implements IConnectionStateHandler {
476
511
  // Helper method to switch between quorum and audience.
477
512
  // Old design was checking only quorum for "write" clients.
478
513
  // Latest change checks audience for all types of connections.
479
- protected get membership() {
480
- return this.protocol?.quorum;
514
+ protected get membership(): IMembership | undefined {
515
+ // We could always use audience here, and in practice it will probably be correct.
516
+ // (including case when this.readClientsWaitForJoinSignal === false).
517
+ // But only if it's superset of quorum, i.e. when filtered to "write" clients, they are always identical!
518
+ // It's safer to assume that we have bugs and engaging kill-bit switch should bring us back to well-known
519
+ // and tested state!
520
+ return this.readClientsWaitForJoinSignal ? this.protocol?.audience : this.protocol?.quorum;
481
521
  }
482
522
 
483
523
  public initProtocol(protocol: IProtocolHandler) {
484
524
  this.protocol = protocol;
485
525
 
486
- this.membership?.on("addMember", (clientId) => {
526
+ this.membership?.on("addMember", (clientId, details) => {
527
+ assert((details as IClient).mode === "read" || protocol.quorum.getMember(clientId) !== undefined,
528
+ 0x4b5 /* Audience is subset of quorum */);
487
529
  this.receivedAddMemberEvent(clientId);
488
530
  });
489
531
 
490
532
  this.membership?.on("removeMember", (clientId) => {
533
+ assert(protocol.quorum.getMember(clientId) === undefined, 0x4b6 /* Audience is subset of quorum */);
491
534
  this.receivedRemoveMemberEvent(clientId);
492
535
  });
493
536
 
494
- // Very unlikely race condition, but theoretically can happen - our new connection is already
495
- // summarized and we are loading from such summary.
537
+ /* There is a tiny tiny race possible, where these events happen in this order:
538
+ 1. A connection is established (no "cached" mode is used, so it happens in parallel / faster than other steps)
539
+ 2. Some other client produces a summary
540
+ 3. We get "lucky" and load from that summary as our initial snapshot
541
+ 4. ConnectionStateHandler.initProtocol is called, "self" is already in the quorum.
542
+ We could avoid this sequence (and delete test case for it) if we move connection lower in Container.load()
543
+ */
496
544
  if (this.hasMember(this.pendingClientId)) {
497
545
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
498
546
  this.receivedAddMemberEvent(this.pendingClientId!);
package/src/container.ts CHANGED
@@ -241,14 +241,14 @@ const getCodeProposal =
241
241
  * @param eventName - event name
242
242
  * @param action - functor to call and measure
243
243
  */
244
- async function ReportIfTooLong(
244
+ export async function ReportIfTooLong(
245
245
  logger: ITelemetryLogger,
246
246
  eventName: string,
247
247
  action: () => Promise<ITelemetryProperties>,
248
248
  ) {
249
249
  const event = PerformanceEvent.start(logger, { eventName });
250
250
  const props = await action();
251
- if (event.duration > 1000) {
251
+ if (event.duration > 200) {
252
252
  event.end(props);
253
253
  }
254
254
  }
@@ -646,13 +646,28 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
646
646
  shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
647
647
  maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
648
648
  logConnectionIssue: (eventName: string, details?: ITelemetryProperties) => {
649
+ const mode = this.connectionMode;
649
650
  // We get here when socket does not receive any ops on "write" connection, including
650
651
  // its own join op. Attempt recovery option.
651
652
  this._deltaManager.logConnectionIssue({
652
653
  eventName,
654
+ mode,
653
655
  duration: performance.now() - this.connectionTransitionTimes[ConnectionState.CatchingUp],
654
656
  ...(details === undefined ? {} : { details: JSON.stringify(details) }),
655
657
  });
658
+
659
+ // If this is "write" connection, it took too long to receive join op. But in most cases that's due
660
+ // to very slow op fetches and we will eventually get there.
661
+ // For "read" connections, we get here due to self join signal not arriving on time. We will need to
662
+ // better understand when and why it may happen.
663
+ // For now, attempt to recover by reconnecting. In future, maybe we can query relay service for
664
+ // current state of audience.
665
+ // Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
666
+ // to call this.applyForConnectedState("addMemberEvent") for "read" connections)
667
+ if (mode === "read") {
668
+ this.disconnect();
669
+ this.connect();
670
+ }
656
671
  },
657
672
  },
658
673
  this.deltaManager,
@@ -1510,8 +1525,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1510
1525
  deltaManager.inboundSignal.pause();
1511
1526
 
1512
1527
  deltaManager.on("connect", (details: IConnectionDetails, _opsBehind?: number) => {
1528
+ assert(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
1513
1529
  this.connectionStateHandler.receivedConnectEvent(
1514
- this.connectionMode,
1515
1530
  details,
1516
1531
  );
1517
1532
  });
@@ -1671,7 +1686,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1671
1686
  MessageType.Operation,
1672
1687
  message.contents,
1673
1688
  true, // batch
1674
- message.metadata);
1689
+ message.metadata,
1690
+ message.compression);
1675
1691
  }
1676
1692
  this._deltaManager.flush();
1677
1693
  return clientSequenceNumber;
@@ -1690,7 +1706,11 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1690
1706
  return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */);
1691
1707
  }
1692
1708
 
1693
- private submitMessage(type: MessageType, contents?: string, batch?: boolean, metadata?: any): number {
1709
+ private submitMessage(type: MessageType,
1710
+ contents?: string,
1711
+ batch?: boolean,
1712
+ metadata?: any,
1713
+ compression?: string): number {
1694
1714
  if (this.connectionState !== ConnectionState.Connected) {
1695
1715
  this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
1696
1716
  return -1;
@@ -1698,7 +1718,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1698
1718
 
1699
1719
  this.messageCountAfterDisconnection += 1;
1700
1720
  this.collabWindowTracker?.stopSequenceNumberUpdate();
1701
- return this._deltaManager.submit(type, contents, batch, metadata);
1721
+ return this._deltaManager.submit(type, contents, batch, metadata, compression);
1702
1722
  }
1703
1723
 
1704
1724
  private processRemoteMessage(message: ISequencedDocumentMessage) {
@@ -46,7 +46,7 @@ import {
46
46
  ISummaryContent,
47
47
  } from "@fluidframework/protocol-definitions";
48
48
  import { PerformanceEvent } from "@fluidframework/telemetry-utils";
49
- import { Container } from "./container";
49
+ import { Container, ReportIfTooLong } from "./container";
50
50
 
51
51
  const PackageNotFactoryError = "Code package does not implement IRuntimeFactory";
52
52
 
@@ -324,7 +324,13 @@ export class ContainerContext implements IContainerContext {
324
324
 
325
325
  private async instantiateRuntime(existing: boolean) {
326
326
  const runtimeFactory = await this.getRuntimeFactory();
327
- this._runtime = await runtimeFactory.instantiateRuntime(this, existing);
327
+ await ReportIfTooLong(
328
+ this.taggedLogger,
329
+ "instantiateRuntime",
330
+ async () => {
331
+ this._runtime = await runtimeFactory.instantiateRuntime(this, existing);
332
+ return {};
333
+ });
328
334
  }
329
335
 
330
336
  private attachListener() {
package/src/contracts.ts CHANGED
@@ -148,15 +148,23 @@ export interface IConnectionManagerFactoryArgs {
148
148
 
149
149
  /**
150
150
  * Called whenever ping/pong messages are roundtripped on connection.
151
+ *
152
+ * @deprecated No replacement API intended.
151
153
  */
152
154
  readonly pongHandler: (latency: number) => void;
153
155
 
154
156
  /**
155
157
  * Called whenever connection type changes from writable to read-only or vice versa.
158
+ *
159
+ * @remarks
160
+ *
156
161
  * Connection can be read-only if user has no edit permissions, or if container forced
157
162
  * connection to be read-only.
158
163
  * This should not be confused with "read" / "write"connection mode which is internal
159
164
  * optimization.
165
+ *
166
+ * @param readonly - Whether or not the container is now read-only.
167
+ * `undefined` indicates that user permissions are not yet known.
160
168
  */
161
169
  readonly readonlyChangeHandler: (readonly?: boolean) => void;
162
170
  }
@@ -118,8 +118,8 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
118
118
  // There are three numbers we track
119
119
  // * lastQueuedSequenceNumber is the last queued sequence number. If there are gaps in seq numbers, then this number
120
120
  // is not updated until we cover that gap, so it increases each time by 1.
121
- // * lastObservedSeqNumber is an estimation of last known sequence number for container in storage. It's initially
122
- // populated at web socket connection time (if storage provides that info) and is updated once ops shows up.
121
+ // * lastObservedSeqNumber is an estimation of last known sequence number for container in storage. It's initially
122
+ // populated at web socket connection time (if storage provides that info) and is updated once ops shows up.
123
123
  // It's never less than lastQueuedSequenceNumber
124
124
  // * lastProcessedSequenceNumber - last processed sequence number
125
125
  private lastQueuedSequenceNumber: number = 0;
@@ -210,12 +210,13 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
210
210
  public get readOnlyInfo() { return this.connectionManager.readOnlyInfo; }
211
211
  public get clientDetails() { return this.connectionManager.clientDetails; }
212
212
 
213
- public submit(type: MessageType, contents?: string, batch = false, metadata?: any) {
213
+ public submit(type: MessageType, contents?: string, batch = false, metadata?: any, compression?: string) {
214
214
  const messagePartial: Omit<IDocumentMessage, "clientSequenceNumber"> = {
215
215
  contents,
216
216
  metadata,
217
217
  referenceSequenceNumber: this.lastProcessedSequenceNumber,
218
218
  type,
219
+ compression,
219
220
  };
220
221
 
221
222
  if (!batch) {
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.0.0-internal.2.1.2";
9
+ export const pkgVersion = "2.0.0-internal.2.2.0";