@fluidframework/driver-utils 2.0.0-internal.3.0.1 → 2.0.0-internal.3.1.0
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/.eslintrc.js +17 -19
- package/.mocharc.js +2 -2
- package/api-extractor.json +2 -2
- package/dist/blobAggregationStorage.d.ts +2 -2
- package/dist/blobAggregationStorage.d.ts.map +1 -1
- package/dist/blobAggregationStorage.js +11 -6
- package/dist/blobAggregationStorage.js.map +1 -1
- package/dist/blobCacheStorageService.d.ts.map +1 -1
- package/dist/blobCacheStorageService.js.map +1 -1
- package/dist/buildSnapshotTree.d.ts.map +1 -1
- package/dist/buildSnapshotTree.js.map +1 -1
- package/dist/documentStorageServiceProxy.d.ts.map +1 -1
- package/dist/documentStorageServiceProxy.js.map +1 -1
- package/dist/emptyDocumentDeltaStorageService.d.ts.map +1 -1
- package/dist/emptyDocumentDeltaStorageService.js.map +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js.map +1 -1
- package/dist/fluidResolvedUrl.d.ts.map +1 -1
- package/dist/fluidResolvedUrl.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/insecureUrlResolver.d.ts.map +1 -1
- package/dist/insecureUrlResolver.js +2 -1
- package/dist/insecureUrlResolver.js.map +1 -1
- package/dist/mapWithExpiration.d.ts.map +1 -1
- package/dist/mapWithExpiration.js +5 -3
- package/dist/mapWithExpiration.js.map +1 -1
- package/dist/messageRecognition.d.ts.map +1 -1
- package/dist/messageRecognition.js.map +1 -1
- package/dist/multiDocumentServiceFactory.d.ts.map +1 -1
- package/dist/multiDocumentServiceFactory.js.map +1 -1
- package/dist/multiUrlResolver.d.ts.map +1 -1
- package/dist/multiUrlResolver.js.map +1 -1
- package/dist/network.d.ts +1 -1
- package/dist/network.d.ts.map +1 -1
- package/dist/network.js +4 -3
- package/dist/network.js.map +1 -1
- package/dist/networkUtils.d.ts.map +1 -1
- package/dist/networkUtils.js +2 -3
- package/dist/networkUtils.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/parallelRequests.d.ts.map +1 -1
- package/dist/parallelRequests.js +19 -9
- package/dist/parallelRequests.js.map +1 -1
- package/dist/prefetchDocumentStorageService.d.ts.map +1 -1
- package/dist/prefetchDocumentStorageService.js.map +1 -1
- package/dist/rateLimiter.d.ts.map +1 -1
- package/dist/rateLimiter.js.map +1 -1
- package/dist/readAndParse.d.ts.map +1 -1
- package/dist/readAndParse.js.map +1 -1
- package/dist/runWithRetry.d.ts.map +1 -1
- package/dist/runWithRetry.js.map +1 -1
- package/dist/summaryForCreateNew.d.ts.map +1 -1
- package/dist/summaryForCreateNew.js.map +1 -1
- package/dist/treeConversions.d.ts.map +1 -1
- package/dist/treeConversions.js +1 -4
- package/dist/treeConversions.js.map +1 -1
- package/dist/treeUtils.d.ts +10 -10
- package/dist/treeUtils.d.ts.map +1 -1
- package/dist/treeUtils.js +10 -10
- package/dist/treeUtils.js.map +1 -1
- package/lib/blobAggregationStorage.d.ts +2 -2
- package/lib/blobAggregationStorage.d.ts.map +1 -1
- package/lib/blobAggregationStorage.js +11 -6
- package/lib/blobAggregationStorage.js.map +1 -1
- package/lib/blobCacheStorageService.d.ts.map +1 -1
- package/lib/blobCacheStorageService.js.map +1 -1
- package/lib/buildSnapshotTree.d.ts.map +1 -1
- package/lib/buildSnapshotTree.js.map +1 -1
- package/lib/documentStorageServiceProxy.d.ts.map +1 -1
- package/lib/documentStorageServiceProxy.js.map +1 -1
- package/lib/emptyDocumentDeltaStorageService.d.ts.map +1 -1
- package/lib/emptyDocumentDeltaStorageService.js.map +1 -1
- package/lib/error.d.ts.map +1 -1
- package/lib/error.js.map +1 -1
- package/lib/fluidResolvedUrl.d.ts.map +1 -1
- package/lib/fluidResolvedUrl.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/insecureUrlResolver.d.ts.map +1 -1
- package/lib/insecureUrlResolver.js +2 -1
- package/lib/insecureUrlResolver.js.map +1 -1
- package/lib/mapWithExpiration.d.ts.map +1 -1
- package/lib/mapWithExpiration.js +5 -3
- package/lib/mapWithExpiration.js.map +1 -1
- package/lib/messageRecognition.d.ts.map +1 -1
- package/lib/messageRecognition.js +1 -1
- package/lib/messageRecognition.js.map +1 -1
- package/lib/multiDocumentServiceFactory.d.ts.map +1 -1
- package/lib/multiDocumentServiceFactory.js.map +1 -1
- package/lib/multiUrlResolver.d.ts.map +1 -1
- package/lib/multiUrlResolver.js.map +1 -1
- package/lib/network.d.ts +1 -1
- package/lib/network.d.ts.map +1 -1
- package/lib/network.js +4 -3
- package/lib/network.js.map +1 -1
- package/lib/networkUtils.d.ts.map +1 -1
- package/lib/networkUtils.js +2 -3
- package/lib/networkUtils.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/parallelRequests.d.ts.map +1 -1
- package/lib/parallelRequests.js +19 -9
- package/lib/parallelRequests.js.map +1 -1
- package/lib/prefetchDocumentStorageService.d.ts.map +1 -1
- package/lib/prefetchDocumentStorageService.js.map +1 -1
- package/lib/rateLimiter.d.ts.map +1 -1
- package/lib/rateLimiter.js.map +1 -1
- package/lib/readAndParse.d.ts.map +1 -1
- package/lib/readAndParse.js.map +1 -1
- package/lib/runWithRetry.d.ts.map +1 -1
- package/lib/runWithRetry.js.map +1 -1
- package/lib/summaryForCreateNew.d.ts.map +1 -1
- package/lib/summaryForCreateNew.js.map +1 -1
- package/lib/treeConversions.d.ts.map +1 -1
- package/lib/treeConversions.js +4 -7
- package/lib/treeConversions.js.map +1 -1
- package/lib/treeUtils.d.ts +10 -10
- package/lib/treeUtils.d.ts.map +1 -1
- package/lib/treeUtils.js +11 -11
- package/lib/treeUtils.js.map +1 -1
- package/package.json +128 -105
- package/prettier.config.cjs +1 -1
- package/src/blobAggregationStorage.ts +374 -322
- package/src/blobCacheStorageService.ts +20 -17
- package/src/buildSnapshotTree.ts +54 -51
- package/src/documentStorageServiceProxy.ts +49 -43
- package/src/emptyDocumentDeltaStorageService.ts +11 -10
- package/src/error.ts +5 -7
- package/src/fluidResolvedUrl.ts +9 -6
- package/src/index.ts +5 -1
- package/src/insecureUrlResolver.ts +127 -116
- package/src/mapWithExpiration.ts +111 -104
- package/src/messageRecognition.ts +25 -19
- package/src/multiDocumentServiceFactory.ts +73 -62
- package/src/multiUrlResolver.ts +26 -29
- package/src/network.ts +114 -112
- package/src/networkUtils.ts +37 -34
- package/src/packageVersion.ts +1 -1
- package/src/parallelRequests.ts +571 -509
- package/src/prefetchDocumentStorageService.ts +76 -74
- package/src/rateLimiter.ts +29 -29
- package/src/readAndParse.ts +7 -4
- package/src/runWithRetry.ts +105 -94
- package/src/summaryForCreateNew.ts +27 -24
- package/src/treeConversions.ts +48 -70
- package/src/treeUtils.ts +70 -74
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +8 -12
|
@@ -3,90 +3,92 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
import { LoaderCachingPolicy } from "@fluidframework/driver-definitions";
|
|
6
|
-
import {
|
|
7
|
-
ISnapshotTree,
|
|
8
|
-
IVersion,
|
|
9
|
-
} from "@fluidframework/protocol-definitions";
|
|
6
|
+
import { ISnapshotTree, IVersion } from "@fluidframework/protocol-definitions";
|
|
10
7
|
import { DocumentStorageServiceProxy } from "./documentStorageServiceProxy";
|
|
11
8
|
import { canRetryOnError } from "./network";
|
|
12
9
|
|
|
13
10
|
export class PrefetchDocumentStorageService extends DocumentStorageServiceProxy {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
// BlobId -> blob prefetchCache cache
|
|
12
|
+
private readonly prefetchCache = new Map<string, Promise<ArrayBufferLike>>();
|
|
13
|
+
private prefetchEnabled = true;
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
public get policies() {
|
|
16
|
+
const policies = this.internalStorageService.policies;
|
|
17
|
+
if (policies) {
|
|
18
|
+
return { ...policies, caching: LoaderCachingPolicy.NoCaching };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
public async getSnapshotTree(version?: IVersion): Promise<ISnapshotTree | null> {
|
|
23
|
+
const p = this.internalStorageService.getSnapshotTree(version);
|
|
24
|
+
if (this.prefetchEnabled) {
|
|
25
|
+
// We don't care if the prefetch succeeds
|
|
26
|
+
void p.then((tree: ISnapshotTree | null | undefined) => {
|
|
27
|
+
if (tree === null || tree === undefined) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.prefetchTree(tree);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return p;
|
|
34
|
+
}
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
public async readBlob(blobId: string): Promise<ArrayBufferLike> {
|
|
37
|
+
return this.cachedRead(blobId);
|
|
38
|
+
}
|
|
39
|
+
public stopPrefetch() {
|
|
40
|
+
this.prefetchEnabled = false;
|
|
41
|
+
this.prefetchCache.clear();
|
|
42
|
+
}
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
44
|
+
private async cachedRead(blobId: string): Promise<ArrayBufferLike> {
|
|
45
|
+
if (this.prefetchEnabled) {
|
|
46
|
+
const prefetchedBlobP = this.prefetchCache.get(blobId);
|
|
47
|
+
if (prefetchedBlobP !== undefined) {
|
|
48
|
+
return prefetchedBlobP;
|
|
49
|
+
}
|
|
50
|
+
const prefetchedBlobPFromStorage = this.internalStorageService.readBlob(blobId);
|
|
51
|
+
this.prefetchCache.set(
|
|
52
|
+
blobId,
|
|
53
|
+
prefetchedBlobPFromStorage.catch((error) => {
|
|
54
|
+
if (canRetryOnError(error)) {
|
|
55
|
+
this.prefetchCache.delete(blobId);
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
return prefetchedBlobPFromStorage;
|
|
61
|
+
}
|
|
62
|
+
return this.internalStorageService.readBlob(blobId);
|
|
63
|
+
}
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
private prefetchTree(tree: ISnapshotTree) {
|
|
66
|
+
const secondary: string[] = [];
|
|
67
|
+
this.prefetchTreeCore(tree, secondary);
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
for (const blob of secondary) {
|
|
70
|
+
// We don't care if the prefetch succeeds
|
|
71
|
+
void this.cachedRead(blob);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
75
|
+
private prefetchTreeCore(tree: ISnapshotTree, secondary: string[]) {
|
|
76
|
+
for (const blobKey of Object.keys(tree.blobs)) {
|
|
77
|
+
const blob = tree.blobs[blobKey];
|
|
78
|
+
if (blobKey.startsWith(".") || blobKey === "header" || blobKey.startsWith("quorum")) {
|
|
79
|
+
if (blob !== null) {
|
|
80
|
+
// We don't care if the prefetch succeeds
|
|
81
|
+
void this.cachedRead(blob);
|
|
82
|
+
}
|
|
83
|
+
} else if (!blobKey.startsWith("deltas")) {
|
|
84
|
+
if (blob !== null) {
|
|
85
|
+
secondary.push(blob);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
for (const subTree of Object.keys(tree.trees)) {
|
|
91
|
+
this.prefetchTreeCore(tree.trees[subTree], secondary);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
92
94
|
}
|
package/src/rateLimiter.ts
CHANGED
|
@@ -6,38 +6,38 @@
|
|
|
6
6
|
import { assert } from "@fluidframework/common-utils";
|
|
7
7
|
|
|
8
8
|
export class RateLimiter {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
private readonly tasks: (() => void)[] = [];
|
|
10
|
+
constructor(private maxRequests: number) {
|
|
11
|
+
assert(maxRequests > 0, 0x0ae /* "Tried to create rate limiter with 0 max requests!" */);
|
|
12
|
+
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
public get waitQueueLength(): number {
|
|
15
|
+
return this.tasks.length;
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
// Run when one of the tasks finished running.
|
|
19
|
+
// Release next task if we have one, or allow more tasks to run in future.
|
|
20
|
+
protected readonly release = () => {
|
|
21
|
+
const task = this.tasks.shift();
|
|
22
|
+
if (task !== undefined) {
|
|
23
|
+
return task();
|
|
24
|
+
}
|
|
25
|
+
this.maxRequests++;
|
|
26
|
+
};
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
protected async acquire() {
|
|
29
|
+
if (this.maxRequests > 0) {
|
|
30
|
+
this.maxRequests--;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
return new Promise<void>((resolve) => {
|
|
35
|
+
this.tasks.push(resolve);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
public async schedule<T>(work: () => Promise<T>) {
|
|
40
|
+
await this.acquire();
|
|
41
|
+
return work().finally(this.release);
|
|
42
|
+
}
|
|
43
43
|
}
|
package/src/readAndParse.ts
CHANGED
|
@@ -18,8 +18,11 @@ import { IDocumentStorageService } from "@fluidframework/driver-definitions";
|
|
|
18
18
|
*
|
|
19
19
|
* @returns The object that we decoded and parsed via `JSON.parse`.
|
|
20
20
|
*/
|
|
21
|
-
export async function readAndParse<T>(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
export async function readAndParse<T>(
|
|
22
|
+
storage: Pick<IDocumentStorageService, "readBlob">,
|
|
23
|
+
id: string,
|
|
24
|
+
): Promise<T> {
|
|
25
|
+
const blob = await storage.readBlob(id);
|
|
26
|
+
const decoded = bufferToString(blob, "utf8");
|
|
27
|
+
return JSON.parse(decoded) as T;
|
|
25
28
|
}
|
package/src/runWithRetry.ts
CHANGED
|
@@ -15,106 +15,117 @@ import { NonRetryableError } from ".";
|
|
|
15
15
|
* It allows caller to control cancellation, as well as learn about any delays.
|
|
16
16
|
*/
|
|
17
17
|
export interface IProgress {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Abort signal used to cancel operation.
|
|
20
|
+
*
|
|
21
|
+
* @remarks Note that most of the layers do not use this signal yet. We need to change that over time.
|
|
22
|
+
* Please consult with API documentation / implementation.
|
|
23
|
+
* Note that number of layers may not check this signal while holding this request in a queue,
|
|
24
|
+
* so it may take a while it takes effect. This can be improved in the future.
|
|
25
|
+
*
|
|
26
|
+
* The layers in question are:
|
|
27
|
+
*
|
|
28
|
+
* - driver (RateLimiter)
|
|
29
|
+
*
|
|
30
|
+
* - runWithRetry
|
|
31
|
+
*/
|
|
32
|
+
cancel?: AbortSignal;
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Called whenever api returns cancellable error and the call is going to be retried.
|
|
36
|
+
* Any exception thrown from this call back result in cancellation of operation
|
|
37
|
+
* and propagation of thrown exception.
|
|
38
|
+
* @param delayInMs - delay before next retry. This value will depend on internal back-off logic,
|
|
39
|
+
* as well as information provided by service (like 429 error asking to wait for some time before retry)
|
|
40
|
+
* @param error - error object returned from the call.
|
|
41
|
+
*/
|
|
42
|
+
onRetry?(delayInMs: number, error: any): void;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
export async function runWithRetry<T>(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
api: (cancel?: AbortSignal) => Promise<T>,
|
|
47
|
+
fetchCallName: string,
|
|
48
|
+
logger: ITelemetryLogger,
|
|
49
|
+
progress: IProgress,
|
|
50
50
|
): Promise<T> {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
51
|
+
let result: T | undefined;
|
|
52
|
+
let success = false;
|
|
53
|
+
let retryAfterMs = 1000; // has to be positive!
|
|
54
|
+
let numRetries = 0;
|
|
55
|
+
const startTime = performance.now();
|
|
56
|
+
let lastError: any;
|
|
57
|
+
do {
|
|
58
|
+
try {
|
|
59
|
+
result = await api(progress.cancel);
|
|
60
|
+
success = true;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// If it is not retriable, then just throw the error.
|
|
63
|
+
if (!canRetryOnError(err)) {
|
|
64
|
+
logger.sendTelemetryEvent(
|
|
65
|
+
{
|
|
66
|
+
eventName: `${fetchCallName}_cancel`,
|
|
67
|
+
retry: numRetries,
|
|
68
|
+
duration: performance.now() - startTime,
|
|
69
|
+
fetchCallName,
|
|
70
|
+
},
|
|
71
|
+
err,
|
|
72
|
+
);
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
if (progress.cancel?.aborted === true) {
|
|
77
|
+
logger.sendTelemetryEvent(
|
|
78
|
+
{
|
|
79
|
+
eventName: `${fetchCallName}_runWithRetryAborted`,
|
|
80
|
+
retry: numRetries,
|
|
81
|
+
duration: performance.now() - startTime,
|
|
82
|
+
fetchCallName,
|
|
83
|
+
},
|
|
84
|
+
err,
|
|
85
|
+
);
|
|
86
|
+
throw new NonRetryableError(
|
|
87
|
+
"runWithRetry was Aborted",
|
|
88
|
+
DriverErrorType.genericError,
|
|
89
|
+
{ driverVersion: pkgVersion, fetchCallName },
|
|
90
|
+
);
|
|
91
|
+
}
|
|
86
92
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
// logging the first failed retry instead of every attempt. We want to avoid filling telemetry
|
|
94
|
+
// when we have tight loop of retrying in offline mode, but we also want to know what caused
|
|
95
|
+
// the failure in the first place
|
|
96
|
+
if (numRetries === 0) {
|
|
97
|
+
logger.sendTelemetryEvent(
|
|
98
|
+
{
|
|
99
|
+
eventName: `${fetchCallName}_firstFailed`,
|
|
100
|
+
duration: performance.now() - startTime,
|
|
101
|
+
fetchCallName,
|
|
102
|
+
},
|
|
103
|
+
err,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
97
106
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
107
|
+
numRetries++;
|
|
108
|
+
lastError = err;
|
|
109
|
+
// If the error is throttling error, then wait for the specified time before retrying.
|
|
110
|
+
// If the waitTime is not specified, then we start with retrying immediately to max of 8s.
|
|
111
|
+
retryAfterMs = getRetryDelayFromError(err) ?? Math.min(retryAfterMs * 2, 8000);
|
|
112
|
+
if (progress.onRetry) {
|
|
113
|
+
progress.onRetry(retryAfterMs, err);
|
|
114
|
+
}
|
|
115
|
+
await delay(retryAfterMs);
|
|
116
|
+
}
|
|
117
|
+
} while (!success);
|
|
118
|
+
if (numRetries > 0) {
|
|
119
|
+
logger.sendTelemetryEvent(
|
|
120
|
+
{
|
|
121
|
+
eventName: `${fetchCallName}_lastError`,
|
|
122
|
+
retry: numRetries,
|
|
123
|
+
duration: performance.now() - startTime,
|
|
124
|
+
fetchCallName,
|
|
125
|
+
},
|
|
126
|
+
lastError,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
130
|
+
return result!;
|
|
120
131
|
}
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
ISummaryTree,
|
|
8
|
+
SummaryType,
|
|
9
|
+
ISummaryBlob,
|
|
10
|
+
ICommittedProposal,
|
|
11
|
+
IDocumentAttributes,
|
|
12
12
|
} from "@fluidframework/protocol-definitions";
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -17,17 +17,17 @@ import {
|
|
|
17
17
|
* @param protocolSummary - Summary of the protocol.
|
|
18
18
|
*/
|
|
19
19
|
export function combineAppAndProtocolSummary(
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
appSummary: ISummaryTree,
|
|
21
|
+
protocolSummary: ISummaryTree,
|
|
22
22
|
): ISummaryTree {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
const createNewSummary: ISummaryTree = {
|
|
24
|
+
type: SummaryType.Tree,
|
|
25
|
+
tree: {
|
|
26
|
+
".protocol": protocolSummary,
|
|
27
|
+
".app": appSummary,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
return createNewSummary;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -35,12 +35,12 @@ export function combineAppAndProtocolSummary(
|
|
|
35
35
|
* @param protocolSummary - protocol summary from which the values are to be extracted.
|
|
36
36
|
*/
|
|
37
37
|
export function getDocAttributesFromProtocolSummary(
|
|
38
|
-
|
|
38
|
+
protocolSummary: ISummaryTree,
|
|
39
39
|
): IDocumentAttributes {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
const attributesBlob = protocolSummary.tree.attributes as ISummaryBlob;
|
|
41
|
+
const documentAttributes = JSON.parse(attributesBlob.content as string) as IDocumentAttributes;
|
|
42
|
+
documentAttributes.term = documentAttributes.term ?? 1;
|
|
43
|
+
return documentAttributes;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -48,9 +48,12 @@ export function getDocAttributesFromProtocolSummary(
|
|
|
48
48
|
* @param protocolSummary - protocol summary from which the values are to be extracted.
|
|
49
49
|
*/
|
|
50
50
|
export function getQuorumValuesFromProtocolSummary(
|
|
51
|
-
|
|
51
|
+
protocolSummary: ISummaryTree,
|
|
52
52
|
): [string, ICommittedProposal][] {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const quorumValuesBlob = protocolSummary.tree.quorumValues as ISummaryBlob;
|
|
54
|
+
const quorumValues = JSON.parse(quorumValuesBlob.content as string) as [
|
|
55
|
+
string,
|
|
56
|
+
ICommittedProposal,
|
|
57
|
+
][];
|
|
58
|
+
return quorumValues;
|
|
56
59
|
}
|