@fluidframework/container-loader 2.0.0-internal.6.0.0 → 2.0.0-internal.6.1.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 (68) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/connectionManager.d.ts +3 -3
  3. package/dist/connectionManager.d.ts.map +1 -1
  4. package/dist/connectionManager.js +33 -24
  5. package/dist/connectionManager.js.map +1 -1
  6. package/dist/connectionStateHandler.d.ts +14 -14
  7. package/dist/connectionStateHandler.d.ts.map +1 -1
  8. package/dist/connectionStateHandler.js +17 -12
  9. package/dist/connectionStateHandler.js.map +1 -1
  10. package/dist/container.d.ts.map +1 -1
  11. package/dist/container.js +27 -39
  12. package/dist/container.js.map +1 -1
  13. package/dist/containerContext.d.ts +6 -1
  14. package/dist/containerContext.d.ts.map +1 -1
  15. package/dist/containerContext.js +8 -1
  16. package/dist/containerContext.js.map +1 -1
  17. package/dist/contracts.d.ts +11 -7
  18. package/dist/contracts.d.ts.map +1 -1
  19. package/dist/contracts.js.map +1 -1
  20. package/dist/deltaManager.d.ts +4 -4
  21. package/dist/deltaManager.d.ts.map +1 -1
  22. package/dist/deltaManager.js +5 -4
  23. package/dist/deltaManager.js.map +1 -1
  24. package/dist/packageVersion.d.ts +1 -1
  25. package/dist/packageVersion.js +1 -1
  26. package/dist/packageVersion.js.map +1 -1
  27. package/dist/protocol.d.ts +4 -2
  28. package/dist/protocol.d.ts.map +1 -1
  29. package/dist/protocol.js +23 -1
  30. package/dist/protocol.js.map +1 -1
  31. package/lib/connectionManager.d.ts +3 -3
  32. package/lib/connectionManager.d.ts.map +1 -1
  33. package/lib/connectionManager.js +33 -24
  34. package/lib/connectionManager.js.map +1 -1
  35. package/lib/connectionStateHandler.d.ts +14 -14
  36. package/lib/connectionStateHandler.d.ts.map +1 -1
  37. package/lib/connectionStateHandler.js +17 -12
  38. package/lib/connectionStateHandler.js.map +1 -1
  39. package/lib/container.d.ts.map +1 -1
  40. package/lib/container.js +28 -40
  41. package/lib/container.js.map +1 -1
  42. package/lib/containerContext.d.ts +6 -1
  43. package/lib/containerContext.d.ts.map +1 -1
  44. package/lib/containerContext.js +8 -1
  45. package/lib/containerContext.js.map +1 -1
  46. package/lib/contracts.d.ts +11 -7
  47. package/lib/contracts.d.ts.map +1 -1
  48. package/lib/contracts.js.map +1 -1
  49. package/lib/deltaManager.d.ts +4 -4
  50. package/lib/deltaManager.d.ts.map +1 -1
  51. package/lib/deltaManager.js +5 -4
  52. package/lib/deltaManager.js.map +1 -1
  53. package/lib/packageVersion.d.ts +1 -1
  54. package/lib/packageVersion.js +1 -1
  55. package/lib/packageVersion.js.map +1 -1
  56. package/lib/protocol.d.ts +4 -2
  57. package/lib/protocol.d.ts.map +1 -1
  58. package/lib/protocol.js +23 -1
  59. package/lib/protocol.js.map +1 -1
  60. package/package.json +14 -14
  61. package/src/connectionManager.ts +46 -31
  62. package/src/connectionStateHandler.ts +28 -36
  63. package/src/container.ts +47 -51
  64. package/src/containerContext.ts +8 -0
  65. package/src/contracts.ts +12 -6
  66. package/src/deltaManager.ts +19 -13
  67. package/src/packageVersion.ts +1 -1
  68. package/src/protocol.ts +33 -1
@@ -48,6 +48,7 @@ import {
48
48
  IConnectionManager,
49
49
  IConnectionManagerFactoryArgs,
50
50
  IConnectionDetailsInternal,
51
+ IConnectionStateChangeReason,
51
52
  } from "./contracts";
52
53
  import { DeltaQueue } from "./deltaQueue";
53
54
  import { SignalType } from "./protocol";
@@ -336,7 +337,7 @@ export class ConnectionManager implements IConnectionManager {
336
337
 
337
338
  private static detailsFromConnection(
338
339
  connection: IDocumentDeltaConnection,
339
- reason: string,
340
+ reason: IConnectionStateChangeReason,
340
341
  ): IConnectionDetailsInternal {
341
342
  return {
342
343
  claims: connection.claims,
@@ -389,7 +390,10 @@ export class ConnectionManager implements IConnectionManager {
389
390
 
390
391
  this._outbound.clear();
391
392
 
392
- const disconnectReason = "Closing DeltaManager";
393
+ const disconnectReason: IConnectionStateChangeReason = {
394
+ text: "Closing DeltaManager",
395
+ error,
396
+ };
393
397
 
394
398
  // This raises "disconnect" event if we have active connection.
395
399
  this.disconnectFromDeltaStream(disconnectReason);
@@ -406,7 +410,7 @@ export class ConnectionManager implements IConnectionManager {
406
410
  * Enables or disables automatic reconnecting.
407
411
  * Will throw an error if reconnectMode set to Never.
408
412
  */
409
- public setAutoReconnect(mode: ReconnectMode): void {
413
+ public setAutoReconnect(mode: ReconnectMode, reason: IConnectionStateChangeReason): void {
410
414
  assert(
411
415
  mode !== ReconnectMode.Never && this._reconnectMode !== ReconnectMode.Never,
412
416
  0x278 /* "API is not supported for non-connecting or closed container" */,
@@ -416,7 +420,7 @@ export class ConnectionManager implements IConnectionManager {
416
420
 
417
421
  if (mode !== ReconnectMode.Enabled) {
418
422
  // immediately disconnect - do not rely on service eventually dropping connection.
419
- this.disconnectFromDeltaStream("setAutoReconnect");
423
+ this.disconnectFromDeltaStream(reason);
420
424
  }
421
425
  }
422
426
 
@@ -463,12 +467,12 @@ export class ConnectionManager implements IConnectionManager {
463
467
  this.logger.sendErrorEvent({ eventName: "ForceReadonlyPendingChanged" });
464
468
  }
465
469
 
466
- reconnect = this.disconnectFromDeltaStream("Force readonly");
470
+ reconnect = this.disconnectFromDeltaStream({ text: "Force readonly" });
467
471
  }
468
472
  this.props.readonlyChangeHandler(this.readonly);
469
473
  if (reconnect) {
470
474
  // reconnect if we disconnected from before.
471
- this.triggerConnect("Force Readonly", "read");
475
+ this.triggerConnect({ text: "Force Readonly" }, "read");
472
476
  }
473
477
  }
474
478
  }
@@ -481,14 +485,17 @@ export class ConnectionManager implements IConnectionManager {
481
485
  }
482
486
  }
483
487
 
484
- public connect(reason: string, connectionMode?: ConnectionMode) {
485
- this.connectCore(reason, connectionMode).catch((error) => {
486
- const normalizedError = normalizeError(error, { props: fatalConnectErrorProp });
488
+ public connect(reason: IConnectionStateChangeReason, connectionMode?: ConnectionMode) {
489
+ this.connectCore(reason, connectionMode).catch((e) => {
490
+ const normalizedError = normalizeError(e, { props: fatalConnectErrorProp });
487
491
  this.props.closeHandler(normalizedError);
488
492
  });
489
493
  }
490
494
 
491
- private async connectCore(reason: string, connectionMode?: ConnectionMode): Promise<void> {
495
+ private async connectCore(
496
+ reason: IConnectionStateChangeReason,
497
+ connectionMode?: ConnectionMode,
498
+ ): Promise<void> {
492
499
  assert(!this._disposed, 0x26a /* "not closed" */);
493
500
 
494
501
  if (this.connection !== undefined) {
@@ -664,7 +671,7 @@ export class ConnectionManager implements IConnectionManager {
664
671
  * And report the error if it escapes for any reason.
665
672
  * @param args - The connection arguments
666
673
  */
667
- private triggerConnect(reason: string, connectionMode: ConnectionMode) {
674
+ private triggerConnect(reason: IConnectionStateChangeReason, connectionMode: ConnectionMode) {
668
675
  // reconnect() includes async awaits, and that causes potential race conditions
669
676
  // where we might already have a connection. If it were to happen, it's possible that we will connect
670
677
  // with different mode to `connectionMode`. Glancing through the caller chains, it looks like code should be
@@ -684,7 +691,7 @@ export class ConnectionManager implements IConnectionManager {
684
691
  * @param error - Error causing the disconnect if any.
685
692
  * @returns A boolean that indicates if there was an existing connection (or pending connection) to disconnect
686
693
  */
687
- private disconnectFromDeltaStream(reason: string, error?: IAnyDriverError): boolean {
694
+ private disconnectFromDeltaStream(reason: IConnectionStateChangeReason): boolean {
688
695
  this.pendingReconnect = false;
689
696
 
690
697
  if (this.connection === undefined) {
@@ -717,7 +724,7 @@ export class ConnectionManager implements IConnectionManager {
717
724
  this._outbound.clear();
718
725
  connection.dispose();
719
726
 
720
- this.props.disconnectHandler(reason, error);
727
+ this.props.disconnectHandler(reason);
721
728
 
722
729
  this._connectionVerboseProps = {};
723
730
 
@@ -727,7 +734,7 @@ export class ConnectionManager implements IConnectionManager {
727
734
  /**
728
735
  * Cancel in-progress connection attempt.
729
736
  */
730
- private cancelConnection(reason: string) {
737
+ private cancelConnection(reason: IConnectionStateChangeReason) {
731
738
  assert(
732
739
  this.pendingConnection !== undefined,
733
740
  0x345 /* this.pendingConnection is undefined when trying to cancel */,
@@ -735,7 +742,10 @@ export class ConnectionManager implements IConnectionManager {
735
742
  this.pendingConnection.abort();
736
743
  this.pendingConnection = undefined;
737
744
  this.logger.sendTelemetryEvent({ eventName: "ConnectionCancelReceived" });
738
- this.props.cancelConnectionHandler(`Cancel Pending Connection due to ${reason}`);
745
+ this.props.cancelConnectionHandler({
746
+ text: `Cancel Pending Connection due to ${reason.text}`,
747
+ error: reason.error,
748
+ });
739
749
  }
740
750
 
741
751
  /**
@@ -746,7 +756,7 @@ export class ConnectionManager implements IConnectionManager {
746
756
  private setupNewSuccessfulConnection(
747
757
  connection: IDocumentDeltaConnection,
748
758
  requestedMode: ConnectionMode,
749
- reason: string,
759
+ reason: IConnectionStateChangeReason,
750
760
  ) {
751
761
  // Old connection should have been cleaned up before establishing a new one
752
762
  assert(
@@ -789,7 +799,7 @@ export class ConnectionManager implements IConnectionManager {
789
799
 
790
800
  if (this._disposed) {
791
801
  // Raise proper events, Log telemetry event and close connection.
792
- this.disconnectFromDeltaStream("ConnectionManager already closed");
802
+ this.disconnectFromDeltaStream({ text: "ConnectionManager already closed" });
793
803
  return;
794
804
  }
795
805
 
@@ -894,7 +904,9 @@ export class ConnectionManager implements IConnectionManager {
894
904
  * @returns A promise that resolves when the connection is reestablished or we stop trying
895
905
  */
896
906
  private reconnectOnError(requestedMode: ConnectionMode, error: IAnyDriverError) {
897
- this.reconnect(requestedMode, error.message, error).catch(this.props.closeHandler);
907
+ this.reconnect(requestedMode, { text: error.message, error }).catch(
908
+ this.props.closeHandler,
909
+ );
898
910
  }
899
911
 
900
912
  /**
@@ -906,26 +918,25 @@ export class ConnectionManager implements IConnectionManager {
906
918
  */
907
919
  private async reconnect(
908
920
  requestedMode: ConnectionMode,
909
- disconnectMessage: string,
910
- error?: IAnyDriverError,
921
+ reason: IConnectionStateChangeReason<IAnyDriverError>,
911
922
  ) {
912
923
  // We quite often get protocol errors before / after observing nack/disconnect
913
924
  // we do not want to run through same sequence twice.
914
925
  // If we're already disconnected/disconnecting it's not appropriate to call this again.
915
926
  assert(this.connection !== undefined, 0x0eb /* "Missing connection for reconnect" */);
916
927
 
917
- this.disconnectFromDeltaStream(disconnectMessage, error);
928
+ this.disconnectFromDeltaStream(reason);
918
929
 
919
930
  // We will always trigger reconnect, even if canRetry is false.
920
931
  // Any truly fatal error state will result in container close upon attempted reconnect,
921
932
  // which is a preferable to closing abruptly when a live connection fails.
922
- if (error !== undefined && !error.canRetry) {
933
+ if (reason.error?.canRetry === false) {
923
934
  this.logger.sendTelemetryEvent(
924
935
  {
925
936
  eventName: "reconnectingDespiteFatalError",
926
937
  reconnectMode: this.reconnectMode,
927
938
  },
928
- error,
939
+ reason.error,
929
940
  );
930
941
  }
931
942
 
@@ -942,9 +953,9 @@ export class ConnectionManager implements IConnectionManager {
942
953
  }
943
954
 
944
955
  // If the error tells us to wait before retrying, then do so.
945
- const delayMs = getRetryDelayFromError(error);
946
- if (error !== undefined && delayMs !== undefined) {
947
- this.props.reconnectionDelayHandler(delayMs, error);
956
+ const delayMs = getRetryDelayFromError(reason.error);
957
+ if (reason.error !== undefined && delayMs !== undefined) {
958
+ this.props.reconnectionDelayHandler(delayMs, reason.error);
948
959
  await new Promise<void>((resolve) => {
949
960
  setTimeout(resolve, delayMs);
950
961
  });
@@ -956,9 +967,13 @@ export class ConnectionManager implements IConnectionManager {
956
967
  await waitForOnline();
957
968
 
958
969
  this.triggerConnect(
959
- error !== undefined
960
- ? "Reconnecting due to Error"
961
- : `Reconnecting due to: ${disconnectMessage}`,
970
+ {
971
+ text:
972
+ reason.error !== undefined
973
+ ? "Reconnecting due to Error"
974
+ : `Reconnecting due to: ${reason.text}`,
975
+ error: reason.error,
976
+ },
962
977
  requestedMode,
963
978
  );
964
979
  }
@@ -1029,7 +1044,7 @@ export class ConnectionManager implements IConnectionManager {
1029
1044
  // still valid?
1030
1045
  await this.reconnect(
1031
1046
  "write", // connectionMode
1032
- "Switch to write", // message
1047
+ { text: "Switch to write" }, // message
1033
1048
  );
1034
1049
  }
1035
1050
  })
@@ -1083,7 +1098,7 @@ export class ConnectionManager implements IConnectionManager {
1083
1098
  // Note - this may close container!
1084
1099
  this.reconnect(
1085
1100
  "read", // connectionMode
1086
- "Switch to read", // message
1101
+ { text: "Switch to read" }, // message
1087
1102
  ).catch((error) => {
1088
1103
  this.logger.sendErrorEvent({ eventName: "SwitchToReadConnection" }, error);
1089
1104
  });
@@ -6,16 +6,16 @@
6
6
  import { ITelemetryProperties, TelemetryEventCategory } from "@fluidframework/core-interfaces";
7
7
  import { assert, Timer } from "@fluidframework/common-utils";
8
8
  import { IDeltaManager } from "@fluidframework/container-definitions";
9
- import { IAnyDriverError } from "@fluidframework/driver-definitions";
10
9
  import { ISequencedClient, IClient } from "@fluidframework/protocol-definitions";
11
10
  import {
12
11
  ITelemetryLoggerExt,
13
12
  PerformanceEvent,
14
13
  loggerToMonitoringContext,
15
14
  } from "@fluidframework/telemetry-utils";
15
+ import { IAnyDriverError } from "@fluidframework/driver-definitions";
16
16
  import { CatchUpMonitor, ICatchUpMonitor } from "./catchUpMonitor";
17
17
  import { ConnectionState } from "./connectionState";
18
- import { IConnectionDetailsInternal } from "./contracts";
18
+ import { IConnectionDetailsInternal, IConnectionStateChangeReason } from "./contracts";
19
19
  import { IProtocolHandler } from "./protocol";
20
20
 
21
21
  // Based on recent data, it looks like majority of cases where we get stuck are due to really slow or
@@ -33,8 +33,7 @@ export interface IConnectionStateHandlerInputs {
33
33
  connectionStateChanged: (
34
34
  value: ConnectionState,
35
35
  oldState: ConnectionState,
36
- reason?: string | undefined,
37
- error?: IAnyDriverError,
36
+ reason?: IConnectionStateChangeReason,
38
37
  ) => void;
39
38
  /** Whether to expect the client to join in write mode on next connection */
40
39
  shouldClientJoinWrite: () => boolean;
@@ -61,14 +60,14 @@ export interface IConnectionStateHandler {
61
60
  dispose(): void;
62
61
  initProtocol(protocol: IProtocolHandler): void;
63
62
  receivedConnectEvent(details: IConnectionDetailsInternal): void;
64
- receivedDisconnectEvent(reason: string, error?: IAnyDriverError): void;
65
- establishingConnection(reason: string): void;
63
+ receivedDisconnectEvent(reason: IConnectionStateChangeReason): void;
64
+ establishingConnection(reason: IConnectionStateChangeReason): void;
66
65
  /**
67
66
  * Switches state to disconnected when we are still establishing connection during container.load(),
68
67
  * container connect() or reconnect and the container gets closed or disposed or disconnect happens.
69
68
  * @param reason - reason for cancelling the connection.
70
69
  */
71
- cancelEstablishingConnection(reason: string): void;
70
+ cancelEstablishingConnection(reason: IConnectionStateChangeReason): void;
72
71
  }
73
72
 
74
73
  export function createConnectionStateHandler(
@@ -150,15 +149,15 @@ class ConnectionStateHandlerPassThrough
150
149
  public initProtocol(protocol: IProtocolHandler) {
151
150
  return this.pimpl.initProtocol(protocol);
152
151
  }
153
- public receivedDisconnectEvent(reason: string, error?: IAnyDriverError) {
154
- return this.pimpl.receivedDisconnectEvent(reason, error);
152
+ public receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>) {
153
+ return this.pimpl.receivedDisconnectEvent(reason);
155
154
  }
156
155
 
157
- public establishingConnection(reason: string) {
156
+ public establishingConnection(reason: IConnectionStateChangeReason) {
158
157
  return this.pimpl.establishingConnection(reason);
159
158
  }
160
159
 
161
- public cancelEstablishingConnection(reason: string) {
160
+ public cancelEstablishingConnection(reason: IConnectionStateChangeReason) {
162
161
  return this.pimpl.cancelEstablishingConnection(reason);
163
162
  }
164
163
 
@@ -176,10 +175,9 @@ class ConnectionStateHandlerPassThrough
176
175
  public connectionStateChanged(
177
176
  value: ConnectionState,
178
177
  oldState: ConnectionState,
179
- reason?: string | undefined,
180
- error?: IAnyDriverError,
178
+ reason?: IConnectionStateChangeReason,
181
179
  ) {
182
- return this.inputs.connectionStateChanged(value, oldState, reason, error);
180
+ return this.inputs.connectionStateChanged(value, oldState, reason);
183
181
  }
184
182
  public shouldClientJoinWrite() {
185
183
  return this.inputs.shouldClientJoinWrite();
@@ -223,8 +221,7 @@ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
223
221
  public connectionStateChanged(
224
222
  value: ConnectionState,
225
223
  oldState: ConnectionState,
226
- reason?: string | undefined,
227
- error?: IAnyDriverError,
224
+ reason?: IConnectionStateChangeReason<IAnyDriverError>,
228
225
  ) {
229
226
  switch (value) {
230
227
  case ConnectionState.Connected:
@@ -268,7 +265,7 @@ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
268
265
  default:
269
266
  }
270
267
  this._connectionState = value;
271
- this.inputs.connectionStateChanged(value, oldState, reason, error);
268
+ this.inputs.connectionStateChanged(value, oldState, reason);
272
269
  }
273
270
 
274
271
  private readonly transitionToConnectedState = () => {
@@ -277,11 +274,9 @@ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
277
274
  assert(state === ConnectionState.Connected, 0x3e5 /* invariant broken */);
278
275
  assert(this._connectionState === ConnectionState.CatchingUp, 0x3e6 /* invariant broken */);
279
276
  this._connectionState = ConnectionState.Connected;
280
- this.inputs.connectionStateChanged(
281
- ConnectionState.Connected,
282
- ConnectionState.CatchingUp,
283
- "caught up",
284
- );
277
+ this.inputs.connectionStateChanged(ConnectionState.Connected, ConnectionState.CatchingUp, {
278
+ text: "caught up",
279
+ });
285
280
  };
286
281
  }
287
282
 
@@ -495,12 +490,12 @@ class ConnectionStateHandler implements IConnectionStateHandler {
495
490
  }
496
491
  }
497
492
 
498
- public receivedDisconnectEvent(reason: string, error?: IAnyDriverError) {
493
+ public receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>) {
499
494
  this.connection = undefined;
500
- this.setConnectionState(ConnectionState.Disconnected, reason, error);
495
+ this.setConnectionState(ConnectionState.Disconnected, reason);
501
496
  }
502
497
 
503
- public cancelEstablishingConnection(reason: string) {
498
+ public cancelEstablishingConnection(reason: IConnectionStateChangeReason) {
504
499
  assert(
505
500
  this._connectionState === ConnectionState.EstablishingConnection,
506
501
  0x6d3 /* Connection state should be EstablishingConnection */,
@@ -511,14 +506,13 @@ class ConnectionStateHandler implements IConnectionStateHandler {
511
506
  this.handler.connectionStateChanged(ConnectionState.Disconnected, oldState, reason);
512
507
  }
513
508
 
514
- public establishingConnection(reason: string) {
509
+ public establishingConnection(reason: IConnectionStateChangeReason) {
515
510
  const oldState = this._connectionState;
516
511
  this._connectionState = ConnectionState.EstablishingConnection;
517
- this.handler.connectionStateChanged(
518
- ConnectionState.EstablishingConnection,
519
- oldState,
520
- `Establishing Connection due to ${reason}`,
521
- );
512
+ this.handler.connectionStateChanged(ConnectionState.EstablishingConnection, oldState, {
513
+ text: `Establishing Connection due to ${reason.text}`,
514
+ error: reason.error,
515
+ });
522
516
  }
523
517
 
524
518
  private shouldWaitForJoinSignal() {
@@ -583,14 +577,12 @@ class ConnectionStateHandler implements IConnectionStateHandler {
583
577
 
584
578
  private setConnectionState(
585
579
  value: ConnectionState.Disconnected,
586
- reason: string,
587
- error?: IAnyDriverError,
580
+ reason: IConnectionStateChangeReason,
588
581
  ): void;
589
582
  private setConnectionState(value: ConnectionState.Connected): void;
590
583
  private setConnectionState(
591
584
  value: ConnectionState.Disconnected | ConnectionState.Connected,
592
- reason?: string,
593
- error?: IAnyDriverError,
585
+ reason?: IConnectionStateChangeReason,
594
586
  ): void {
595
587
  if (this.connectionState === value) {
596
588
  // Already in the desired state - exit early
@@ -651,7 +643,7 @@ class ConnectionStateHandler implements IConnectionStateHandler {
651
643
  }
652
644
 
653
645
  // Report transition before we propagate event across layers
654
- this.handler.connectionStateChanged(this._connectionState, oldState, reason, error);
646
+ this.handler.connectionStateChanged(this._connectionState, oldState, reason);
655
647
  }
656
648
 
657
649
  // Helper method to switch between quorum and audience.
package/src/container.ts CHANGED
@@ -40,17 +40,16 @@ import {
40
40
  IProvideFluidCodeDetailsComparer,
41
41
  IFluidCodeDetailsComparer,
42
42
  IRuntime,
43
- isFluidCodeDetails,
44
- IThrottlingWarning,
45
43
  ReadOnlyInfo,
44
+ isFluidCodeDetails,
46
45
  } from "@fluidframework/container-definitions";
47
46
  import { GenericError, UsageError } from "@fluidframework/container-utils";
48
47
  import {
49
- IAnyDriverError,
50
48
  IDocumentService,
51
49
  IDocumentServiceFactory,
52
50
  IDocumentStorageService,
53
51
  IResolvedUrl,
52
+ IThrottlingWarning,
54
53
  IUrlResolver,
55
54
  } from "@fluidframework/driver-definitions";
56
55
  import {
@@ -60,7 +59,6 @@ import {
60
59
  runWithRetry,
61
60
  isCombinedAppAndProtocolSummary,
62
61
  MessageType2,
63
- canBeCoalescedByService,
64
62
  } from "@fluidframework/driver-utils";
65
63
  import { IQuorumSnapshot } from "@fluidframework/protocol-base";
66
64
  import {
@@ -102,6 +100,7 @@ import {
102
100
  IConnectionManagerFactoryArgs,
103
101
  getPackageName,
104
102
  IConnectionDetailsInternal,
103
+ IConnectionStateChangeReason,
105
104
  } from "./contracts";
106
105
  import { DeltaManager, IConnectionArgs } from "./deltaManager";
107
106
  import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader";
@@ -761,7 +760,19 @@ export class Container
761
760
  this.scope = scope;
762
761
  this.detachedBlobStorage = detachedBlobStorage;
763
762
  this.protocolHandlerBuilder =
764
- protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
763
+ protocolHandlerBuilder ??
764
+ ((
765
+ attributes: IDocumentAttributes,
766
+ quorumSnapshot: IQuorumSnapshot,
767
+ sendProposal: (key: string, value: any) => number,
768
+ ) =>
769
+ new ProtocolHandler(
770
+ attributes,
771
+ quorumSnapshot,
772
+ sendProposal,
773
+ new Audience(),
774
+ (clientId: string) => this.clientsWhoShouldHaveLeft.has(clientId),
775
+ ));
765
776
 
766
777
  // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
767
778
  this.clone = async (
@@ -827,11 +838,11 @@ export class Container
827
838
  this.connectionStateHandler = createConnectionStateHandler(
828
839
  {
829
840
  logger: this.mc.logger,
830
- connectionStateChanged: (value, oldState, reason, error) => {
841
+ connectionStateChanged: (value, oldState, reason) => {
831
842
  if (value === ConnectionState.Connected) {
832
843
  this._clientId = this.connectionStateHandler.pendingClientId;
833
844
  }
834
- this.logConnectionStateChangeTelemetry(value, oldState, reason, error);
845
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
835
846
  if (this._lifecycleState === "loaded") {
836
847
  this.propagateConnectionState(
837
848
  false /* initial transition */,
@@ -873,8 +884,9 @@ export class Container
873
884
  // Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
874
885
  // to call this.applyForConnectedState("addMemberEvent") for "read" connections)
875
886
  if (mode === "read") {
876
- this.disconnect();
877
- this.connect();
887
+ const reason = { text: "NoJoinSignal" };
888
+ this.disconnectInternal(reason);
889
+ this.connectInternal({ reason, fetchOpsFromStorage: false });
878
890
  }
879
891
  },
880
892
  clientShouldHaveLeft: (clientId: string) => {
@@ -1071,7 +1083,7 @@ export class Container
1071
1083
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
1072
1084
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
1073
1085
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
1074
- this.disconnect(); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
1086
+ this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
1075
1087
  const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
1076
1088
  this.close();
1077
1089
  return pendingState;
@@ -1267,7 +1279,7 @@ export class Container
1267
1279
  if (!this.closed) {
1268
1280
  this.resumeInternal({
1269
1281
  fetchOpsFromStorage: false,
1270
- reason: "createDetached",
1282
+ reason: { text: "createDetached" },
1271
1283
  });
1272
1284
  }
1273
1285
  } catch (error) {
@@ -1291,7 +1303,7 @@ export class Container
1291
1303
  );
1292
1304
  }
1293
1305
 
1294
- private setAutoReconnectInternal(mode: ReconnectMode) {
1306
+ private setAutoReconnectInternal(mode: ReconnectMode, reason: IConnectionStateChangeReason) {
1295
1307
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
1296
1308
 
1297
1309
  if (currentMode === mode) {
@@ -1310,7 +1322,7 @@ export class Container
1310
1322
  duration,
1311
1323
  });
1312
1324
 
1313
- this._deltaManager.connectionManager.setAutoReconnect(mode);
1325
+ this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
1314
1326
  }
1315
1327
 
1316
1328
  public connect() {
@@ -1322,7 +1334,10 @@ export class Container
1322
1334
  // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
1323
1335
  // If there is gap, we will learn about it once connected, but the gap should be small (if any),
1324
1336
  // assuming that connect() is called quickly after initial container boot.
1325
- this.connectInternal({ reason: "DocumentConnect", fetchOpsFromStorage: false });
1337
+ this.connectInternal({
1338
+ reason: { text: "DocumentConnect" },
1339
+ fetchOpsFromStorage: false,
1340
+ });
1326
1341
  }
1327
1342
  }
1328
1343
 
@@ -1338,23 +1353,23 @@ export class Container
1338
1353
 
1339
1354
  // Set Auto Reconnect Mode
1340
1355
  const mode = ReconnectMode.Enabled;
1341
- this.setAutoReconnectInternal(mode);
1356
+ this.setAutoReconnectInternal(mode, args.reason);
1342
1357
  }
1343
1358
 
1344
1359
  public disconnect() {
1345
1360
  if (this.closed) {
1346
1361
  throw new UsageError(`The Container is closed and cannot be disconnected`);
1347
1362
  } else {
1348
- this.disconnectInternal();
1363
+ this.disconnectInternal({ text: "DocumentDisconnect" });
1349
1364
  }
1350
1365
  }
1351
1366
 
1352
- private disconnectInternal() {
1367
+ private disconnectInternal(reason: IConnectionStateChangeReason) {
1353
1368
  assert(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
1354
1369
 
1355
1370
  // Set Auto Reconnect Mode
1356
1371
  const mode = ReconnectMode.Disabled;
1357
- this.setAutoReconnectInternal(mode);
1372
+ this.setAutoReconnectInternal(mode, reason);
1358
1373
  }
1359
1374
 
1360
1375
  private resumeInternal(args: IConnectionArgs) {
@@ -1506,7 +1521,7 @@ export class Container
1506
1521
  // A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
1507
1522
  // B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
1508
1523
  const connectionArgs: IConnectionArgs = {
1509
- reason: "DocumentOpen",
1524
+ reason: { text: "DocumentOpen" },
1510
1525
  mode: "write",
1511
1526
  fetchOpsFromStorage: false,
1512
1527
  };
@@ -2009,18 +2024,18 @@ export class Container
2009
2024
  this.connectionStateHandler.receivedConnectEvent(details);
2010
2025
  });
2011
2026
 
2012
- deltaManager.on("establishingConnection", (reason: string) => {
2027
+ deltaManager.on("establishingConnection", (reason: IConnectionStateChangeReason) => {
2013
2028
  this.connectionStateHandler.establishingConnection(reason);
2014
2029
  });
2015
2030
 
2016
- deltaManager.on("cancelEstablishingConnection", (reason: string) => {
2031
+ deltaManager.on("cancelEstablishingConnection", (reason: IConnectionStateChangeReason) => {
2017
2032
  this.connectionStateHandler.cancelEstablishingConnection(reason);
2018
2033
  });
2019
2034
 
2020
- deltaManager.on("disconnect", (reason: string, error?: IAnyDriverError) => {
2035
+ deltaManager.on("disconnect", (reason: IConnectionStateChangeReason) => {
2021
2036
  this.noopHeuristic?.notifyDisconnect();
2022
2037
  if (!this.closed) {
2023
- this.connectionStateHandler.receivedDisconnectEvent(reason, error);
2038
+ this.connectionStateHandler.receivedDisconnectEvent(reason);
2024
2039
  }
2025
2040
  });
2026
2041
 
@@ -2073,8 +2088,7 @@ export class Container
2073
2088
  private logConnectionStateChangeTelemetry(
2074
2089
  value: ConnectionState,
2075
2090
  oldState: ConnectionState,
2076
- reason?: string,
2077
- error?: IAnyDriverError,
2091
+ reason?: IConnectionStateChangeReason,
2078
2092
  ) {
2079
2093
  // Log actual event
2080
2094
  const time = performance.now();
@@ -2113,7 +2127,7 @@ export class Container
2113
2127
  from: ConnectionState[oldState],
2114
2128
  duration,
2115
2129
  durationFromDisconnected,
2116
- reason,
2130
+ reason: reason?.text,
2117
2131
  connectionInitiationReason,
2118
2132
  pendingClientId: this.connectionStateHandler.pendingClientId,
2119
2133
  clientId: this.clientId,
@@ -2129,7 +2143,7 @@ export class Container
2129
2143
  isDirty: this.isDirty,
2130
2144
  ...this._deltaManager.connectionProps,
2131
2145
  },
2132
- error,
2146
+ reason?.error,
2133
2147
  );
2134
2148
 
2135
2149
  if (value === ConnectionState.Connected) {
@@ -2137,7 +2151,10 @@ export class Container
2137
2151
  }
2138
2152
  }
2139
2153
 
2140
- private propagateConnectionState(initialTransition: boolean, disconnectedReason?: string) {
2154
+ private propagateConnectionState(
2155
+ initialTransition: boolean,
2156
+ disconnectedReason?: IConnectionStateChangeReason,
2157
+ ) {
2141
2158
  // When container loaded, we want to propagate initial connection state.
2142
2159
  // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
2143
2160
  // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
@@ -2154,7 +2171,7 @@ export class Container
2154
2171
 
2155
2172
  this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
2156
2173
  this.protocolHandler.setConnectionState(state, this.clientId);
2157
- raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
2174
+ raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
2158
2175
  }
2159
2176
 
2160
2177
  // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
@@ -2247,28 +2264,6 @@ export class Container
2247
2264
  }
2248
2265
  const local = this.clientId === message.clientId;
2249
2266
 
2250
- // Check and report if we're getting messages from a clientId that we previously
2251
- // flagged should have left, or from a client that's not in the quorum but should be
2252
- if (message.clientId != null) {
2253
- const client = this.protocolHandler.quorum.getMember(message.clientId);
2254
-
2255
- if (client === undefined && message.type !== MessageType.ClientJoin) {
2256
- // pre-0.58 error message: messageClientIdMissingFromQuorum
2257
- throw new Error("Remote message's clientId is missing from the quorum");
2258
- }
2259
-
2260
- // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
2261
- // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
2262
- // document we don't need to blow up aggressively.
2263
- if (
2264
- this.clientsWhoShouldHaveLeft.has(message.clientId) &&
2265
- !canBeCoalescedByService(message)
2266
- ) {
2267
- // pre-0.58 error message: messageClientIdShouldHaveLeft
2268
- throw new Error("Remote message's clientId already should have left");
2269
- }
2270
- }
2271
-
2272
2267
  // Allow the protocol handler to process the message
2273
2268
  const result = this.protocolHandler.processMessage(message, local);
2274
2269
 
@@ -2413,6 +2408,7 @@ export class Container
2413
2408
  (error?: ICriticalContainerError) => this.close(error),
2414
2409
  this.updateDirtyContainerState,
2415
2410
  this.getAbsoluteUrl,
2411
+ () => this.resolvedUrl?.id,
2416
2412
  () => this.clientId,
2417
2413
  () => this.attachState,
2418
2414
  () => this.connected,