@fluidframework/container-loader 0.47.0-36699 → 0.48.0-38142
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.
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +21 -27
- package/dist/container.js.map +1 -1
- package/dist/deltaManager.d.ts +1 -2
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +76 -39
- package/dist/deltaManager.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +0 -3
- package/dist/utils.js.map +1 -1
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +21 -27
- package/lib/container.js.map +1 -1
- package/lib/deltaManager.d.ts +1 -2
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +76 -39
- package/lib/deltaManager.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +1 -4
- package/lib/utils.js.map +1 -1
- package/package.json +7 -7
- package/src/container.ts +28 -31
- package/src/deltaManager.ts +87 -34
- package/src/packageVersion.ts +1 -1
- package/src/utils.ts +0 -4
package/lib/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC;AAC5B,OAAO,EAAE,EAAE,IAAI,IAAI,EAAE,MAAM,MAAM,CAAC;AAClC,OAAO,EACH,MAAM,EACN,cAAc,EACd,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { parse } from \"url\";\nimport { v4 as uuid } from \"uuid\";\nimport {\n assert,\n stringToBuffer,\n Uint8ArrayToArrayBuffer,\n unreachableCase,\n} from \"@fluidframework/common-utils\";\nimport { ISummaryTree, ISnapshotTree, SummaryType } from \"@fluidframework/protocol-definitions\";\n\n// This is used when we rehydrate a container from the snapshot. Here we put the blob contents\n// in separate property: blobContents.\nexport interface ISnapshotTreeWithBlobContents extends ISnapshotTree {\n blobsContents: {[path: string]: ArrayBufferLike},\n trees: {[path: string]: ISnapshotTreeWithBlobContents},\n}\n\nexport interface IParsedUrl {\n id: string;\n path: string;\n query: string;\n /**\n * Null means do not use snapshots, undefined means load latest snapshot\n * otherwise it's version ID passed to IDocumentStorageService.getVersions() to figure out what snapshot to use.\n * If needed, can add undefined which is treated by Container.load() as load latest snapshot.\n */\n version: string | null | undefined;\n}\n\nexport function parseUrl(url: string): IParsedUrl | undefined {\n const parsed = parse(url, true);\n if (typeof parsed.pathname !== \"string\") {\n throw new Error(\"Failed to parse pathname\");\n }\n const query = parsed.search ?? \"\";\n const regex = /^\\/([^/]*\\/[^/]*)(\\/?.*)$/;\n const match = regex.exec(parsed.pathname);\n return (match?.length === 3)\n ? { id: match[1], path: match[2], query, version: parsed.query.version as string }\n : undefined;\n}\n\n/**\n * Converts summary tree (for upload) to snapshot tree (for download).\n * Summary tree blobs contain contents, but snapshot tree blobs normally\n * contain IDs pointing to storage. This will create 2 blob entries in the\n * snapshot tree for each blob in the summary tree. One will be the regular\n * path pointing to a uniquely generated ID. Then there will be another\n * entry with the path as that uniquely generated ID, and value as the\n * blob contents as a base-64 string.\n * @param summary - summary to convert\n */\nfunction convertSummaryToSnapshotWithEmbeddedBlobContents(\n summary: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n const treeNode: ISnapshotTreeWithBlobContents = {\n blobs: {},\n blobsContents: {},\n trees: {},\n commits: {},\n id: uuid(),\n unreferenced: summary.unreferenced,\n };\n const keys = Object.keys(summary.tree);\n for (const key of keys) {\n const summaryObject = summary.tree[key];\n\n switch (summaryObject.type) {\n case SummaryType.Tree: {\n treeNode.trees[key] = convertSummaryToSnapshotWithEmbeddedBlobContents(summaryObject);\n break;\n }\n case SummaryType.Attachment:\n treeNode.blobs[key] = summaryObject.id;\n break;\n case SummaryType.Blob: {\n const blobId = uuid();\n treeNode.blobs[key] = blobId;\n const contentBuffer = typeof summaryObject.content === \"string\" ?\n stringToBuffer(summaryObject.content, \"utf8\") : Uint8ArrayToArrayBuffer(summaryObject.content);\n treeNode.blobsContents[blobId] = contentBuffer;\n break;\n }\n case SummaryType.Handle:\n throw new Error(\"No handles should be there in summary in detached container!!\");\n break;\n default: {\n unreachableCase(summaryObject, `Unknown tree type ${(summaryObject as any).type}`);\n }\n }\n }\n return treeNode;\n}\n\n/**\n * Combine and convert protocol and app summary tree to format which is readable by container while rehydrating.\n * @param protocolSummaryTree - Protocol Summary Tree\n * @param appSummaryTree - App Summary Tree\n */\nexport function convertProtocolAndAppSummaryToSnapshotTree(\n protocolSummaryTree: ISummaryTree,\n appSummaryTree: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n // Shallow copy is fine, since we are doing a deep clone below.\n const combinedSummary: ISummaryTree = {\n type: SummaryType.Tree,\n tree: { ...appSummaryTree.tree },\n };\n\n combinedSummary.tree[\".protocol\"] = protocolSummaryTree;\n const snapshotTreeWithBlobContents =\n convertSummaryToSnapshotWithEmbeddedBlobContents(combinedSummary);\n return snapshotTreeWithBlobContents;\n}\n\n// This function converts the snapshot taken in detached container(by serialize api) to snapshotTree with which\n// a detached container can be rehydrated.\nexport const getSnapshotTreeFromSerializedContainer = (detachedContainerSnapshot: ISummaryTree) => {\n const protocolSummaryTree = detachedContainerSnapshot.tree[\".protocol\"] as ISummaryTree;\n const appSummaryTree = detachedContainerSnapshot.tree[\".app\"] as ISummaryTree;\n assert(protocolSummaryTree !== undefined && appSummaryTree !== undefined,\n 0x1e0 /* \"Protocol and App summary trees should be present\" */);\n const snapshotTreeWithBlobContents = convertProtocolAndAppSummaryToSnapshotTree(\n protocolSummaryTree,\n appSummaryTree,\n );\n return snapshotTreeWithBlobContents;\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/container-loader",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0-38142",
|
|
4
4
|
"description": "Fluid container loader",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": "https://github.com/microsoft/FluidFramework",
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
"@fluidframework/common-definitions": "^0.20.1",
|
|
60
60
|
"@fluidframework/common-utils": "^0.32.1",
|
|
61
61
|
"@fluidframework/container-definitions": "^0.39.8",
|
|
62
|
-
"@fluidframework/container-utils": "0.
|
|
62
|
+
"@fluidframework/container-utils": "0.48.0-38142",
|
|
63
63
|
"@fluidframework/core-interfaces": "^0.39.7",
|
|
64
64
|
"@fluidframework/driver-definitions": "^0.39.6",
|
|
65
|
-
"@fluidframework/driver-utils": "0.
|
|
66
|
-
"@fluidframework/protocol-base": "^0.
|
|
65
|
+
"@fluidframework/driver-utils": "0.48.0-38142",
|
|
66
|
+
"@fluidframework/protocol-base": "^0.1031.0-37526",
|
|
67
67
|
"@fluidframework/protocol-definitions": "^0.1024.0",
|
|
68
|
-
"@fluidframework/telemetry-utils": "0.
|
|
68
|
+
"@fluidframework/telemetry-utils": "0.48.0-38142",
|
|
69
69
|
"abort-controller": "^3.0.0",
|
|
70
70
|
"double-ended-queue": "^2.1.0-0",
|
|
71
71
|
"lodash": "^4.17.21",
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@fluidframework/build-common": "^0.23.0-0",
|
|
76
76
|
"@fluidframework/eslint-config-fluid": "^0.23.0",
|
|
77
|
-
"@fluidframework/mocha-test-setup": "0.
|
|
78
|
-
"@fluidframework/test-loader-utils": "0.
|
|
77
|
+
"@fluidframework/mocha-test-setup": "0.48.0-38142",
|
|
78
|
+
"@fluidframework/test-loader-utils": "0.48.0-38142",
|
|
79
79
|
"@microsoft/api-extractor": "^7.16.1",
|
|
80
80
|
"@types/double-ended-queue": "^2.1.0",
|
|
81
81
|
"@types/lodash": "^4.14.118",
|
package/src/container.ts
CHANGED
|
@@ -185,11 +185,13 @@ export async function waitContainerToCatchUp(container: Container) {
|
|
|
185
185
|
const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
|
|
186
186
|
|
|
187
187
|
const connectionOpSeqNumber = deltaManager.lastKnownSeqNumber;
|
|
188
|
+
assert(deltaManager.lastSequenceNumber <= connectionOpSeqNumber,
|
|
189
|
+
0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
|
|
188
190
|
if (deltaManager.lastSequenceNumber === connectionOpSeqNumber) {
|
|
189
191
|
accept(hasCheckpointSequenceNumber);
|
|
190
192
|
return;
|
|
191
193
|
}
|
|
192
|
-
const callbackOps = (message) => {
|
|
194
|
+
const callbackOps = (message: ISequencedDocumentMessage) => {
|
|
193
195
|
if (connectionOpSeqNumber <= message.sequenceNumber) {
|
|
194
196
|
accept(hasCheckpointSequenceNumber);
|
|
195
197
|
deltaManager.off("op", callbackOps);
|
|
@@ -643,6 +645,9 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
643
645
|
dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
|
|
644
646
|
dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
|
|
645
647
|
dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId,
|
|
648
|
+
connectionState: () => ConnectionState[this.connectionState],
|
|
649
|
+
connectionStateDuration:
|
|
650
|
+
() => performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
646
651
|
},
|
|
647
652
|
});
|
|
648
653
|
|
|
@@ -970,8 +975,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
970
975
|
}
|
|
971
976
|
|
|
972
977
|
public setAutoReconnect(reconnect: boolean) {
|
|
973
|
-
if (
|
|
974
|
-
throw new Error("Attempting to setAutoReconnect() a closed
|
|
978
|
+
if (this.closed) {
|
|
979
|
+
throw new Error("Attempting to setAutoReconnect() a closed Container");
|
|
975
980
|
}
|
|
976
981
|
|
|
977
982
|
this._deltaManager.setAutomaticReconnect(reconnect);
|
|
@@ -1234,42 +1239,30 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1234
1239
|
|
|
1235
1240
|
const attributes = await this.getDocumentAttributes(this.storageService, snapshot);
|
|
1236
1241
|
|
|
1237
|
-
// Attach op handlers to start processing ops
|
|
1238
|
-
this.attachDeltaManagerOpHandler(attributes);
|
|
1239
|
-
|
|
1240
|
-
// ...load in the existing quorum
|
|
1241
|
-
// Initialize the protocol handler
|
|
1242
|
-
const protocolHandlerP =
|
|
1243
|
-
this.loadAndInitializeProtocolState(attributes, this.storageService, snapshot);
|
|
1244
|
-
|
|
1245
1242
|
let opsBeforeReturnP: Promise<void> | undefined;
|
|
1246
1243
|
|
|
1247
|
-
//
|
|
1248
|
-
//
|
|
1244
|
+
// Attach op handlers to finish initialization and be able to start processing ops
|
|
1245
|
+
// Kick off any ops fetching if required.
|
|
1249
1246
|
switch (loadMode.opsBeforeReturn) {
|
|
1250
1247
|
case undefined:
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
this._deltaManager.preFetchOps(false);
|
|
1255
|
-
}
|
|
1248
|
+
// Start prefetch, but not set opsBeforeReturnP - boot is not blocked by it!
|
|
1249
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1250
|
+
this.attachDeltaManagerOpHandler(attributes, loadMode.deltaConnection !== "none" ? "all" : "none");
|
|
1256
1251
|
break;
|
|
1257
1252
|
case "cached":
|
|
1258
|
-
opsBeforeReturnP = this.
|
|
1259
|
-
// Keep going with fetching ops from storage once we have all cached ops in.
|
|
1260
|
-
// Ops processing will start once cached ops are in and and will stop when queue is empty
|
|
1261
|
-
// (which in most cases will happen when we are done processing cached ops)
|
|
1262
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1263
|
-
opsBeforeReturnP.then(async () => this._deltaManager.preFetchOps(false));
|
|
1253
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(attributes, "cached");
|
|
1264
1254
|
break;
|
|
1265
1255
|
case "all":
|
|
1266
|
-
opsBeforeReturnP = this.
|
|
1256
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(attributes, "all");
|
|
1267
1257
|
break;
|
|
1268
1258
|
default:
|
|
1269
1259
|
unreachableCase(loadMode.opsBeforeReturn);
|
|
1270
1260
|
}
|
|
1271
1261
|
|
|
1272
|
-
|
|
1262
|
+
// ...load in the existing quorum
|
|
1263
|
+
// Initialize the protocol handler
|
|
1264
|
+
this._protocolHandler =
|
|
1265
|
+
await this.loadAndInitializeProtocolState(attributes, this.storageService, snapshot);
|
|
1273
1266
|
|
|
1274
1267
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1275
1268
|
await this.instantiateContext(
|
|
@@ -1350,7 +1343,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1350
1343
|
const proposals: [number, ISequencedProposal, string[]][] = [];
|
|
1351
1344
|
const values: [string, ICommittedProposal][] = [["code", committedCodeProposal]];
|
|
1352
1345
|
|
|
1353
|
-
this.attachDeltaManagerOpHandler(attributes);
|
|
1346
|
+
await this.attachDeltaManagerOpHandler(attributes);
|
|
1354
1347
|
|
|
1355
1348
|
// Need to just seed the source data in the code quorum. Quorum itself is empty
|
|
1356
1349
|
this._protocolHandler = await this.initializeProtocolState(
|
|
@@ -1380,7 +1373,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1380
1373
|
this._storage.loadSnapshotForRehydratingContainer(snapshotTree);
|
|
1381
1374
|
const attributes = await this.getDocumentAttributes(this._storage, snapshotTree);
|
|
1382
1375
|
assert(attributes.sequenceNumber === 0, 0x0db /* "Seq number in detached container should be 0!!" */);
|
|
1383
|
-
this.attachDeltaManagerOpHandler(attributes);
|
|
1376
|
+
await this.attachDeltaManagerOpHandler(attributes);
|
|
1384
1377
|
|
|
1385
1378
|
// ...load in the existing quorum
|
|
1386
1379
|
// Initialize the protocol handler
|
|
@@ -1654,8 +1647,11 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1654
1647
|
return deltaManager;
|
|
1655
1648
|
}
|
|
1656
1649
|
|
|
1657
|
-
private attachDeltaManagerOpHandler(
|
|
1658
|
-
|
|
1650
|
+
private async attachDeltaManagerOpHandler(
|
|
1651
|
+
attributes: IDocumentAttributes,
|
|
1652
|
+
prefetchType?: "cached" | "all" | "none")
|
|
1653
|
+
{
|
|
1654
|
+
return this._deltaManager.attachOpHandler(
|
|
1659
1655
|
attributes.minimumSequenceNumber,
|
|
1660
1656
|
attributes.sequenceNumber,
|
|
1661
1657
|
attributes.term ?? 1,
|
|
@@ -1664,7 +1660,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1664
1660
|
processSignal: (message) => {
|
|
1665
1661
|
this.processSignal(message);
|
|
1666
1662
|
},
|
|
1667
|
-
}
|
|
1663
|
+
},
|
|
1664
|
+
prefetchType);
|
|
1668
1665
|
}
|
|
1669
1666
|
|
|
1670
1667
|
private logConnectionStateChangeTelemetry(
|
package/src/deltaManager.ts
CHANGED
|
@@ -380,13 +380,17 @@ export class DeltaManager
|
|
|
380
380
|
* about current or last connection (if there is no connection at the moment)
|
|
381
381
|
*/
|
|
382
382
|
public connectionProps(): ITelemetryProperties {
|
|
383
|
+
const common = {
|
|
384
|
+
sequenceNumber: this.lastSequenceNumber,
|
|
385
|
+
};
|
|
383
386
|
if (this.connection !== undefined) {
|
|
384
387
|
return {
|
|
385
|
-
|
|
388
|
+
...common,
|
|
386
389
|
connectionMode: this.connectionMode,
|
|
387
390
|
};
|
|
388
391
|
} else {
|
|
389
392
|
return {
|
|
393
|
+
...common,
|
|
390
394
|
// Report how many ops this client sent in last disconnected session
|
|
391
395
|
sentOps: this.clientSequenceNumber,
|
|
392
396
|
};
|
|
@@ -552,11 +556,12 @@ export class DeltaManager
|
|
|
552
556
|
/**
|
|
553
557
|
* Sets the sequence number from which inbound messages should be returned
|
|
554
558
|
*/
|
|
555
|
-
public attachOpHandler(
|
|
559
|
+
public async attachOpHandler(
|
|
556
560
|
minSequenceNumber: number,
|
|
557
561
|
sequenceNumber: number,
|
|
558
562
|
term: number,
|
|
559
563
|
handler: IDeltaHandlerStrategy,
|
|
564
|
+
prefetchType: "cached" | "all" | "none" = "none",
|
|
560
565
|
) {
|
|
561
566
|
this.initSequenceNumber = sequenceNumber;
|
|
562
567
|
this.lastProcessedSequenceNumber = sequenceNumber;
|
|
@@ -571,24 +576,36 @@ export class DeltaManager
|
|
|
571
576
|
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
572
577
|
assert(!!(this.handler as any), 0x0e3 /* "Newly set op handler is null/undefined!" */);
|
|
573
578
|
|
|
579
|
+
// There should be no pending fetch!
|
|
580
|
+
// This API is called right after attachOpHandler by Container.load().
|
|
581
|
+
// We might have connection already and it might have called fetchMissingDeltas() from
|
|
582
|
+
// setupNewSuccessfulConnection. But it should do nothing, because there is no way to fetch ops before
|
|
583
|
+
// we know snapshot sequence number that is set in attachOpHandler. So all such calls should be noop.
|
|
584
|
+
assert(this.fetchReason === undefined, "There can't be pending fetch that early in boot sequence!");
|
|
585
|
+
|
|
586
|
+
if (this.closed) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
574
590
|
this._inbound.resume();
|
|
575
591
|
this._inboundSignal.resume();
|
|
576
592
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
this.
|
|
593
|
+
if (prefetchType !== "none") {
|
|
594
|
+
const cacheOnly = prefetchType === "cached";
|
|
595
|
+
await this.fetchMissingDeltasCore("DocumentOpen", cacheOnly, this.lastQueuedSequenceNumber);
|
|
596
|
+
|
|
597
|
+
// Keep going with fetching ops from storage once we have all cached ops in.
|
|
598
|
+
// But do not block load and make this request async / not blocking this api.
|
|
599
|
+
// Ops processing will start once cached ops are in and and will stop when queue is empty
|
|
600
|
+
// (which in most cases will happen when we are done processing cached ops)
|
|
601
|
+
if (cacheOnly) {
|
|
602
|
+
// fire and forget
|
|
603
|
+
this.fetchMissingDeltas("DocumentOpen", this.lastQueuedSequenceNumber);
|
|
604
|
+
}
|
|
583
605
|
}
|
|
584
|
-
}
|
|
585
606
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
// If we did, then we proactively fetch ops at the end of setupNewSuccessfulConnection to ensure
|
|
589
|
-
if (this.connection === undefined) {
|
|
590
|
-
return this.fetchMissingDeltasCore("DocumentOpen", cacheOnly, this.lastQueuedSequenceNumber, undefined);
|
|
591
|
-
}
|
|
607
|
+
// Ensure there is no need to call this.processPendingOps() at the end of boot sequence
|
|
608
|
+
assert(this.fetchReason !== undefined || this.pending.length === 0, "pending ops are not dropped");
|
|
592
609
|
}
|
|
593
610
|
|
|
594
611
|
private static detailsFromConnection(connection: IDocumentDeltaConnection): IConnectionDetails {
|
|
@@ -630,6 +647,8 @@ export class DeltaManager
|
|
|
630
647
|
}
|
|
631
648
|
|
|
632
649
|
private async connectCore(args: IConnectionArgs): Promise<IDocumentDeltaConnection> {
|
|
650
|
+
assert(!this.closed, "not closed");
|
|
651
|
+
|
|
633
652
|
if (this.connection !== undefined) {
|
|
634
653
|
return this.connection;
|
|
635
654
|
}
|
|
@@ -664,7 +683,7 @@ export class DeltaManager
|
|
|
664
683
|
this.handler !== undefined || !fetchOpsFromStorage,
|
|
665
684
|
this.logger,
|
|
666
685
|
"CantFetchWithoutBaseline"); // can't fetch if no baseline
|
|
667
|
-
if (fetchOpsFromStorage
|
|
686
|
+
if (fetchOpsFromStorage) {
|
|
668
687
|
this.fetchMissingDeltas(args.reason, this.lastQueuedSequenceNumber);
|
|
669
688
|
}
|
|
670
689
|
|
|
@@ -1037,15 +1056,7 @@ export class DeltaManager
|
|
|
1037
1056
|
// check message.content for Back-compat with old service.
|
|
1038
1057
|
const reconnectInfo = message.content !== undefined
|
|
1039
1058
|
? getNackReconnectInfo(message.content) :
|
|
1040
|
-
createGenericNetworkError(
|
|
1041
|
-
|
|
1042
|
-
if (this.reconnectMode !== ReconnectMode.Enabled) {
|
|
1043
|
-
this.logger.sendErrorEvent({
|
|
1044
|
-
eventName: "NackWithNoReconnect",
|
|
1045
|
-
reason: reconnectInfo.message,
|
|
1046
|
-
mode: this.connectionMode,
|
|
1047
|
-
});
|
|
1048
|
-
}
|
|
1059
|
+
createGenericNetworkError("nack:UnknownReason", true);
|
|
1049
1060
|
|
|
1050
1061
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1051
1062
|
this.reconnectOnError(
|
|
@@ -1417,7 +1428,7 @@ export class DeltaManager
|
|
|
1417
1428
|
const message2 = this.comparableMessagePayload(message);
|
|
1418
1429
|
if (message1 !== message2) {
|
|
1419
1430
|
const error = new NonRetryableError(
|
|
1420
|
-
"
|
|
1431
|
+
"twoMessagesWithSameSeqNumAndDifferentPayload",
|
|
1421
1432
|
DriverErrorType.fileOverwrittenInStorage,
|
|
1422
1433
|
{
|
|
1423
1434
|
clientId: this.connection?.clientId,
|
|
@@ -1513,6 +1524,10 @@ export class DeltaManager
|
|
|
1513
1524
|
}
|
|
1514
1525
|
this.lastProcessedSequenceNumber = message.sequenceNumber;
|
|
1515
1526
|
|
|
1527
|
+
// a bunch of code assumes that this is true
|
|
1528
|
+
assert(this.lastProcessedSequenceNumber <= this.lastObservedSeqNumber,
|
|
1529
|
+
0x267 /* "lastObservedSeqNumber should be updated first" */);
|
|
1530
|
+
|
|
1516
1531
|
// Back-compat for older server with no term
|
|
1517
1532
|
if (message.term === undefined) {
|
|
1518
1533
|
message.term = 1;
|
|
@@ -1525,6 +1540,9 @@ export class DeltaManager
|
|
|
1525
1540
|
this.handler.process(message);
|
|
1526
1541
|
|
|
1527
1542
|
const endTime = Date.now();
|
|
1543
|
+
|
|
1544
|
+
// Should be last, after changing this.lastProcessedSequenceNumber above, as many callers
|
|
1545
|
+
// test this.lastProcessedSequenceNumber instead of using op.sequenceNumber itself.
|
|
1528
1546
|
this.emit("op", message, endTime - startTime);
|
|
1529
1547
|
}
|
|
1530
1548
|
|
|
@@ -1532,8 +1550,9 @@ export class DeltaManager
|
|
|
1532
1550
|
* Retrieves the missing deltas between the given sequence numbers
|
|
1533
1551
|
*/
|
|
1534
1552
|
private fetchMissingDeltas(reasonArg: string, lastKnowOp: number, to?: number) {
|
|
1535
|
-
|
|
1536
|
-
|
|
1553
|
+
this.fetchMissingDeltasCore(reasonArg, false /* cacheOnly */, lastKnowOp, to).catch((error) => {
|
|
1554
|
+
this.logger.sendErrorEvent({ eventName: "fetchMissingDeltasException" }, error);
|
|
1555
|
+
});
|
|
1537
1556
|
}
|
|
1538
1557
|
|
|
1539
1558
|
/**
|
|
@@ -1555,6 +1574,12 @@ export class DeltaManager
|
|
|
1555
1574
|
return;
|
|
1556
1575
|
}
|
|
1557
1576
|
|
|
1577
|
+
if (this.handler === undefined) {
|
|
1578
|
+
// We do not poses yet any information
|
|
1579
|
+
assert(lastKnowOp === 0, "initial state");
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1558
1583
|
try {
|
|
1559
1584
|
assert(lastKnowOp === this.lastQueuedSequenceNumber, 0x0f1 /* "from arg" */);
|
|
1560
1585
|
let from = lastKnowOp + 1;
|
|
@@ -1596,12 +1621,40 @@ export class DeltaManager
|
|
|
1596
1621
|
* Sorts pending ops and attempts to apply them
|
|
1597
1622
|
*/
|
|
1598
1623
|
private processPendingOps(reason?: string): void {
|
|
1599
|
-
if (this.
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1624
|
+
if (this.closed) {
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
assert(this.handler !== undefined, "handler should be installed");
|
|
1629
|
+
|
|
1630
|
+
const pendingSorted = this.pending.sort((a, b) => a.sequenceNumber - b.sequenceNumber);
|
|
1631
|
+
this.pending = [];
|
|
1632
|
+
// Given that we do not track where these ops came from any more, it's not very
|
|
1633
|
+
// actionably to report gaps in this range.
|
|
1634
|
+
this.enqueueMessages(pendingSorted, `${reason}_pending`, true /* allowGaps */);
|
|
1635
|
+
|
|
1636
|
+
// Re-entrancy is ignored by fetchMissingDeltas, execution will come here when it's over
|
|
1637
|
+
if (this.fetchReason === undefined) {
|
|
1638
|
+
// See issue #7312 for more details
|
|
1639
|
+
// We observe cases where client gets into situation where it is not aware of missing ops
|
|
1640
|
+
// (i.e. client being behind), and as such, does not attempt to fetch them.
|
|
1641
|
+
// In some cases client may not have enough signal (example - "read" connection that is silent -
|
|
1642
|
+
// there is no easy way for client to realize it's behind, see a bit of commentary / logic at the
|
|
1643
|
+
// end of setupNewSuccessfulConnection). In other cases it should be able to learn that info ("write"
|
|
1644
|
+
// connection, learn by receiving its own join op), but data suggest it does not happen.
|
|
1645
|
+
// In 50% of these cases we do know we are behind through checkpointSequenceNumber on connection object
|
|
1646
|
+
// and thus can leverage that to trigger recovery. But this is not going to solve all the problems
|
|
1647
|
+
// (the other 50%), and thus these errors below should be looked at even if code below results in
|
|
1648
|
+
// recovery.
|
|
1649
|
+
if (this.lastQueuedSequenceNumber < this.lastObservedSeqNumber) {
|
|
1650
|
+
// connectionMode === "read" case is too noisy, so not log it.
|
|
1651
|
+
// It happens because fetch in setupNewSuccessfulConnection get cancelled due to other fetch, and we
|
|
1652
|
+
// never retry (other than here)
|
|
1653
|
+
if (this.connectionMode === "write") {
|
|
1654
|
+
this.logConnectionIssue({ eventName: "OpsBehind" });
|
|
1655
|
+
}
|
|
1656
|
+
this.fetchMissingDeltas("OpsBehind", this.lastQueuedSequenceNumber);
|
|
1657
|
+
}
|
|
1605
1658
|
}
|
|
1606
1659
|
}
|
|
1607
1660
|
|
package/src/packageVersion.ts
CHANGED
package/src/utils.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { parse } from "url";
|
|
|
7
7
|
import { v4 as uuid } from "uuid";
|
|
8
8
|
import {
|
|
9
9
|
assert,
|
|
10
|
-
bufferToString,
|
|
11
10
|
stringToBuffer,
|
|
12
11
|
Uint8ArrayToArrayBuffer,
|
|
13
12
|
unreachableCase,
|
|
@@ -85,9 +84,6 @@ function convertSummaryToSnapshotWithEmbeddedBlobContents(
|
|
|
85
84
|
const contentBuffer = typeof summaryObject.content === "string" ?
|
|
86
85
|
stringToBuffer(summaryObject.content, "utf8") : Uint8ArrayToArrayBuffer(summaryObject.content);
|
|
87
86
|
treeNode.blobsContents[blobId] = contentBuffer;
|
|
88
|
-
// 0.43 back-compat old runtime will still expect content in the blobs only.
|
|
89
|
-
// So need to put in blobs for now.
|
|
90
|
-
treeNode.blobs[blobId] = bufferToString(contentBuffer, "base64");
|
|
91
87
|
break;
|
|
92
88
|
}
|
|
93
89
|
case SummaryType.Handle:
|