@fluidframework/container-loader 1.2.7 → 1.3.0-100520

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.
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,qCAAqC,CAAC;AAC1D,eAAO,MAAM,UAAU,UAAU,CAAC"}
1
+ {"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,qCAAqC,CAAC;AAC1D,eAAO,MAAM,UAAU,iBAAiB,CAAC"}
@@ -5,5 +5,5 @@
5
5
  * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
6
6
  */
7
7
  export const pkgName = "@fluidframework/container-loader";
8
- export const pkgVersion = "1.2.7";
8
+ export const pkgVersion = "1.3.0-100520";
9
9
  //# sourceMappingURL=packageVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,kCAAkC,CAAC;AAC1D,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-loader\";\nexport const pkgVersion = \"1.2.7\";\n"]}
1
+ {"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,kCAAkC,CAAC;AAC1D,MAAM,CAAC,MAAM,UAAU,GAAG,cAAc,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/container-loader\";\nexport const pkgVersion = \"1.3.0-100520\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-loader",
3
- "version": "1.2.7",
3
+ "version": "1.3.0-100520",
4
4
  "description": "Fluid container loader",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -63,26 +63,28 @@
63
63
  "dependencies": {
64
64
  "@fluidframework/common-definitions": "^0.20.1",
65
65
  "@fluidframework/common-utils": "^0.32.1",
66
- "@fluidframework/container-definitions": "^1.2.7",
67
- "@fluidframework/container-utils": "^1.2.7",
68
- "@fluidframework/core-interfaces": "^1.2.7",
69
- "@fluidframework/driver-definitions": "^1.2.7",
70
- "@fluidframework/driver-utils": "^1.2.7",
66
+ "@fluidframework/container-definitions": "1.3.0-100520",
67
+ "@fluidframework/container-utils": "1.3.0-100520",
68
+ "@fluidframework/core-interfaces": "1.3.0-100520",
69
+ "@fluidframework/driver-definitions": "1.3.0-100520",
70
+ "@fluidframework/driver-utils": "1.3.0-100520",
71
71
  "@fluidframework/protocol-base": "^0.1036.5000",
72
72
  "@fluidframework/protocol-definitions": "^0.1028.2000",
73
- "@fluidframework/telemetry-utils": "^1.2.7",
73
+ "@fluidframework/telemetry-utils": "1.3.0-100520",
74
74
  "abort-controller": "^3.0.0",
75
75
  "double-ended-queue": "^2.1.0-0",
76
+ "events": "^3.1.0",
76
77
  "lodash": "^4.17.21",
78
+ "url": "^0.11.0",
77
79
  "uuid": "^8.3.1"
78
80
  },
79
81
  "devDependencies": {
80
82
  "@fluidframework/build-common": "^0.24.0",
81
83
  "@fluidframework/build-tools": "^0.2.74327",
82
- "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@1.2.1",
84
+ "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@^1.2.0",
83
85
  "@fluidframework/eslint-config-fluid": "^0.28.2000",
84
- "@fluidframework/mocha-test-setup": "^1.2.7",
85
- "@fluidframework/test-loader-utils": "^1.2.7",
86
+ "@fluidframework/mocha-test-setup": "1.3.0-100520",
87
+ "@fluidframework/test-loader-utils": "1.3.0-100520",
86
88
  "@microsoft/api-extractor": "^7.22.2",
87
89
  "@rushstack/eslint-config": "^2.5.1",
88
90
  "@types/double-ended-queue": "^2.1.0",
@@ -102,7 +104,7 @@
102
104
  "typescript-formatter": "7.1.0"
103
105
  },
104
106
  "typeValidation": {
105
- "version": "1.2.2",
107
+ "version": "1.3.0",
106
108
  "broken": {}
107
109
  }
108
110
  }
package/src/container.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  import merge from "lodash/merge";
8
8
  import { v4 as uuid } from "uuid";
9
9
  import {
10
- IDisposable, ITelemetryProperties,
10
+ IDisposable, ITelemetryLogger, ITelemetryProperties,
11
11
  } from "@fluidframework/common-definitions";
12
12
  import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
13
13
  import {
@@ -228,6 +228,24 @@ const getCodeProposal =
228
228
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
229
229
  (quorum: IQuorumProposals) => quorum.get("code") ?? quorum.get("code2");
230
230
 
231
+ /**
232
+ * Helper function to report to telemetry cases where operation takes longer than expected (1s)
233
+ * @param logger - logger to use
234
+ * @param eventName - event name
235
+ * @param action - functor to call and measure
236
+ */
237
+ async function ReportIfTooLong(
238
+ logger: ITelemetryLogger,
239
+ eventName: string,
240
+ action: () => Promise<ITelemetryProperties>,
241
+ ) {
242
+ const event = PerformanceEvent.start(logger, { eventName });
243
+ const props = await action();
244
+ if (event.duration > 1000) {
245
+ event.end(props);
246
+ }
247
+ }
248
+
231
249
  /**
232
250
  * State saved by a container at close time, to be used to load a new instance
233
251
  * of the container to the same state
@@ -566,6 +584,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
566
584
  containerAttachState: () => this._attachState,
567
585
  containerLifecycleState: () => this._lifecycleState,
568
586
  containerConnectionState: () => ConnectionState[this.connectionState],
587
+ serializedContainer: config.serializedContainerState !== undefined,
569
588
  },
570
589
  // we need to be judicious with our logging here to avoid generating too much data
571
590
  // all data logged here should be broadly applicable, and not specific to a
@@ -578,6 +597,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
578
597
  containerLoadedFromVersionId: () => this.loadedFromVersion?.id,
579
598
  containerLoadedFromVersionDate: () => this.loadedFromVersion?.date,
580
599
  // message information to associate errors with the specific execution state
600
+ // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
581
601
  dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
582
602
  dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
583
603
  dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId,
@@ -1175,17 +1195,20 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1175
1195
  pendingLocalState?.pendingRuntimeState,
1176
1196
  );
1177
1197
 
1178
- // Internal context is fully loaded at this point
1179
- this.setLoaded();
1180
-
1181
1198
  // We might have hit some failure that did not manifest itself in exception in this flow,
1182
1199
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
1183
1200
  if (!this.closed) {
1184
1201
  if (opsBeforeReturnP !== undefined) {
1185
1202
  this._deltaManager.inbound.resume();
1186
1203
 
1187
- await opsBeforeReturnP;
1188
- await this._deltaManager.inbound.waitTillProcessingDone();
1204
+ await ReportIfTooLong(
1205
+ this.mc.logger,
1206
+ "WaitOps",
1207
+ async () => { await opsBeforeReturnP; return {}; });
1208
+ await ReportIfTooLong(
1209
+ this.mc.logger,
1210
+ "WaitOpProcessing",
1211
+ async () => this._deltaManager.inbound.waitTillProcessingDone());
1189
1212
 
1190
1213
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1191
1214
  this._deltaManager.inbound.pause();
@@ -1215,9 +1238,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1215
1238
  throw new Error("Container was closed while load()");
1216
1239
  }
1217
1240
 
1241
+ // Internal context is fully loaded at this point
1242
+ this.setLoaded();
1243
+
1218
1244
  return {
1219
1245
  sequenceNumber: attributes.sequenceNumber,
1220
1246
  version: versionId,
1247
+ dmLastProcessedSeqNumber: this._deltaManager.lastSequenceNumber,
1248
+ dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
1221
1249
  };
1222
1250
  }
1223
1251
 
@@ -410,7 +410,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
410
410
 
411
411
  if (prefetchType !== "none") {
412
412
  const cacheOnly = prefetchType === "cached";
413
- await this.fetchMissingDeltasCore("DocumentOpen", cacheOnly, this.lastQueuedSequenceNumber);
413
+ await this.fetchMissingDeltasCore(`DocumentOpen_${prefetchType}`, cacheOnly);
414
414
 
415
415
  // Keep going with fetching ops from storage once we have all cached ops in.
416
416
  // But do not block load and make this request async / not blocking this api.
@@ -418,7 +418,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
418
418
  // (which in most cases will happen when we are done processing cached ops)
419
419
  if (cacheOnly) {
420
420
  // fire and forget
421
- this.fetchMissingDeltas("DocumentOpen");
421
+ this.fetchMissingDeltas("PostDocumentOpen");
422
422
  }
423
423
  }
424
424
 
@@ -453,6 +453,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
453
453
  private async getDeltas(
454
454
  from: number, // inclusive
455
455
  to: number | undefined, // exclusive
456
+ fetchReason: string,
456
457
  callback: (messages: ISequencedDocumentMessage[]) => void,
457
458
  cacheOnly: boolean) {
458
459
  const docService = this.serviceProvider();
@@ -473,7 +474,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
473
474
  // received through delta stream. Validate that before moving forward.
474
475
  if (this.lastQueuedSequenceNumber >= lastExpectedOp) {
475
476
  this.logger.sendPerformanceEvent({
476
- reason: this.fetchReason,
477
+ reason: fetchReason,
477
478
  eventName: "ExtraStorageCall",
478
479
  early: true,
479
480
  from,
@@ -521,7 +522,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
521
522
  to, // exclusive
522
523
  controller.signal,
523
524
  cacheOnly,
524
- this.fetchReason);
525
+ fetchReason);
525
526
 
526
527
  // eslint-disable-next-line no-constant-condition
527
528
  while (true) {
@@ -876,6 +877,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
876
877
  await this.getDeltas(
877
878
  from,
878
879
  to,
880
+ fetchReason,
879
881
  (messages) => {
880
882
  this.refreshDelayInfo(this.deltaStorageDelayId);
881
883
  this.enqueueMessages(messages, fetchReason);
package/src/deltaQueue.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { IDeltaQueue, IDeltaQueueEvents } from "@fluidframework/container-definitions";
7
- import { assert, performance, Deferred, TypedEventEmitter } from "@fluidframework/common-utils";
7
+ import { assert, performance, TypedEventEmitter } from "@fluidframework/common-utils";
8
8
  import Deque from "double-ended-queue";
9
9
 
10
10
  export interface IDeltaQueueWriter<T> {
@@ -30,7 +30,7 @@ export class DeltaQueue<T>
30
30
  * When processing is ongoing, holds a deferred that will resolve once processing stops.
31
31
  * Undefined when not processing.
32
32
  */
33
- private processingDeferred: Deferred<void> | undefined;
33
+ private processingPromise: Promise<{ count: number; duration: number; }> | undefined;
34
34
 
35
35
  public get disposed(): boolean {
36
36
  return this.isDisposed;
@@ -48,13 +48,11 @@ export class DeltaQueue<T>
48
48
  }
49
49
 
50
50
  public get idle(): boolean {
51
- return this.processingDeferred === undefined && this.q.length === 0;
51
+ return this.processingPromise === undefined && this.q.length === 0;
52
52
  }
53
53
 
54
- public async waitTillProcessingDone(): Promise<void> {
55
- if (this.processingDeferred !== undefined) {
56
- return this.processingDeferred.promise;
57
- }
54
+ public async waitTillProcessingDone() {
55
+ return this.processingPromise ?? { count: 0, duration: 0 };
58
56
  }
59
57
 
60
58
  /**
@@ -98,7 +96,7 @@ export class DeltaQueue<T>
98
96
  this.pauseCount++;
99
97
  // If called from within the processing loop, we are in the middle of processing an op. Return a promise
100
98
  // that will resolve when processing has actually stopped.
101
- return this.waitTillProcessingDone();
99
+ await this.waitTillProcessingDone();
102
100
  }
103
101
 
104
102
  public resume(): void {
@@ -113,20 +111,31 @@ export class DeltaQueue<T>
113
111
  * not already started.
114
112
  */
115
113
  private ensureProcessing() {
116
- if (!this.paused && this.processingDeferred === undefined) {
117
- this.processingDeferred = new Deferred<void>();
114
+ if (this.anythingToProcess() && this.processingPromise === undefined) {
118
115
  // Use a resolved promise to start the processing on a separate stack.
119
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
120
- Promise.resolve().then(() => {
121
- this.processDeltas();
122
- if (this.processingDeferred !== undefined) {
123
- this.processingDeferred.resolve();
124
- this.processingDeferred = undefined;
125
- }
116
+ this.processingPromise = Promise.resolve().then(() => {
117
+ assert(this.processingPromise !== undefined, "reentrancy?");
118
+ const result = this.processDeltas();
119
+ assert(this.processingPromise !== undefined, "reentrancy?");
120
+ // WARNING: Do not move next line to .finally() clause!
121
+ // It runs async and creates a race condition where incoming ensureProcessing() call observes
122
+ // from previous run while previous run is over (but finally clause was not scheduled yet)
123
+ this.processingPromise = undefined;
124
+ return result;
125
+ }).catch((error) => {
126
+ this.error = error;
127
+ this.processingPromise = undefined;
128
+ this.emit("error", error);
129
+ return { count: 0, duration: 0 };
126
130
  });
131
+ assert(this.processingPromise !== undefined, "processDeltas() should run async");
127
132
  }
128
133
  }
129
134
 
135
+ private anythingToProcess() {
136
+ return this.q.length !== 0 && !this.paused && this.error === undefined;
137
+ }
138
+
130
139
  /**
131
140
  * Executes the delta processing loop until a stop condition is reached.
132
141
  */
@@ -136,24 +145,21 @@ export class DeltaQueue<T>
136
145
 
137
146
  // For grouping to work we must process all local messages immediately and in the single turn.
138
147
  // So loop over them until no messages to process, we have become paused, or hit an error.
139
- while (!(this.q.length === 0 || this.paused || this.error !== undefined)) {
148
+ while (this.anythingToProcess()) {
140
149
  // Get the next message in the queue
141
150
  const next = this.q.shift();
142
151
  count++;
143
152
  // Process the message.
144
- try {
145
- // We know next is defined since we did a length check just prior to shifting.
146
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
147
- this.worker(next!);
148
- this.emit("op", next);
149
- } catch (error) {
150
- this.error = error;
151
- this.emit("error", error);
152
- }
153
+ // We know next is defined since we did a length check just prior to shifting.
154
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
155
+ this.worker(next!);
156
+ this.emit("op", next);
153
157
  }
154
158
 
159
+ const duration = performance.now() - start;
155
160
  if (this.q.length === 0) {
156
- this.emit("idle", count, performance.now() - start);
161
+ this.emit("idle", count, duration);
157
162
  }
163
+ return { count, duration };
158
164
  }
159
165
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "1.2.7";
9
+ export const pkgVersion = "1.3.0-100520";