@fluidframework/container-loader 1.0.0 → 1.1.0-76254

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 (85) hide show
  1. package/dist/connectionManager.d.ts.map +1 -1
  2. package/dist/connectionManager.js +3 -1
  3. package/dist/connectionManager.js.map +1 -1
  4. package/dist/connectionStateHandler.d.ts +44 -10
  5. package/dist/connectionStateHandler.d.ts.map +1 -1
  6. package/dist/connectionStateHandler.js +90 -35
  7. package/dist/connectionStateHandler.js.map +1 -1
  8. package/dist/container.d.ts +2 -2
  9. package/dist/container.d.ts.map +1 -1
  10. package/dist/container.js +48 -70
  11. package/dist/container.js.map +1 -1
  12. package/dist/containerStorageAdapter.d.ts +2 -2
  13. package/dist/containerStorageAdapter.d.ts.map +1 -1
  14. package/dist/containerStorageAdapter.js +4 -4
  15. package/dist/containerStorageAdapter.js.map +1 -1
  16. package/dist/contracts.d.ts +1 -1
  17. package/dist/contracts.js +1 -1
  18. package/dist/contracts.js.map +1 -1
  19. package/dist/deltaManager.d.ts.map +1 -1
  20. package/dist/deltaManager.js +9 -1
  21. package/dist/deltaManager.js.map +1 -1
  22. package/dist/deltaQueue.d.ts.map +1 -1
  23. package/dist/deltaQueue.js +8 -3
  24. package/dist/deltaQueue.js.map +1 -1
  25. package/dist/packageVersion.d.ts +1 -1
  26. package/dist/packageVersion.d.ts.map +1 -1
  27. package/dist/packageVersion.js +1 -1
  28. package/dist/packageVersion.js.map +1 -1
  29. package/dist/protocolTreeDocumentStorageService.d.ts +2 -2
  30. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  31. package/dist/retriableDocumentStorageService.d.ts +2 -2
  32. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  33. package/dist/retriableDocumentStorageService.js +4 -4
  34. package/dist/retriableDocumentStorageService.js.map +1 -1
  35. package/dist/utils.d.ts.map +1 -1
  36. package/dist/utils.js +3 -2
  37. package/dist/utils.js.map +1 -1
  38. package/lib/connectionManager.d.ts.map +1 -1
  39. package/lib/connectionManager.js +4 -2
  40. package/lib/connectionManager.js.map +1 -1
  41. package/lib/connectionStateHandler.d.ts +44 -10
  42. package/lib/connectionStateHandler.d.ts.map +1 -1
  43. package/lib/connectionStateHandler.js +90 -35
  44. package/lib/connectionStateHandler.js.map +1 -1
  45. package/lib/container.d.ts +2 -2
  46. package/lib/container.d.ts.map +1 -1
  47. package/lib/container.js +49 -71
  48. package/lib/container.js.map +1 -1
  49. package/lib/containerStorageAdapter.d.ts +2 -2
  50. package/lib/containerStorageAdapter.d.ts.map +1 -1
  51. package/lib/containerStorageAdapter.js +4 -4
  52. package/lib/containerStorageAdapter.js.map +1 -1
  53. package/lib/contracts.d.ts +1 -1
  54. package/lib/contracts.js +1 -1
  55. package/lib/contracts.js.map +1 -1
  56. package/lib/deltaManager.d.ts.map +1 -1
  57. package/lib/deltaManager.js +9 -1
  58. package/lib/deltaManager.js.map +1 -1
  59. package/lib/deltaQueue.d.ts.map +1 -1
  60. package/lib/deltaQueue.js +8 -3
  61. package/lib/deltaQueue.js.map +1 -1
  62. package/lib/packageVersion.d.ts +1 -1
  63. package/lib/packageVersion.d.ts.map +1 -1
  64. package/lib/packageVersion.js +1 -1
  65. package/lib/packageVersion.js.map +1 -1
  66. package/lib/protocolTreeDocumentStorageService.d.ts +2 -2
  67. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  68. package/lib/retriableDocumentStorageService.d.ts +2 -2
  69. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  70. package/lib/retriableDocumentStorageService.js +4 -4
  71. package/lib/retriableDocumentStorageService.js.map +1 -1
  72. package/lib/utils.d.ts.map +1 -1
  73. package/lib/utils.js +3 -2
  74. package/lib/utils.js.map +1 -1
  75. package/package.json +15 -28
  76. package/src/connectionManager.ts +4 -6
  77. package/src/connectionStateHandler.ts +113 -54
  78. package/src/container.ts +66 -89
  79. package/src/containerStorageAdapter.ts +4 -4
  80. package/src/contracts.ts +1 -1
  81. package/src/deltaManager.ts +8 -2
  82. package/src/deltaQueue.ts +7 -3
  83. package/src/packageVersion.ts +1 -1
  84. package/src/retriableDocumentStorageService.ts +4 -4
  85. package/src/utils.ts +3 -2
package/dist/container.js CHANGED
@@ -117,7 +117,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
117
117
  // Tells if container can reconnect on losing fist connection
118
118
  // If false, container gets closed on loss of connection.
119
119
  this._canReconnect = true;
120
- this._lifecycleState = "created";
120
+ this._lifecycleState = "loading";
121
121
  this._attachState = container_definitions_1.AttachState.Detached;
122
122
  this.resumedOpProcessingAfterLoad = false;
123
123
  this.firstConnection = true;
@@ -173,16 +173,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
173
173
  logConnectionStateChangeTelemetry: (value, oldState, reason) => this.logConnectionStateChangeTelemetry(value, oldState, reason),
174
174
  shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
175
175
  maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
176
- logConnectionIssue: (eventName) => {
176
+ logConnectionIssue: (eventName, details) => {
177
177
  // We get here when socket does not receive any ops on "write" connection, including
178
178
  // its own join op. Attempt recovery option.
179
- this._deltaManager.logConnectionIssue({
180
- eventName,
181
- duration: common_utils_1.performance.now() - this.connectionTransitionTimes[connectionState_1.ConnectionState.CatchingUp],
182
- });
179
+ this._deltaManager.logConnectionIssue(Object.assign({ eventName, duration: common_utils_1.performance.now() - this.connectionTransitionTimes[connectionState_1.ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
183
180
  },
184
181
  connectionStateChanged: () => {
185
- if (this.loaded) {
182
+ // Fire events only if container is fully loaded and not closed
183
+ if (this._lifecycleState === "loaded") {
186
184
  this.propagateConnectionState();
187
185
  }
188
186
  },
@@ -268,7 +266,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
268
266
  });
269
267
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
270
268
  var _a, _b;
271
- container._lifecycleState = "loading";
272
269
  const version = loadOptions.version;
273
270
  const defaultMode = { opsBeforeReturn: "cached" };
274
271
  // if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
@@ -303,7 +300,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
303
300
  static async createDetached(loader, codeDetails) {
304
301
  const container = new Container(loader, {});
305
302
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "CreateDetached" }, async (_event) => {
306
- container._lifecycleState = "loading";
307
303
  await container.createDetached(codeDetails);
308
304
  return container;
309
305
  }, { start: true, end: true, cancel: "generic" });
@@ -316,20 +312,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
316
312
  const container = new Container(loader, {});
317
313
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "RehydrateDetachedFromSnapshot" }, async (_event) => {
318
314
  const deserializedSummary = JSON.parse(snapshot);
319
- container._lifecycleState = "loading";
320
315
  await container.rehydrateDetachedFromSnapshot(deserializedSummary);
321
316
  return container;
322
317
  }, { start: true, end: true, cancel: "generic" });
323
318
  }
324
- get loaded() {
325
- return (this._lifecycleState !== "created" && this._lifecycleState !== "loading");
326
- }
327
- set loaded(t) {
328
- (0, common_utils_1.assert)(t, 0x27d /* "Setting loaded state to false is not supported" */);
329
- (0, common_utils_1.assert)(this._lifecycleState !== "created", 0x27e /* "Must go through loading state before loaded" */);
319
+ setLoaded() {
330
320
  // It's conceivable the container could be closed when this is called
331
321
  // Only transition states if currently loading
332
322
  if (this._lifecycleState === "loading") {
323
+ // Propagate current connection state through the system.
324
+ this.propagateConnectionState();
333
325
  this._lifecycleState = "loaded";
334
326
  }
335
327
  }
@@ -451,19 +443,30 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
451
443
  return this.protocolHandler.quorum;
452
444
  }
453
445
  close(error) {
446
+ // 1. Ensure that close sequence is exactly the same no matter if it's initiated by host or by DeltaManager
447
+ // 2. We need to ensure that we deliver disconnect event to runtime properly. See connectionStateChanged
448
+ // handler. We only deliver events if container fully loaded. Transitioning from "loading" ->
449
+ // "closing" will lose that info (can also solve by tracking extra state).
450
+ this._deltaManager.close(error);
451
+ (0, common_utils_1.assert)(this.connectionState === connectionState_1.ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
452
+ (0, common_utils_1.assert)(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
453
+ }
454
+ closeCore(error) {
454
455
  var _a, _b, _c, _d;
455
- if (this.closed) {
456
- return;
457
- }
456
+ (0, common_utils_1.assert)(!this.closed, 0x315 /* re-entrancy */);
458
457
  try {
459
- this._lifecycleState = "closing";
460
458
  // Ensure that we raise all key events even if one of these throws
461
459
  try {
462
- this._deltaManager.close(error);
460
+ // Raise event first, to ensure we capture _lifecycleState before transition.
461
+ // This gives us a chance to know what errors happened on open vs. on fully loaded container.
462
+ this.mc.logger.sendTelemetryEvent({
463
+ eventName: "ContainerClose",
464
+ category: error === undefined ? "generic" : "error",
465
+ }, error);
466
+ this._lifecycleState = "closing";
463
467
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
464
468
  this.connectionStateHandler.dispose();
465
469
  (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
466
- (0, common_utils_1.assert)(this.connectionState === connectionState_1.ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
467
470
  (_c = this._storageService) === null || _c === void 0 ? void 0 : _c.dispose();
468
471
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
469
472
  // about file, like file being overwritten in storage, but client having stale local cache.
@@ -473,10 +476,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
473
476
  catch (exception) {
474
477
  this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
475
478
  }
476
- this.mc.logger.sendTelemetryEvent({
477
- eventName: "ContainerClose",
478
- category: error === undefined ? "generic" : "error",
479
- }, error);
480
479
  this.emit("closed", error);
481
480
  this.removeAllListeners();
482
481
  if (this.visibilityEventHandler !== undefined) {
@@ -494,11 +493,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
494
493
  (0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
495
494
  (0, common_utils_1.assert)(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
496
495
  (0, common_utils_1.assert)(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
496
+ (0, common_utils_1.assert)(this._protocolHandler.attributes.term !== undefined, 0x30b /* Must have a valid protocol handler instance */);
497
497
  const pendingState = {
498
498
  pendingRuntimeState: this.context.getPendingLocalState(),
499
499
  url: this.resolvedUrl.url,
500
- protocol: this._protocolHandler.getProtocolState(),
501
- term: this._protocolHandler.term,
500
+ protocol: this.protocolHandler.getProtocolState(),
501
+ term: this._protocolHandler.attributes.term,
502
502
  clientId: this.clientId,
503
503
  };
504
504
  this.close();
@@ -797,10 +797,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
797
797
  const codeDetails = this.getCodeDetailsFromQuorum();
798
798
  await this.instantiateContext(true, // existing
799
799
  codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
800
- // Propagate current connection state through the system.
801
- this.propagateConnectionState();
802
800
  // Internal context is fully loaded at this point
803
- this.loaded = true;
801
+ this.setLoaded();
804
802
  // We might have hit some failure that did not manifest itself in exception in this flow,
805
803
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
806
804
  if (!this.closed) {
@@ -856,8 +854,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
856
854
  qValues);
857
855
  // The load context - given we seeded the quorum - will be great
858
856
  await this.instantiateContextDetached(false);
859
- this.propagateConnectionState();
860
- this.loaded = true;
857
+ this.setLoaded();
861
858
  }
862
859
  async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
863
860
  if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
@@ -878,8 +875,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
878
875
  codeDetails !== undefined ? (0, quorum_1.initQuorumValuesFromCodeDetails)(codeDetails) : []);
879
876
  await this.instantiateContextDetached(true, // existing
880
877
  snapshotTree);
881
- this.loaded = true;
882
- this.propagateConnectionState();
878
+ this.setLoaded();
883
879
  }
884
880
  async connectStorageService() {
885
881
  var _a, _b;
@@ -933,7 +929,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
933
929
  return protocolHandler;
934
930
  }
935
931
  async initializeProtocolState(attributes, members, proposals, values) {
936
- const protocol = new protocol_base_1.ProtocolOpHandler(attributes.minimumSequenceNumber, attributes.sequenceNumber, attributes.term, members, proposals, values, (key, value) => this.submitMessage(protocol_definitions_1.MessageType.Propose, { key, value }));
932
+ const protocol = new protocol_base_1.ProtocolOpHandlerWithClientValidation(attributes.minimumSequenceNumber, attributes.sequenceNumber, attributes.term, members, proposals, values, (key, value) => this.submitMessage(protocol_definitions_1.MessageType.Propose, { key, value }));
937
933
  const protocolLogger = telemetry_utils_1.ChildLogger.create(this.subLogger, "ProtocolHandler");
938
934
  protocol.quorum.on("error", (error) => {
939
935
  protocolLogger.sendErrorEvent(error);
@@ -961,17 +957,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
961
957
  return protocol;
962
958
  }
963
959
  captureProtocolSummary() {
964
- const quorumSnapshot = this.protocolHandler.quorum.snapshot();
965
- // Save attributes for the document
966
- const documentAttributes = {
967
- minimumSequenceNumber: this.protocolHandler.minimumSequenceNumber,
968
- sequenceNumber: this.protocolHandler.sequenceNumber,
969
- term: this.protocolHandler.term,
970
- };
960
+ const quorumSnapshot = this.protocolHandler.snapshot();
971
961
  const summary = {
972
962
  tree: {
973
963
  attributes: {
974
- content: JSON.stringify(documentAttributes),
964
+ content: JSON.stringify(this.protocolHandler.attributes),
975
965
  type: protocol_definitions_1.SummaryType.Blob,
976
966
  },
977
967
  quorumMembers: {
@@ -1060,7 +1050,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1060
1050
  this.emit("readonly", readonly);
1061
1051
  });
1062
1052
  deltaManager.on("closed", (error) => {
1063
- this.close(error);
1053
+ this.closeCore(error);
1064
1054
  });
1065
1055
  return deltaManager;
1066
1056
  }
@@ -1116,6 +1106,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1116
1106
  }
1117
1107
  }
1118
1108
  propagateConnectionState() {
1109
+ var _a;
1119
1110
  const logOpsOnReconnect = this.connectionState === connectionState_1.ConnectionState.Connected &&
1120
1111
  !this.firstConnection &&
1121
1112
  this.connectionMode === "write";
@@ -1123,11 +1114,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1123
1114
  this.messageCountAfterDisconnection = 0;
1124
1115
  }
1125
1116
  const state = this.connectionState === connectionState_1.ConnectionState.Connected;
1126
- if (!this.context.disposed) {
1117
+ // Both protocol and context should not be undefined if we got so far.
1118
+ if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1127
1119
  this.context.setConnectionState(state, this.clientId);
1128
1120
  }
1129
- (0, common_utils_1.assert)(this.protocolHandler !== undefined, 0x0dc /* "Protocol handler should be set here" */);
1130
- this.protocolHandler.quorum.setConnectionState(state, this.clientId);
1121
+ this.protocolHandler.setConnectionState(state, this.clientId);
1131
1122
  (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId);
1132
1123
  if (logOpsOnReconnect) {
1133
1124
  this.mc.logger.sendTelemetryEvent({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
@@ -1169,32 +1160,19 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1169
1160
  return this._deltaManager.submit(type, contents, batch, metadata);
1170
1161
  }
1171
1162
  processRemoteMessage(message) {
1172
- var _a, _b;
1173
- // Check and report if we're getting messages from a clientId that we previously
1174
- // flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
1175
- if (message.clientId != null) {
1176
- let errorMsg;
1177
- const client = this.getQuorum().getMember(message.clientId);
1178
- if (client === undefined && message.type !== protocol_definitions_1.MessageType.ClientJoin) {
1179
- // pre-0.58 error message: messageClientIdMissingFromQuorum
1180
- errorMsg = "Remote message's clientId is missing from the quorum";
1181
- }
1182
- else if ((client === null || client === void 0 ? void 0 : client.shouldHaveLeft) === true && message.type !== protocol_definitions_1.MessageType.NoOp) {
1183
- // pre-0.58 error message: messageClientIdShouldHaveLeft
1184
- errorMsg = "Remote message's clientId already should have left";
1185
- }
1186
- if (errorMsg !== undefined) {
1187
- const error = new container_utils_1.DataCorruptionError(errorMsg, (0, container_utils_1.extractSafePropertiesFromMessage)(message));
1188
- this.close((0, telemetry_utils_1.normalizeError)(error));
1189
- }
1190
- }
1191
1163
  const local = this.clientId === message.clientId;
1164
+ // Allow the protocol handler to process the message
1165
+ let result = { immediateNoOp: false };
1166
+ try {
1167
+ result = this.protocolHandler.processMessage(message, local);
1168
+ }
1169
+ catch (error) {
1170
+ this.close((0, telemetry_utils_1.wrapError)(error, (errorMessage) => new container_utils_1.DataCorruptionError(errorMessage, (0, container_utils_1.extractSafePropertiesFromMessage)(message))));
1171
+ }
1192
1172
  // Forward non system messages to the loaded runtime for processing
1193
1173
  if (!(0, protocol_base_1.isSystemMessage)(message)) {
1194
1174
  this.context.process(message, local, undefined);
1195
1175
  }
1196
- // Allow the protocol handler to process the message
1197
- const result = this.protocolHandler.processMessage(message, local);
1198
1176
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1199
1177
  if (this.activeConnection()) {
1200
1178
  if (this.collabWindowTracker === undefined) {
@@ -1206,7 +1184,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1206
1184
  this.collabWindowTracker = new collabWindowTracker_1.CollabWindowTracker((type, contents) => {
1207
1185
  (0, common_utils_1.assert)(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1208
1186
  this.submitMessage(type, contents);
1209
- }, (_a = this.serviceConfiguration) === null || _a === void 0 ? void 0 : _a.noopTimeFrequency, (_b = this.serviceConfiguration) === null || _b === void 0 ? void 0 : _b.noopCountFrequency);
1187
+ }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1210
1188
  }
1211
1189
  this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1212
1190
  }