@fluidframework/container-loader 2.0.0-internal.5.3.4 → 2.0.0-internal.5.4.2

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 (78) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +6 -3
  3. package/dist/audience.d.ts +1 -0
  4. package/dist/audience.d.ts.map +1 -1
  5. package/dist/audience.js +3 -1
  6. package/dist/audience.js.map +1 -1
  7. package/dist/connectionManager.d.ts.map +1 -1
  8. package/dist/connectionManager.js +6 -11
  9. package/dist/connectionManager.js.map +1 -1
  10. package/dist/container.d.ts +2 -3
  11. package/dist/container.d.ts.map +1 -1
  12. package/dist/container.js +58 -93
  13. package/dist/container.js.map +1 -1
  14. package/dist/debugLogger.d.ts +30 -0
  15. package/dist/debugLogger.d.ts.map +1 -0
  16. package/dist/debugLogger.js +96 -0
  17. package/dist/debugLogger.js.map +1 -0
  18. package/dist/deltaManager.d.ts +0 -1
  19. package/dist/deltaManager.d.ts.map +1 -1
  20. package/dist/deltaManager.js +17 -9
  21. package/dist/deltaManager.js.map +1 -1
  22. package/dist/loader.d.ts.map +1 -1
  23. package/dist/loader.js +16 -5
  24. package/dist/loader.js.map +1 -1
  25. package/dist/packageVersion.d.ts +1 -1
  26. package/dist/packageVersion.js +1 -1
  27. package/dist/packageVersion.js.map +1 -1
  28. package/dist/protocol.d.ts +4 -2
  29. package/dist/protocol.d.ts.map +1 -1
  30. package/dist/protocol.js +23 -1
  31. package/dist/protocol.js.map +1 -1
  32. package/dist/quorum.d.ts +4 -1
  33. package/dist/quorum.d.ts.map +1 -1
  34. package/dist/quorum.js +1 -13
  35. package/dist/quorum.js.map +1 -1
  36. package/lib/audience.d.ts +1 -0
  37. package/lib/audience.d.ts.map +1 -1
  38. package/lib/audience.js +3 -1
  39. package/lib/audience.js.map +1 -1
  40. package/lib/connectionManager.d.ts.map +1 -1
  41. package/lib/connectionManager.js +7 -9
  42. package/lib/connectionManager.js.map +1 -1
  43. package/lib/container.d.ts +2 -3
  44. package/lib/container.d.ts.map +1 -1
  45. package/lib/container.js +61 -96
  46. package/lib/container.js.map +1 -1
  47. package/lib/debugLogger.d.ts +30 -0
  48. package/lib/debugLogger.d.ts.map +1 -0
  49. package/lib/debugLogger.js +92 -0
  50. package/lib/debugLogger.js.map +1 -0
  51. package/lib/deltaManager.d.ts +0 -1
  52. package/lib/deltaManager.d.ts.map +1 -1
  53. package/lib/deltaManager.js +15 -4
  54. package/lib/deltaManager.js.map +1 -1
  55. package/lib/loader.d.ts.map +1 -1
  56. package/lib/loader.js +16 -5
  57. package/lib/loader.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/lib/protocol.d.ts +4 -2
  62. package/lib/protocol.d.ts.map +1 -1
  63. package/lib/protocol.js +23 -1
  64. package/lib/protocol.js.map +1 -1
  65. package/lib/quorum.d.ts +4 -1
  66. package/lib/quorum.d.ts.map +1 -1
  67. package/lib/quorum.js +0 -11
  68. package/lib/quorum.js.map +1 -1
  69. package/package.json +12 -12
  70. package/src/audience.ts +6 -0
  71. package/src/connectionManager.ts +7 -12
  72. package/src/container.ts +78 -116
  73. package/src/debugLogger.ts +113 -0
  74. package/src/deltaManager.ts +28 -4
  75. package/src/loader.ts +16 -7
  76. package/src/packageVersion.ts +1 -1
  77. package/src/protocol.ts +33 -1
  78. package/src/quorum.ts +0 -10
package/src/container.ts CHANGED
@@ -62,7 +62,6 @@ import {
62
62
  runWithRetry,
63
63
  isCombinedAppAndProtocolSummary,
64
64
  MessageType2,
65
- canBeCoalescedByService,
66
65
  } from "@fluidframework/driver-utils";
67
66
  import { IQuorumSnapshot } from "@fluidframework/protocol-base";
68
67
  import {
@@ -85,17 +84,17 @@ import {
85
84
  SummaryType,
86
85
  } from "@fluidframework/protocol-definitions";
87
86
  import {
88
- ChildLogger,
87
+ createChildLogger,
89
88
  EventEmitterWithErrorHandling,
90
89
  PerformanceEvent,
91
90
  raiseConnectedEvent,
92
- TelemetryLogger,
93
91
  connectedEventName,
94
92
  normalizeError,
95
93
  MonitoringContext,
96
- loggerToMonitoringContext,
94
+ createChildMonitoringContext,
97
95
  wrapError,
98
96
  ITelemetryLoggerExt,
97
+ formatTick,
99
98
  } from "@fluidframework/telemetry-utils";
100
99
  import { Audience } from "./audience";
101
100
  import { ContainerContext } from "./containerContext";
@@ -111,7 +110,7 @@ import {
111
110
  } from "./containerStorageAdapter";
112
111
  import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
113
112
  import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
114
- import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues } from "./quorum";
113
+ import { initQuorumValuesFromCodeDetails } from "./quorum";
115
114
  import { NoopHeuristic } from "./noopHeuristic";
116
115
  import { ConnectionManager } from "./connectionManager";
117
116
  import { ConnectionState } from "./connectionState";
@@ -473,7 +472,7 @@ export class Container
473
472
  private readonly codeLoader: ICodeDetailsLoader;
474
473
  private readonly options: ILoaderOptions;
475
474
  private readonly scope: FluidObject;
476
- private readonly subLogger: TelemetryLogger;
475
+ private readonly subLogger: ITelemetryLoggerExt;
477
476
  private readonly detachedBlobStorage: IDetachedBlobStorage | undefined;
478
477
  private readonly protocolHandlerBuilder: ProtocolHandlerBuilder;
479
478
 
@@ -523,13 +522,14 @@ export class Container
523
522
 
524
523
  public get closed(): boolean {
525
524
  return (
526
- this._lifecycleState === "closing" ||
527
- this._lifecycleState === "closed" ||
528
- this._lifecycleState === "disposing" ||
529
- this._lifecycleState === "disposed"
525
+ this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed
530
526
  );
531
527
  }
532
528
 
529
+ public get disposed(): boolean {
530
+ return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
531
+ }
532
+
533
533
  private _attachState = AttachState.Detached;
534
534
 
535
535
  private readonly storageAdapter: ContainerStorageAdapter;
@@ -556,7 +556,6 @@ export class Container
556
556
  private inboundQueuePausedFromInit = true;
557
557
  private firstConnection = true;
558
558
  private readonly connectionTransitionTimes: number[] = [];
559
- private messageCountAfterDisconnection: number = 0;
560
559
  private _loadedFromVersion: IVersion | undefined;
561
560
  private attachStarted = false;
562
561
  private _dirtyContainer = false;
@@ -748,7 +747,19 @@ export class Container
748
747
  this.scope = scope;
749
748
  this.detachedBlobStorage = detachedBlobStorage;
750
749
  this.protocolHandlerBuilder =
751
- protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
750
+ protocolHandlerBuilder ??
751
+ ((
752
+ attributes: IDocumentAttributes,
753
+ quorumSnapshot: IQuorumSnapshot,
754
+ sendProposal: (key: string, value: any) => number,
755
+ ) =>
756
+ new ProtocolHandler(
757
+ attributes,
758
+ quorumSnapshot,
759
+ sendProposal,
760
+ new Audience(),
761
+ (clientId: string) => this.clientsWhoShouldHaveLeft.has(clientId),
762
+ ));
752
763
 
753
764
  // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
754
765
  this.clone = async (
@@ -769,42 +780,45 @@ export class Container
769
780
  }`;
770
781
  // Need to use the property getter for docId because for detached flow we don't have the docId initially.
771
782
  // We assign the id later so property getter is used.
772
- this.subLogger = ChildLogger.create(subLogger, undefined, {
773
- all: {
774
- clientType, // Differentiating summarizer container from main container
775
- containerId: uuid(),
776
- docId: () => this.resolvedUrl?.id,
777
- containerAttachState: () => this._attachState,
778
- containerLifecycleState: () => this._lifecycleState,
779
- containerConnectionState: () => ConnectionState[this.connectionState],
780
- serializedContainer: pendingLocalState !== undefined,
781
- },
782
- // we need to be judicious with our logging here to avoid generating too much data
783
- // all data logged here should be broadly applicable, and not specific to a
784
- // specific error or class of errors
785
- error: {
786
- // load information to associate errors with the specific load point
787
- dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
788
- dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
789
- dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
790
- containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
791
- containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
792
- // message information to associate errors with the specific execution state
793
- // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
794
- dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
795
- dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
796
- dmLastMsqSeqClientId: () =>
797
- this.deltaManager?.lastMessage?.clientId === null
798
- ? "null"
799
- : this.deltaManager?.lastMessage?.clientId,
800
- dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
801
- connectionStateDuration: () =>
802
- performance.now() - this.connectionTransitionTimes[this.connectionState],
783
+ this.subLogger = createChildLogger({
784
+ logger: subLogger,
785
+ properties: {
786
+ all: {
787
+ clientType, // Differentiating summarizer container from main container
788
+ containerId: uuid(),
789
+ docId: () => this.resolvedUrl?.id,
790
+ containerAttachState: () => this._attachState,
791
+ containerLifecycleState: () => this._lifecycleState,
792
+ containerConnectionState: () => ConnectionState[this.connectionState],
793
+ serializedContainer: pendingLocalState !== undefined,
794
+ },
795
+ // we need to be judicious with our logging here to avoid generating too much data
796
+ // all data logged here should be broadly applicable, and not specific to a
797
+ // specific error or class of errors
798
+ error: {
799
+ // load information to associate errors with the specific load point
800
+ dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
801
+ dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
802
+ dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
803
+ containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
804
+ containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
805
+ // message information to associate errors with the specific execution state
806
+ // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
807
+ dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
808
+ dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
809
+ dmLastMsqSeqClientId: () =>
810
+ this.deltaManager?.lastMessage?.clientId === null
811
+ ? "null"
812
+ : this.deltaManager?.lastMessage?.clientId,
813
+ dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
814
+ connectionStateDuration: () =>
815
+ performance.now() - this.connectionTransitionTimes[this.connectionState],
816
+ },
803
817
  },
804
818
  });
805
819
 
806
820
  // Prefix all events in this file with container-loader
807
- this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
821
+ this.mc = createChildMonitoringContext({ logger: this.subLogger, namespace: "Container" });
808
822
 
809
823
  this._deltaManager = this.createDeltaManager();
810
824
 
@@ -900,8 +914,8 @@ export class Container
900
914
  document !== null &&
901
915
  typeof document.addEventListener === "function" &&
902
916
  document.addEventListener !== null;
903
- // keep track of last time page was visible for telemetry
904
- if (isDomAvailable) {
917
+ // keep track of last time page was visible for telemetry (on interactive clients only)
918
+ if (isDomAvailable && interactive) {
905
919
  this.lastVisible = document.hidden ? performance.now() : undefined;
906
920
  this.visibilityEventHandler = () => {
907
921
  if (document.hidden) {
@@ -1565,8 +1579,7 @@ export class Container
1565
1579
  await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
1566
1580
 
1567
1581
  const codeDetails = this.getCodeDetailsFromQuorum();
1568
- await this.instantiateContext(
1569
- true, // existing
1582
+ await this.instantiateRuntime(
1570
1583
  codeDetails,
1571
1584
  snapshot,
1572
1585
  pendingLocalState?.pendingRuntimeState,
@@ -1654,7 +1667,7 @@ export class Container
1654
1667
  };
1655
1668
  }
1656
1669
 
1657
- private async createDetached(source: IFluidCodeDetails) {
1670
+ private async createDetached(codeDetails: IFluidCodeDetails) {
1658
1671
  const attributes: IDocumentAttributes = {
1659
1672
  sequenceNumber: detachedContainerRefSeqNumber,
1660
1673
  term: OnlyValidTermValue,
@@ -1664,7 +1677,7 @@ export class Container
1664
1677
  await this.attachDeltaManagerOpHandler(attributes);
1665
1678
 
1666
1679
  // Need to just seed the source data in the code quorum. Quorum itself is empty
1667
- const qValues = initQuorumValuesFromCodeDetails(source);
1680
+ const qValues = initQuorumValuesFromCodeDetails(codeDetails);
1668
1681
  this.initializeProtocolState(
1669
1682
  attributes,
1670
1683
  {
@@ -1674,10 +1687,7 @@ export class Container
1674
1687
  }, // IQuorumSnapShot
1675
1688
  );
1676
1689
 
1677
- // The load context - given we seeded the quorum - will be great
1678
- await this.instantiateContextDetached(
1679
- false, // existing
1680
- );
1690
+ await this.instantiateRuntime(codeDetails, undefined);
1681
1691
 
1682
1692
  this.setLoaded();
1683
1693
  }
@@ -1703,21 +1713,17 @@ export class Container
1703
1713
  this.storageAdapter,
1704
1714
  baseTree.blobs.quorumValues,
1705
1715
  );
1706
- const codeDetails = getCodeDetailsFromQuorumValues(qValues);
1707
1716
  this.initializeProtocolState(
1708
1717
  attributes,
1709
1718
  {
1710
1719
  members: [],
1711
1720
  proposals: [],
1712
- values:
1713
- codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
1721
+ values: qValues,
1714
1722
  }, // IQuorumSnapShot
1715
1723
  );
1724
+ const codeDetails = this.getCodeDetailsFromQuorum();
1716
1725
 
1717
- await this.instantiateContextDetached(
1718
- true, // existing
1719
- snapshotTree,
1720
- );
1726
+ await this.instantiateRuntime(codeDetails, snapshotTree);
1721
1727
 
1722
1728
  this.setLoaded();
1723
1729
  }
@@ -1786,7 +1792,10 @@ export class Container
1786
1792
  this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })),
1787
1793
  );
1788
1794
 
1789
- const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
1795
+ const protocolLogger = createChildLogger({
1796
+ logger: this.subLogger,
1797
+ namespace: "ProtocolHandler",
1798
+ });
1790
1799
 
1791
1800
  protocol.quorum.on("error", (error) => {
1792
1801
  protocolLogger.sendErrorEvent(error);
@@ -1897,7 +1906,7 @@ export class Container
1897
1906
  const serviceProvider = () => this.service;
1898
1907
  const deltaManager = new DeltaManager<ConnectionManager>(
1899
1908
  serviceProvider,
1900
- ChildLogger.create(this.subLogger, "DeltaManager"),
1909
+ createChildLogger({ logger: this.subLogger, namespace: "DeltaManager" }),
1901
1910
  () => this.activeConnection(),
1902
1911
  (props: IConnectionManagerFactoryArgs) =>
1903
1912
  new ConnectionManager(
@@ -1905,7 +1914,7 @@ export class Container
1905
1914
  () => this.isDirty,
1906
1915
  this.client,
1907
1916
  this._canReconnect,
1908
- ChildLogger.create(this.subLogger, "ConnectionManager"),
1917
+ createChildLogger({ logger: this.subLogger, namespace: "ConnectionManager" }),
1909
1918
  props,
1910
1919
  ),
1911
1920
  );
@@ -2004,7 +2013,7 @@ export class Container
2004
2013
  if (value === ConnectionState.Connected) {
2005
2014
  durationFromDisconnected =
2006
2015
  time - this.connectionTransitionTimes[ConnectionState.Disconnected];
2007
- durationFromDisconnected = TelemetryLogger.formatTick(durationFromDisconnected);
2016
+ durationFromDisconnected = formatTick(durationFromDisconnected);
2008
2017
  } else if (value === ConnectionState.CatchingUp) {
2009
2018
  // This info is of most interesting while Catching Up.
2010
2019
  checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
@@ -2038,6 +2047,7 @@ export class Container
2038
2047
  : undefined,
2039
2048
  checkpointSequenceNumber,
2040
2049
  quorumSize: this._protocolHandler?.quorum.getMembers().size,
2050
+ isDirty: this.isDirty,
2041
2051
  ...this._deltaManager.connectionProps,
2042
2052
  },
2043
2053
  error,
@@ -2061,26 +2071,11 @@ export class Container
2061
2071
  }
2062
2072
  const state = this.connectionState === ConnectionState.Connected;
2063
2073
 
2064
- const logOpsOnReconnect: boolean =
2065
- this.connectionState === ConnectionState.Connected &&
2066
- !this.firstConnection &&
2067
- this.connectionMode === "write";
2068
- if (logOpsOnReconnect) {
2069
- this.messageCountAfterDisconnection = 0;
2070
- }
2071
-
2072
2074
  // Both protocol and context should not be undefined if we got so far.
2073
2075
 
2074
2076
  this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
2075
2077
  this.protocolHandler.setConnectionState(state, this.clientId);
2076
2078
  raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
2077
-
2078
- if (logOpsOnReconnect) {
2079
- this.mc.logger.sendTelemetryEvent({
2080
- eventName: "OpsSentOnReconnect",
2081
- count: this.messageCountAfterDisconnection,
2082
- });
2083
- }
2084
2079
  }
2085
2080
 
2086
2081
  // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
@@ -2156,7 +2151,6 @@ export class Container
2156
2151
  return -1;
2157
2152
  }
2158
2153
 
2159
- this.messageCountAfterDisconnection += 1;
2160
2154
  this.noopHeuristic?.notifyMessageSent();
2161
2155
  return this._deltaManager.submit(
2162
2156
  type,
@@ -2174,28 +2168,6 @@ export class Container
2174
2168
  }
2175
2169
  const local = this.clientId === message.clientId;
2176
2170
 
2177
- // Check and report if we're getting messages from a clientId that we previously
2178
- // flagged should have left, or from a client that's not in the quorum but should be
2179
- if (message.clientId != null) {
2180
- const client = this.protocolHandler.quorum.getMember(message.clientId);
2181
-
2182
- if (client === undefined && message.type !== MessageType.ClientJoin) {
2183
- // pre-0.58 error message: messageClientIdMissingFromQuorum
2184
- throw new Error("Remote message's clientId is missing from the quorum");
2185
- }
2186
-
2187
- // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
2188
- // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
2189
- // document we don't need to blow up aggressively.
2190
- if (
2191
- this.clientsWhoShouldHaveLeft.has(message.clientId) &&
2192
- !canBeCoalescedByService(message)
2193
- ) {
2194
- // pre-0.58 error message: messageClientIdShouldHaveLeft
2195
- throw new Error("Remote message's clientId already should have left");
2196
- }
2197
- }
2198
-
2199
2171
  // Allow the protocol handler to process the message
2200
2172
  const result = this.protocolHandler.processMessage(message, local);
2201
2173
 
@@ -2280,17 +2252,7 @@ export class Container
2280
2252
  return { snapshot, versionId: version?.id };
2281
2253
  }
2282
2254
 
2283
- private async instantiateContextDetached(existing: boolean, snapshot?: ISnapshotTree) {
2284
- const codeDetails = this.getCodeDetailsFromQuorum();
2285
- if (codeDetails === undefined) {
2286
- throw new Error("pkg should be provided in create flow!!");
2287
- }
2288
-
2289
- await this.instantiateContext(existing, codeDetails, snapshot);
2290
- }
2291
-
2292
- private async instantiateContext(
2293
- existing: boolean,
2255
+ private async instantiateRuntime(
2294
2256
  codeDetails: IFluidCodeDetails,
2295
2257
  snapshot: ISnapshotTree | undefined,
2296
2258
  pendingLocalState?: unknown,
@@ -2327,6 +2289,8 @@ export class Container
2327
2289
  (this.protocolHandler.quorum.get("code") ??
2328
2290
  this.protocolHandler.quorum.get("code2")) as IFluidCodeDetails | undefined;
2329
2291
 
2292
+ const existing = snapshot !== undefined;
2293
+
2330
2294
  const context = new ContainerContext(
2331
2295
  this.options,
2332
2296
  this.scope,
@@ -2371,8 +2335,6 @@ export class Container
2371
2335
  this._lifecycleEvents.emit("runtimeInstantiated");
2372
2336
 
2373
2337
  this._loadedCodeDetails = codeDetails;
2374
-
2375
- this.emit("contextChanged", codeDetails);
2376
2338
  }
2377
2339
 
2378
2340
  private readonly updateDirtyContainerState = (dirty: boolean) => {
@@ -0,0 +1,113 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import {
7
+ ITelemetryBaseEvent,
8
+ ITelemetryBaseLogger,
9
+ ITelemetryProperties,
10
+ } from "@fluidframework/core-interfaces";
11
+ import { performance } from "@fluidframework/common-utils";
12
+ import { debug as registerDebug, IDebugger } from "debug";
13
+ import {
14
+ ITelemetryLoggerExt,
15
+ ITelemetryLoggerPropertyBags,
16
+ createMultiSinkLogger,
17
+ eventNamespaceSeparator,
18
+ formatTick,
19
+ } from "@fluidframework/telemetry-utils";
20
+
21
+ /**
22
+ * Implementation of debug logger
23
+ */
24
+ export class DebugLogger implements ITelemetryBaseLogger {
25
+ /**
26
+ * Mix in debug logger with another logger.
27
+ * Returned logger will output events to both newly created debug logger, as well as base logger
28
+ * @param namespace - Telemetry event name prefix to add to all events
29
+ * @param properties - Base properties to add to all events
30
+ * @param propertyGetters - Getters to add additional properties to all events
31
+ * @param baseLogger - Base logger to output events (in addition to debug logger being created). Can be undefined.
32
+ */
33
+ public static mixinDebugLogger(
34
+ namespace: string,
35
+ baseLogger?: ITelemetryBaseLogger,
36
+ properties?: ITelemetryLoggerPropertyBags,
37
+ ): ITelemetryLoggerExt {
38
+ // Setup base logger upfront, such that host can disable it (if needed)
39
+ const debug = registerDebug(namespace);
40
+
41
+ // Create one for errors that is always enabled
42
+ // It can be silenced by replacing console.error if the debug namespace is not enabled.
43
+ const debugErr = registerDebug(namespace);
44
+ debugErr.log = function (...args) {
45
+ if (debug.enabled === true) {
46
+ // if the namespace is enabled, just use the default logger
47
+ registerDebug.log(...args);
48
+ } else {
49
+ // other wise, use the console logger (which could be replaced and silenced)
50
+ console.error(...args);
51
+ }
52
+ };
53
+ debugErr.enabled = true;
54
+
55
+ return createMultiSinkLogger({
56
+ namespace,
57
+ loggers: [baseLogger, new DebugLogger(debug, debugErr)],
58
+ properties,
59
+ tryInheritProperties: true,
60
+ });
61
+ }
62
+
63
+ private constructor(private readonly debug: IDebugger, private readonly debugErr: IDebugger) {}
64
+
65
+ /**
66
+ * Send an event to debug loggers
67
+ *
68
+ * @param event - the event to send
69
+ */
70
+ public send(event: ITelemetryBaseEvent): void {
71
+ const newEvent: ITelemetryProperties = { ...event };
72
+ const isError = newEvent.category === "error";
73
+ let logger = isError ? this.debugErr : this.debug;
74
+
75
+ // Use debug's coloring schema for base of the event
76
+ const index = event.eventName.lastIndexOf(eventNamespaceSeparator);
77
+ const name = event.eventName.substring(index + 1);
78
+ if (index > 0) {
79
+ logger = logger.extend(event.eventName.substring(0, index));
80
+ }
81
+ newEvent.eventName = undefined;
82
+
83
+ let tick = "";
84
+ tick = `tick=${formatTick(performance.now())}`;
85
+
86
+ // Extract stack to put it last, but also to avoid escaping '\n' in it by JSON.stringify below
87
+ const stack = newEvent.stack ?? "";
88
+ newEvent.stack = undefined;
89
+
90
+ // Watch out for circular references - they can come from two sources
91
+ // 1) error object - we do not control it and should remove it and retry
92
+ // 2) properties supplied by telemetry caller - that's a bug that should be addressed!
93
+ let payload: string;
94
+ try {
95
+ payload = JSON.stringify(newEvent);
96
+ } catch (error) {
97
+ newEvent.error = undefined;
98
+ payload = JSON.stringify(newEvent);
99
+ }
100
+
101
+ if (payload === "{}") {
102
+ payload = "";
103
+ }
104
+
105
+ // Force errors out, to help with diagnostics
106
+ if (isError) {
107
+ logger.enabled = true;
108
+ }
109
+
110
+ // Print multi-line.
111
+ logger(`${name} ${payload} ${tick} ${stack}`);
112
+ }
113
+ }
@@ -3,7 +3,6 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { default as AbortController } from "abort-controller";
7
6
  import { v4 as uuid } from "uuid";
8
7
  import { IEventProvider } from "@fluidframework/common-definitions";
9
8
  import { ITelemetryProperties, ITelemetryErrorEvent } from "@fluidframework/core-interfaces";
@@ -93,6 +92,17 @@ function isClientMessage(message: ISequencedDocumentMessage | IDocumentMessage):
93
92
  }
94
93
  }
95
94
 
95
+ /**
96
+ * Type is used to cast AbortController to represent new version of DOM API and prevent build issues
97
+ * TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
98
+ */
99
+ type AbortControllerReal = AbortController & { abort(reason?: any): void };
100
+ /**
101
+ * Type is used to cast AbortSignal to represent new version of DOM API and prevent build issues
102
+ * TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
103
+ */
104
+ type AbortSignalReal = AbortSignal & { reason: any };
105
+
96
106
  /**
97
107
  * Manages the flow of both inbound and outbound messages. This class ensures that shared objects receive delta
98
108
  * messages in order regardless of possible network conditions or timings causing out of order delivery.
@@ -624,7 +634,8 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
624
634
  // This is useless for known ranges (to is defined) as it means request is over either way.
625
635
  // And it will cancel unbound request too early, not allowing us to learn where the end of the file is.
626
636
  if (!opsFromFetch && cancelFetch(op)) {
627
- controller.abort();
637
+ // TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
638
+ (controller as AbortControllerReal).abort("DeltaManager getDeltas fetch cancelled");
628
639
  this._inbound.off("push", opListener);
629
640
  }
630
641
  };
@@ -632,7 +643,11 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
632
643
  try {
633
644
  this._inbound.on("push", opListener);
634
645
  assert(this.closeAbortController.signal.onabort === null, 0x1e8 /* "reentrancy" */);
635
- this.closeAbortController.signal.onabort = () => controller.abort();
646
+ this.closeAbortController.signal.onabort = () =>
647
+ // TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
648
+ (controller as AbortControllerReal).abort(
649
+ (this.closeAbortController.signal as AbortSignalReal).reason,
650
+ );
636
651
 
637
652
  const stream = this.deltaStorage.fetchMessages(
638
653
  from, // inclusive
@@ -656,6 +671,14 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
656
671
  }
657
672
  }
658
673
  } finally {
674
+ if (controller.signal.aborted) {
675
+ this.logger.sendTelemetryEvent({
676
+ eventName: "DeltaManager_GetDeltasAborted",
677
+ fetchReason,
678
+ // TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
679
+ reason: (controller.signal as AbortSignalReal).reason,
680
+ });
681
+ }
659
682
  this.closeAbortController.signal.onabort = null;
660
683
  this._inbound.off("push", opListener);
661
684
  assert(!opsFromFetch, 0x289 /* "logic error" */);
@@ -710,7 +733,8 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
710
733
  }
711
734
 
712
735
  private clearQueues() {
713
- this.closeAbortController.abort();
736
+ // TODO: Remove when typescript version of the repo contains the AbortSignal.reason property (AB#5045)
737
+ (this.closeAbortController as AbortControllerReal).abort("DeltaManager is closed");
714
738
 
715
739
  this._inbound.clear();
716
740
  this._inboundSignal.clear();
package/src/loader.ts CHANGED
@@ -6,14 +6,12 @@
6
6
  import { v4 as uuid } from "uuid";
7
7
  import {
8
8
  ITelemetryLoggerExt,
9
- ChildLogger,
10
- DebugLogger,
11
9
  IConfigProviderBase,
12
- loggerToMonitoringContext,
13
10
  mixinMonitoringContext,
14
11
  MonitoringContext,
15
12
  PerformanceEvent,
16
13
  sessionStorageConfigProvider,
14
+ createChildMonitoringContext,
17
15
  } from "@fluidframework/telemetry-utils";
18
16
  import {
19
17
  ITelemetryBaseLogger,
@@ -44,6 +42,7 @@ import { Container, IPendingContainerState } from "./container";
44
42
  import { IParsedUrl, parseUrl } from "./utils";
45
43
  import { pkgVersion } from "./packageVersion";
46
44
  import { ProtocolHandlerBuilder } from "./protocol";
45
+ import { DebugLogger } from "./debugLogger";
47
46
 
48
47
  function canUseCache(request: IRequest): boolean {
49
48
  if (request.headers === undefined) {
@@ -343,7 +342,10 @@ export class Loader implements IHostLoader {
343
342
  protocolHandlerBuilder,
344
343
  subLogger: subMc.logger,
345
344
  };
346
- this.mc = loggerToMonitoringContext(ChildLogger.create(this.services.subLogger, "Loader"));
345
+ this.mc = createChildMonitoringContext({
346
+ logger: this.services.subLogger,
347
+ namespace: "Loader",
348
+ });
347
349
  }
348
350
 
349
351
  public get IFluidRouter(): IFluidRouter {
@@ -407,16 +409,23 @@ export class Loader implements IHostLoader {
407
409
  this.containers.set(key, containerP);
408
410
  containerP
409
411
  .then((container) => {
410
- // If the container is closed or becomes closed after we resolve it, remove it from the cache.
411
- if (container.closed) {
412
+ // If the container is closed/disposed or becomes closed/disposed after we resolve it,
413
+ // remove it from the cache.
414
+ if (container.closed || container.disposed) {
412
415
  this.containers.delete(key);
413
416
  } else {
414
417
  container.once("closed", () => {
415
418
  this.containers.delete(key);
416
419
  });
420
+ container.once("disposed", () => {
421
+ this.containers.delete(key);
422
+ });
417
423
  }
418
424
  })
419
- .catch((error) => {});
425
+ .catch((error) => {
426
+ // If an error occured while resolving the container request, then remove it from the cache.
427
+ this.containers.delete(key);
428
+ });
420
429
  }
421
430
 
422
431
  private async resolveCore(
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.0.0-internal.5.3.4";
9
+ export const pkgVersion = "2.0.0-internal.5.4.2";