@fluidframework/odsp-driver 0.52.1 → 0.54.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/dist/createFile.d.ts.map +1 -1
- package/dist/createFile.js +5 -4
- package/dist/createFile.js.map +1 -1
- package/dist/epochTracker.d.ts +2 -1
- package/dist/epochTracker.d.ts.map +1 -1
- package/dist/epochTracker.js +50 -21
- package/dist/epochTracker.js.map +1 -1
- package/dist/fetchSnapshot.d.ts.map +1 -1
- package/dist/fetchSnapshot.js +15 -14
- package/dist/fetchSnapshot.js.map +1 -1
- package/dist/getFileLink.js +3 -3
- package/dist/getFileLink.js.map +1 -1
- package/dist/odspDeltaStorageService.d.ts +3 -3
- package/dist/odspDeltaStorageService.d.ts.map +1 -1
- package/dist/odspDeltaStorageService.js +7 -4
- package/dist/odspDeltaStorageService.js.map +1 -1
- package/dist/odspDocumentDeltaConnection.d.ts.map +1 -1
- package/dist/odspDocumentDeltaConnection.js +2 -0
- package/dist/odspDocumentDeltaConnection.js.map +1 -1
- package/dist/odspDocumentService.d.ts +1 -1
- package/dist/odspDocumentService.d.ts.map +1 -1
- package/dist/odspDocumentService.js +13 -25
- package/dist/odspDocumentService.js.map +1 -1
- package/dist/odspDocumentStorageManager.d.ts.map +1 -1
- package/dist/odspDocumentStorageManager.js +29 -26
- package/dist/odspDocumentStorageManager.js.map +1 -1
- package/dist/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
- package/dist/odspDriverUrlResolverForShareLink.js +4 -3
- package/dist/odspDriverUrlResolverForShareLink.js.map +1 -1
- package/dist/odspError.d.ts.map +1 -1
- package/dist/odspError.js +3 -1
- package/dist/odspError.js.map +1 -1
- package/dist/odspSummaryUploadManager.d.ts +1 -1
- package/dist/odspSummaryUploadManager.d.ts.map +1 -1
- package/dist/odspSummaryUploadManager.js +7 -20
- package/dist/odspSummaryUploadManager.js.map +1 -1
- package/dist/odspUtils.d.ts.map +1 -1
- package/dist/odspUtils.js +31 -22
- package/dist/odspUtils.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/vroom.d.ts.map +1 -1
- package/dist/vroom.js +4 -1
- package/dist/vroom.js.map +1 -1
- package/dist/zipItDataRepresentationUtils.d.ts.map +1 -1
- package/dist/zipItDataRepresentationUtils.js +3 -3
- package/dist/zipItDataRepresentationUtils.js.map +1 -1
- package/lib/createFile.d.ts.map +1 -1
- package/lib/createFile.js +6 -5
- package/lib/createFile.js.map +1 -1
- package/lib/epochTracker.d.ts +2 -1
- package/lib/epochTracker.d.ts.map +1 -1
- package/lib/epochTracker.js +50 -21
- package/lib/epochTracker.js.map +1 -1
- package/lib/fetchSnapshot.d.ts.map +1 -1
- package/lib/fetchSnapshot.js +15 -14
- package/lib/fetchSnapshot.js.map +1 -1
- package/lib/getFileLink.js +4 -4
- package/lib/getFileLink.js.map +1 -1
- package/lib/odspDeltaStorageService.d.ts +3 -3
- package/lib/odspDeltaStorageService.d.ts.map +1 -1
- package/lib/odspDeltaStorageService.js +7 -4
- package/lib/odspDeltaStorageService.js.map +1 -1
- package/lib/odspDocumentDeltaConnection.d.ts.map +1 -1
- package/lib/odspDocumentDeltaConnection.js +2 -0
- package/lib/odspDocumentDeltaConnection.js.map +1 -1
- package/lib/odspDocumentService.d.ts +1 -1
- package/lib/odspDocumentService.d.ts.map +1 -1
- package/lib/odspDocumentService.js +15 -27
- package/lib/odspDocumentService.js.map +1 -1
- package/lib/odspDocumentStorageManager.d.ts.map +1 -1
- package/lib/odspDocumentStorageManager.js +31 -28
- package/lib/odspDocumentStorageManager.js.map +1 -1
- package/lib/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
- package/lib/odspDriverUrlResolverForShareLink.js +5 -4
- package/lib/odspDriverUrlResolverForShareLink.js.map +1 -1
- package/lib/odspError.d.ts.map +1 -1
- package/lib/odspError.js +3 -1
- package/lib/odspError.js.map +1 -1
- package/lib/odspSummaryUploadManager.d.ts +1 -1
- package/lib/odspSummaryUploadManager.d.ts.map +1 -1
- package/lib/odspSummaryUploadManager.js +8 -21
- package/lib/odspSummaryUploadManager.js.map +1 -1
- package/lib/odspUtils.d.ts.map +1 -1
- package/lib/odspUtils.js +33 -24
- package/lib/odspUtils.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/vroom.d.ts.map +1 -1
- package/lib/vroom.js +4 -1
- package/lib/vroom.js.map +1 -1
- package/lib/zipItDataRepresentationUtils.d.ts.map +1 -1
- package/lib/zipItDataRepresentationUtils.js +3 -3
- package/lib/zipItDataRepresentationUtils.js.map +1 -1
- package/package.json +8 -8
- package/src/createFile.ts +13 -9
- package/src/epochTracker.ts +50 -20
- package/src/fetchSnapshot.ts +15 -8
- package/src/getFileLink.ts +10 -4
- package/src/odspDeltaStorageService.ts +10 -3
- package/src/odspDocumentDeltaConnection.ts +2 -0
- package/src/odspDocumentService.ts +27 -29
- package/src/odspDocumentStorageManager.ts +48 -28
- package/src/odspDriverUrlResolverForShareLink.ts +8 -3
- package/src/odspError.ts +5 -1
- package/src/odspSummaryUploadManager.ts +7 -22
- package/src/odspUtils.ts +43 -34
- package/src/packageVersion.ts +1 -1
- package/src/vroom.ts +6 -1
- package/src/zipItDataRepresentationUtils.ts +4 -7
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
7
|
import { performance } from "@fluidframework/common-utils";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
ChildLogger,
|
|
10
|
+
loggerToMonitoringContext,
|
|
11
|
+
MonitoringContext,
|
|
12
|
+
} from "@fluidframework/telemetry-utils";
|
|
9
13
|
import {
|
|
10
14
|
IDocumentDeltaConnection,
|
|
11
15
|
IDocumentDeltaStorageService,
|
|
@@ -14,8 +18,8 @@ import {
|
|
|
14
18
|
IDocumentStorageService,
|
|
15
19
|
IDocumentServicePolicies,
|
|
16
20
|
} from "@fluidframework/driver-definitions";
|
|
17
|
-
import { DeltaStreamConnectionForbiddenError } from "@fluidframework/driver-utils";
|
|
18
|
-
import {
|
|
21
|
+
import { DeltaStreamConnectionForbiddenError, NonRetryableError } from "@fluidframework/driver-utils";
|
|
22
|
+
import { IFacetCodes } from "@fluidframework/odsp-doclib-utils";
|
|
19
23
|
import {
|
|
20
24
|
IClient,
|
|
21
25
|
ISequencedDocumentMessage,
|
|
@@ -26,6 +30,7 @@ import {
|
|
|
26
30
|
IEntry,
|
|
27
31
|
HostStoragePolicy,
|
|
28
32
|
InstrumentedStorageTokenFetcher,
|
|
33
|
+
OdspErrorType,
|
|
29
34
|
} from "@fluidframework/odsp-driver-definitions";
|
|
30
35
|
import { HostStoragePolicyInternal, ISocketStorageDiscovery } from "./contracts";
|
|
31
36
|
import { IOdspCache } from "./odspCache";
|
|
@@ -39,18 +44,6 @@ import { EpochTracker } from "./epochTracker";
|
|
|
39
44
|
import { OpsCache } from "./opsCaching";
|
|
40
45
|
import { RetryErrorsStorageAdapter } from "./retryErrorsStorageAdapter";
|
|
41
46
|
|
|
42
|
-
// Gate that when set to "1", instructs to fetch the binary format snapshot from the spo.
|
|
43
|
-
function gatesBinaryFormatSnapshot() {
|
|
44
|
-
try {
|
|
45
|
-
if (typeof localStorage === "object" && localStorage !== null) {
|
|
46
|
-
if (localStorage.binaryFormatSnapshot === "1") {
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
} catch (e) {}
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
47
|
/**
|
|
55
48
|
* The DocumentService manages the Socket.IO connection and manages routing requests to connected
|
|
56
49
|
* clients
|
|
@@ -98,7 +91,7 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
98
91
|
|
|
99
92
|
private storageManager?: OdspDocumentStorageService;
|
|
100
93
|
|
|
101
|
-
private readonly
|
|
94
|
+
private readonly mc: MonitoringContext;
|
|
102
95
|
|
|
103
96
|
private readonly joinSessionKey: string;
|
|
104
97
|
|
|
@@ -139,16 +132,18 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
139
132
|
};
|
|
140
133
|
|
|
141
134
|
this.joinSessionKey = `${this.odspResolvedUrl.hashedDocumentId}/joinsession`;
|
|
142
|
-
this.
|
|
135
|
+
this.mc = loggerToMonitoringContext(
|
|
136
|
+
ChildLogger.create(logger,
|
|
143
137
|
undefined,
|
|
144
138
|
{
|
|
145
139
|
all: {
|
|
146
140
|
odc: isOdcOrigin(new URL(this.odspResolvedUrl.endpoints.snapshotStorageUrl).origin),
|
|
147
141
|
},
|
|
148
|
-
});
|
|
142
|
+
}));
|
|
149
143
|
|
|
150
144
|
this.hostPolicy = hostPolicy;
|
|
151
|
-
this.hostPolicy.fetchBinarySnapshotFormat ??=
|
|
145
|
+
this.hostPolicy.fetchBinarySnapshotFormat ??=
|
|
146
|
+
this.mc.config.getBoolean("Fluid.Driver.Odsp.binaryFormatSnapshot");
|
|
152
147
|
if (this.odspResolvedUrl.summarizer) {
|
|
153
148
|
this.hostPolicy = { ...this.hostPolicy, summarizerClient: true };
|
|
154
149
|
}
|
|
@@ -171,7 +166,7 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
171
166
|
this.storageManager = new OdspDocumentStorageService(
|
|
172
167
|
this.odspResolvedUrl,
|
|
173
168
|
this.getStorageToken,
|
|
174
|
-
this.logger,
|
|
169
|
+
this.mc.logger,
|
|
175
170
|
true,
|
|
176
171
|
this.cache,
|
|
177
172
|
this.hostPolicy,
|
|
@@ -186,7 +181,7 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
186
181
|
);
|
|
187
182
|
}
|
|
188
183
|
|
|
189
|
-
return new RetryErrorsStorageAdapter(this.storageManager, this.logger);
|
|
184
|
+
return new RetryErrorsStorageAdapter(this.storageManager, this.mc.logger);
|
|
190
185
|
}
|
|
191
186
|
|
|
192
187
|
/**
|
|
@@ -200,7 +195,7 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
200
195
|
this.odspResolvedUrl.endpoints.deltaStorageUrl,
|
|
201
196
|
this.getStorageToken,
|
|
202
197
|
this.epochTracker,
|
|
203
|
-
this.logger,
|
|
198
|
+
this.mc.logger,
|
|
204
199
|
);
|
|
205
200
|
|
|
206
201
|
// batch size, please see issue #5211 for data around batch sizing
|
|
@@ -208,10 +203,10 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
208
203
|
const concurrency = this.hostPolicy.concurrentOpsBatches ?? 1;
|
|
209
204
|
return new OdspDeltaStorageWithCache(
|
|
210
205
|
snapshotOps,
|
|
211
|
-
this.logger,
|
|
206
|
+
this.mc.logger,
|
|
212
207
|
batchSize,
|
|
213
208
|
concurrency,
|
|
214
|
-
async (from, to, telemetryProps) => service.get(from, to, telemetryProps),
|
|
209
|
+
async (from, to, telemetryProps, fetchReason) => service.get(from, to, telemetryProps, fetchReason),
|
|
215
210
|
async (from, to) => {
|
|
216
211
|
const res = await this.opsCache?.get(from, to);
|
|
217
212
|
return res as ISequencedDocumentMessage[] ?? [];
|
|
@@ -270,7 +265,10 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
270
265
|
|
|
271
266
|
const finalWebsocketToken = websocketToken ?? (websocketEndpoint.socketToken || null);
|
|
272
267
|
if (finalWebsocketToken === null) {
|
|
273
|
-
|
|
268
|
+
throw new NonRetryableError(
|
|
269
|
+
"pushTokenIsNull",
|
|
270
|
+
"Websocket token is null",
|
|
271
|
+
OdspErrorType.fetchTokenError);
|
|
274
272
|
}
|
|
275
273
|
try {
|
|
276
274
|
const connection = await this.connectToDeltaStreamWithRetry(
|
|
@@ -304,7 +302,7 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
304
302
|
this.odspResolvedUrl,
|
|
305
303
|
"opStream/joinSession",
|
|
306
304
|
"POST",
|
|
307
|
-
this.logger,
|
|
305
|
+
this.mc.logger,
|
|
308
306
|
this.getStorageToken,
|
|
309
307
|
this.epochTracker,
|
|
310
308
|
requestSocketToken,
|
|
@@ -344,7 +342,7 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
344
342
|
io,
|
|
345
343
|
client,
|
|
346
344
|
webSocketUrl,
|
|
347
|
-
this.logger,
|
|
345
|
+
this.mc.logger,
|
|
348
346
|
60000,
|
|
349
347
|
this.epochTracker,
|
|
350
348
|
this.socketReferenceKeyPrefix,
|
|
@@ -354,7 +352,7 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
354
352
|
// Given that most reconnects result in reusing socket and happen very quickly,
|
|
355
353
|
// report event only if it took longer than threshold.
|
|
356
354
|
if (duration >= 2000) {
|
|
357
|
-
this.logger.sendPerformanceEvent({
|
|
355
|
+
this.mc.logger.sendPerformanceEvent({
|
|
358
356
|
eventName: "ConnectionSuccess",
|
|
359
357
|
duration,
|
|
360
358
|
});
|
|
@@ -391,7 +389,7 @@ export class OdspDocumentService implements IDocumentService {
|
|
|
391
389
|
};
|
|
392
390
|
this._opsCache = new OpsCache(
|
|
393
391
|
seqNumber,
|
|
394
|
-
this.logger,
|
|
392
|
+
this.mc.logger,
|
|
395
393
|
// ICache
|
|
396
394
|
{
|
|
397
395
|
write: async (key: string, opsData: string) => {
|
|
@@ -17,9 +17,9 @@ import {
|
|
|
17
17
|
ISummaryContext,
|
|
18
18
|
IDocumentStorageService,
|
|
19
19
|
LoaderCachingPolicy,
|
|
20
|
+
DriverErrorType,
|
|
20
21
|
} from "@fluidframework/driver-definitions";
|
|
21
|
-
import { RateLimiter } from "@fluidframework/driver-utils";
|
|
22
|
-
import { throwOdspNetworkError } from "@fluidframework/odsp-doclib-utils";
|
|
22
|
+
import { RateLimiter, NonRetryableError } from "@fluidframework/driver-utils";
|
|
23
23
|
import {
|
|
24
24
|
IOdspResolvedUrl,
|
|
25
25
|
ISnapshotOptions,
|
|
@@ -313,9 +313,6 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
313
313
|
this.blobCache.setBlob(blobId, blob);
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
if (!this.attributesBlobHandles.has(blobId)) {
|
|
317
|
-
return blob;
|
|
318
|
-
}
|
|
319
316
|
return blob;
|
|
320
317
|
}
|
|
321
318
|
|
|
@@ -406,22 +403,31 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
406
403
|
{ eventName: "ObtainSnapshot" },
|
|
407
404
|
async (event: PerformanceEvent) => {
|
|
408
405
|
const props = {};
|
|
409
|
-
let
|
|
406
|
+
let retrievedSnapshot: ISnapshotContents | undefined;
|
|
407
|
+
// Here's the logic to grab the persistent cache snapshot implemented by the host
|
|
408
|
+
// Epoch tracker is responsible for communicating with the persistent cache, handling epochs and cache versions
|
|
410
409
|
const cachedSnapshotP: Promise<ISnapshotContents | undefined> =
|
|
411
410
|
this.epochTracker.get(createCacheSnapshotKey(this.odspResolvedUrl))
|
|
412
|
-
.then((snapshotCachedEntry: ISnapshotCachedEntry) => {
|
|
411
|
+
.then(async (snapshotCachedEntry: ISnapshotCachedEntry) => {
|
|
413
412
|
if (snapshotCachedEntry !== undefined) {
|
|
414
413
|
// If the cached entry does not contain the entry time, then assign it a default of 30 days old.
|
|
415
|
-
|
|
416
|
-
props["cacheEntryAge"] = Date.now() - (snapshotCachedEntry.cacheEntryTime ??
|
|
414
|
+
const age = Date.now() - (snapshotCachedEntry.cacheEntryTime ??
|
|
417
415
|
(Date.now() - 30 * 24 * 60 * 60 * 1000));
|
|
416
|
+
|
|
417
|
+
// Record the cache age
|
|
418
|
+
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
419
|
+
props["cacheEntryAge"] = age;
|
|
418
420
|
}
|
|
421
|
+
|
|
419
422
|
return snapshotCachedEntry;
|
|
420
423
|
});
|
|
421
424
|
|
|
425
|
+
// Based on the concurrentSnapshotFetch policy:
|
|
426
|
+
// Either retrieve both the network and cache snapshots concurrently and pick the first to return,
|
|
427
|
+
// or grab the cache value and then the network value if the cache value returns undefined.
|
|
422
428
|
let method: string;
|
|
423
429
|
if (this.hostPolicy.concurrentSnapshotFetch && !this.hostPolicy.summarizerClient) {
|
|
424
|
-
const
|
|
430
|
+
const networkSnapshotP = this.fetchSnapshot(hostSnapshotOptions);
|
|
425
431
|
|
|
426
432
|
// Ensure that failures on both paths are ignored initially.
|
|
427
433
|
// I.e. if cache fails for some reason, we will proceed with network result.
|
|
@@ -429,20 +435,20 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
429
435
|
// do want to attempt to succeed with cached data!
|
|
430
436
|
const promiseRaceWinner = await promiseRaceWithWinner([
|
|
431
437
|
cachedSnapshotP.catch(() => undefined),
|
|
432
|
-
|
|
438
|
+
networkSnapshotP.catch(() => undefined),
|
|
433
439
|
]);
|
|
434
|
-
|
|
440
|
+
retrievedSnapshot = promiseRaceWinner.value;
|
|
435
441
|
method = promiseRaceWinner.index === 0 ? "cache" : "network";
|
|
436
442
|
|
|
437
|
-
if (
|
|
443
|
+
if (retrievedSnapshot === undefined) {
|
|
438
444
|
// if network failed -> wait for cache ( then return network failure)
|
|
439
445
|
// If cache returned empty or failed -> wait for network (success of failure)
|
|
440
446
|
if (promiseRaceWinner.index === 1) {
|
|
441
|
-
|
|
447
|
+
retrievedSnapshot = await cachedSnapshotP;
|
|
442
448
|
method = "cache";
|
|
443
449
|
}
|
|
444
|
-
if (
|
|
445
|
-
|
|
450
|
+
if (retrievedSnapshot === undefined) {
|
|
451
|
+
retrievedSnapshot = await networkSnapshotP;
|
|
446
452
|
method = "network";
|
|
447
453
|
}
|
|
448
454
|
}
|
|
@@ -450,12 +456,12 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
450
456
|
// Note: There's a race condition here - another caller may come past the undefined check
|
|
451
457
|
// while the first caller is awaiting later async code in this block.
|
|
452
458
|
|
|
453
|
-
|
|
459
|
+
retrievedSnapshot = await cachedSnapshotP;
|
|
454
460
|
|
|
455
|
-
method =
|
|
461
|
+
method = retrievedSnapshot !== undefined ? "cache" : "network";
|
|
456
462
|
|
|
457
|
-
if (
|
|
458
|
-
|
|
463
|
+
if (retrievedSnapshot === undefined) {
|
|
464
|
+
retrievedSnapshot = await this.fetchSnapshot(hostSnapshotOptions);
|
|
459
465
|
}
|
|
460
466
|
}
|
|
461
467
|
if (method === "network") {
|
|
@@ -463,12 +469,11 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
463
469
|
props["cacheEntryAge"] = undefined;
|
|
464
470
|
}
|
|
465
471
|
event.end({ ...props, method });
|
|
466
|
-
return
|
|
472
|
+
return retrievedSnapshot;
|
|
467
473
|
},
|
|
468
|
-
{end: true, cancel: "error"},
|
|
469
474
|
);
|
|
470
475
|
|
|
471
|
-
// Successful call,
|
|
476
|
+
// Successful call, make network calls only
|
|
472
477
|
this.firstVersionCall = false;
|
|
473
478
|
|
|
474
479
|
this._snapshotSequenceNumber = odspSnapshotCacheValue.sequenceNumber;
|
|
@@ -503,10 +508,16 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
503
508
|
);
|
|
504
509
|
const versionsResponse = response.content;
|
|
505
510
|
if (!versionsResponse) {
|
|
506
|
-
|
|
511
|
+
throw new NonRetryableError(
|
|
512
|
+
"getVersionsReturnedNoResponse",
|
|
513
|
+
"No response from /versions endpoint",
|
|
514
|
+
DriverErrorType.genericNetworkError);
|
|
507
515
|
}
|
|
508
516
|
if (!Array.isArray(versionsResponse.value)) {
|
|
509
|
-
|
|
517
|
+
throw new NonRetryableError(
|
|
518
|
+
"getVersionsReturnedNonArrayResponse",
|
|
519
|
+
"Incorrect response from /versions endpoint",
|
|
520
|
+
DriverErrorType.genericNetworkError);
|
|
510
521
|
}
|
|
511
522
|
return versionsResponse.value.map((version) => {
|
|
512
523
|
// Parse the date from the message
|
|
@@ -678,19 +689,28 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
678
689
|
|
|
679
690
|
private checkSnapshotUrl() {
|
|
680
691
|
if (!this.snapshotUrl) {
|
|
681
|
-
|
|
692
|
+
throw new NonRetryableError(
|
|
693
|
+
"noSnapshotUrlProvided",
|
|
694
|
+
"Method failed because no snapshot url was available",
|
|
695
|
+
DriverErrorType.genericError);
|
|
682
696
|
}
|
|
683
697
|
}
|
|
684
698
|
|
|
685
699
|
private checkAttachmentPOSTUrl() {
|
|
686
700
|
if (!this.attachmentPOSTUrl) {
|
|
687
|
-
|
|
701
|
+
throw new NonRetryableError(
|
|
702
|
+
"noAttachmentPOSTUrlProvided",
|
|
703
|
+
"Method failed because no attachment POST url was available",
|
|
704
|
+
DriverErrorType.genericError);
|
|
688
705
|
}
|
|
689
706
|
}
|
|
690
707
|
|
|
691
708
|
private checkAttachmentGETUrl() {
|
|
692
709
|
if (!this.attachmentGETUrl) {
|
|
693
|
-
|
|
710
|
+
throw new NonRetryableError(
|
|
711
|
+
"noAttachmentGETUrlWasProvided",
|
|
712
|
+
"Method failed because no attachment GET url was available",
|
|
713
|
+
DriverErrorType.genericError);
|
|
694
714
|
}
|
|
695
715
|
}
|
|
696
716
|
|
|
@@ -7,7 +7,7 @@ import { PromiseCache } from "@fluidframework/common-utils";
|
|
|
7
7
|
import { IFluidCodeDetails, IRequest, isFluidPackage } from "@fluidframework/core-interfaces";
|
|
8
8
|
import { IResolvedUrl, IUrlResolver } from "@fluidframework/driver-definitions";
|
|
9
9
|
import { ITelemetryBaseLogger, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
10
|
-
import {
|
|
10
|
+
import { NonRetryableError } from "@fluidframework/driver-utils";
|
|
11
11
|
import { PerformanceEvent } from "@fluidframework/telemetry-utils";
|
|
12
12
|
import {
|
|
13
13
|
IOdspResolvedUrl,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isTokenFromCache,
|
|
16
16
|
OdspResourceTokenFetchOptions,
|
|
17
17
|
TokenFetcher,
|
|
18
|
+
OdspErrorType,
|
|
18
19
|
} from "@fluidframework/odsp-driver-definitions";
|
|
19
20
|
import {
|
|
20
21
|
getLocatorFromOdspUrl,
|
|
@@ -132,8 +133,9 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
|
|
|
132
133
|
// We need to remove the nav param if set by host when setting the sharelink as otherwise the shareLinkId
|
|
133
134
|
// when redeeming the share link during the redeem fallback for trees latest call becomes greater than
|
|
134
135
|
// the eligible length.
|
|
136
|
+
odspResolvedUrl.sharingLinkToRedeem = this.removeNavParam(request.url);
|
|
135
137
|
odspResolvedUrl.shareLinkInfo = Object.assign(odspResolvedUrl.shareLinkInfo || {},
|
|
136
|
-
{sharingLinkToRedeem:
|
|
138
|
+
{sharingLinkToRedeem: odspResolvedUrl.sharingLinkToRedeem});
|
|
137
139
|
}
|
|
138
140
|
if (odspResolvedUrl.itemId) {
|
|
139
141
|
// Kick start the sharing link request if we don't have it already as a performance optimization.
|
|
@@ -161,7 +163,10 @@ export class OdspDriverUrlResolverForShareLink implements IUrlResolver {
|
|
|
161
163
|
{ eventName: "GetSharingLinkToken" },
|
|
162
164
|
async (event) => tokenFetcher(options).then((tokenResponse) => {
|
|
163
165
|
if (tokenResponse === null) {
|
|
164
|
-
|
|
166
|
+
throw new NonRetryableError(
|
|
167
|
+
"shareLinkTokenIsNull",
|
|
168
|
+
"Token callback returned null",
|
|
169
|
+
OdspErrorType.fetchTokenError);
|
|
165
170
|
}
|
|
166
171
|
event.end({ fromCache: isTokenFromCache(tokenResponse) });
|
|
167
172
|
return tokenResponse;
|
package/src/odspError.ts
CHANGED
|
@@ -11,9 +11,13 @@ import { IOdspSocketError } from "./contracts";
|
|
|
11
11
|
*/
|
|
12
12
|
export function errorObjectFromSocketError(socketError: IOdspSocketError, handler: string) {
|
|
13
13
|
const message = `OdspSocketError (${handler}): ${socketError.message}`;
|
|
14
|
-
|
|
14
|
+
const error = createOdspNetworkError(
|
|
15
15
|
`odspSocketError [${handler}]`,
|
|
16
16
|
message,
|
|
17
17
|
socketError.code,
|
|
18
18
|
socketError.retryAfter);
|
|
19
|
+
|
|
20
|
+
error.addTelemetryProperties({ odspError: true, relayServiceError: true });
|
|
21
|
+
|
|
22
|
+
return error;
|
|
19
23
|
}
|
|
@@ -9,7 +9,7 @@ import { ISummaryContext } from "@fluidframework/driver-definitions";
|
|
|
9
9
|
import { getGitType } from "@fluidframework/protocol-base";
|
|
10
10
|
import * as api from "@fluidframework/protocol-definitions";
|
|
11
11
|
import { InstrumentedStorageTokenFetcher } from "@fluidframework/odsp-driver-definitions";
|
|
12
|
-
import { PerformanceEvent } from "@fluidframework/telemetry-utils";
|
|
12
|
+
import { loggerToMonitoringContext, MonitoringContext, PerformanceEvent } from "@fluidframework/telemetry-utils";
|
|
13
13
|
import {
|
|
14
14
|
IOdspSummaryPayload,
|
|
15
15
|
IWriteSummaryResponse,
|
|
@@ -24,23 +24,6 @@ import { getWithRetryForTokenRefresh } from "./odspUtils";
|
|
|
24
24
|
|
|
25
25
|
/* eslint-disable max-len */
|
|
26
26
|
|
|
27
|
-
// Gate that when flipped, instructs to mark unreferenced nodes as such in the summary sent to SPO.
|
|
28
|
-
function gatesMarkUnreferencedNodes() {
|
|
29
|
-
try {
|
|
30
|
-
// Leave override for testing purposes
|
|
31
|
-
if (typeof localStorage === "object" && localStorage !== null) {
|
|
32
|
-
if (localStorage.FluidMarkUnreferencedNodes === "1") {
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
if (localStorage.FluidMarkUnreferencedNodes === "0") {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
} catch (e) {}
|
|
40
|
-
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
27
|
/**
|
|
45
28
|
* This class manages a summary upload. When it receives a call to upload summary, it converts the summary tree into
|
|
46
29
|
* a snapshot tree and then uploads that to the server.
|
|
@@ -48,20 +31,22 @@ function gatesMarkUnreferencedNodes() {
|
|
|
48
31
|
export class OdspSummaryUploadManager {
|
|
49
32
|
// Last proposed handle of the uploaded app summary.
|
|
50
33
|
private lastSummaryProposalHandle: string | undefined;
|
|
34
|
+
private readonly mc: MonitoringContext;
|
|
51
35
|
|
|
52
36
|
constructor(
|
|
53
37
|
private readonly snapshotUrl: string,
|
|
54
38
|
private readonly getStorageToken: InstrumentedStorageTokenFetcher,
|
|
55
|
-
|
|
39
|
+
logger: ITelemetryLogger,
|
|
56
40
|
private readonly epochTracker: EpochTracker,
|
|
57
41
|
) {
|
|
42
|
+
this.mc = loggerToMonitoringContext(logger);
|
|
58
43
|
}
|
|
59
44
|
|
|
60
45
|
public async writeSummaryTree(tree: api.ISummaryTree, context: ISummaryContext) {
|
|
61
46
|
// If the last proposed handle is not the proposed handle of the acked summary(could happen when the last summary get nacked),
|
|
62
47
|
// then re-initialize the caches with the previous ones else just update the previous caches with the caches from acked summary.
|
|
63
48
|
if (context.proposalHandle !== this.lastSummaryProposalHandle) {
|
|
64
|
-
this.logger.sendTelemetryEvent({
|
|
49
|
+
this.mc.logger.sendTelemetryEvent({
|
|
65
50
|
eventName: "LastSummaryProposedHandleMismatch",
|
|
66
51
|
ackedSummaryProposedHandle: context.proposalHandle,
|
|
67
52
|
lastSummaryProposalHandle: this.lastSummaryProposalHandle,
|
|
@@ -107,7 +92,7 @@ export class OdspSummaryUploadManager {
|
|
|
107
92
|
|
|
108
93
|
const postBody = JSON.stringify(snapshot);
|
|
109
94
|
|
|
110
|
-
return PerformanceEvent.timedExecAsync(this.logger,
|
|
95
|
+
return PerformanceEvent.timedExecAsync(this.mc.logger,
|
|
111
96
|
{
|
|
112
97
|
eventName: "uploadSummary",
|
|
113
98
|
attempt: options.refresh ? 2 : 1,
|
|
@@ -145,7 +130,7 @@ export class OdspSummaryUploadManager {
|
|
|
145
130
|
tree: api.ISummaryTree,
|
|
146
131
|
rootNodeName: string,
|
|
147
132
|
path: string = "",
|
|
148
|
-
markUnreferencedNodes: boolean =
|
|
133
|
+
markUnreferencedNodes: boolean = this.mc.config.getBoolean("Fluid.Driver.Odsp.MarkUnreferencedNodes") ?? true,
|
|
149
134
|
) {
|
|
150
135
|
const snapshotTree: IOdspSummaryTree = {
|
|
151
136
|
type: "tree",
|
package/src/odspUtils.ts
CHANGED
|
@@ -5,19 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
import { ITelemetryProperties, ITelemetryBaseLogger, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
7
|
import { IResolvedUrl, DriverErrorType } from "@fluidframework/driver-definitions";
|
|
8
|
-
import { isOnline, OnlineStatus } from "@fluidframework/driver-utils";
|
|
8
|
+
import { isOnline, OnlineStatus, RetryableError, NonRetryableError } from "@fluidframework/driver-utils";
|
|
9
9
|
import { assert, performance } from "@fluidframework/common-utils";
|
|
10
10
|
import { ISequencedDocumentMessage, ISnapshotTree } from "@fluidframework/protocol-definitions";
|
|
11
11
|
import { ChildLogger, PerformanceEvent, wrapError } from "@fluidframework/telemetry-utils";
|
|
12
12
|
import {
|
|
13
13
|
fetchIncorrectResponse,
|
|
14
|
-
offlineFetchFailureStatusCode,
|
|
15
|
-
fetchFailureStatusCode,
|
|
16
|
-
fetchTimeoutStatusCode,
|
|
17
14
|
throwOdspNetworkError,
|
|
18
15
|
getSPOAndGraphRequestIdsFromResponse,
|
|
19
|
-
fetchTokenErrorCode,
|
|
20
|
-
createOdspNetworkError,
|
|
21
16
|
} from "@fluidframework/odsp-doclib-utils";
|
|
22
17
|
import {
|
|
23
18
|
IOdspResolvedUrl,
|
|
@@ -82,7 +77,7 @@ export async function getWithRetryForTokenRefresh<T>(get: (options: TokenFetchOp
|
|
|
82
77
|
case DriverErrorType.authorizationError:
|
|
83
78
|
return get({ ...options, claims: e.claims, tenantId: e.tenantId });
|
|
84
79
|
|
|
85
|
-
case DriverErrorType.incorrectServerResponse: //
|
|
80
|
+
case DriverErrorType.incorrectServerResponse: // some error on the wire, retry once
|
|
86
81
|
case OdspErrorType.fetchTokenError: // If the token was null, then retry once.
|
|
87
82
|
return get(options);
|
|
88
83
|
|
|
@@ -107,7 +102,10 @@ export async function fetchHelper(
|
|
|
107
102
|
const response = fetchResponse as any as Response;
|
|
108
103
|
// Let's assume we can retry.
|
|
109
104
|
if (!response) {
|
|
110
|
-
|
|
105
|
+
throw new NonRetryableError(
|
|
106
|
+
"odspFetchErrorNoResponse",
|
|
107
|
+
"No response from fetch call",
|
|
108
|
+
DriverErrorType.incorrectServerResponse);
|
|
111
109
|
}
|
|
112
110
|
if (!response.ok || response.status < 200 || response.status >= 300) {
|
|
113
111
|
throwOdspNetworkError(
|
|
@@ -130,11 +128,13 @@ export async function fetchHelper(
|
|
|
130
128
|
if (errorText === "TypeError: Failed to fetch") {
|
|
131
129
|
online = OnlineStatus.Offline;
|
|
132
130
|
}
|
|
131
|
+
// This error is thrown by fetch() when AbortSignal is provided and it gets cancelled
|
|
133
132
|
if (error.name === "AbortError") {
|
|
134
|
-
|
|
133
|
+
throw new RetryableError("fetchAbort", "Fetch Timeout (AbortError)", OdspErrorType.fetchTimeout);
|
|
135
134
|
}
|
|
135
|
+
// TCP/IP timeout
|
|
136
136
|
if (errorText.indexOf("ETIMEDOUT") !== -1) {
|
|
137
|
-
|
|
137
|
+
throw new RetryableError("fetchETimedout", "Fetch Timeout (ETIMEDOUT)", OdspErrorType.fetchTimeout);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
//
|
|
@@ -142,11 +142,11 @@ export async function fetchHelper(
|
|
|
142
142
|
// It could container PII, like URI in message itself, or token in properties.
|
|
143
143
|
// It is also non-serializable object due to circular references.
|
|
144
144
|
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
if (online === OnlineStatus.Offline) {
|
|
146
|
+
throw new RetryableError("OdspFetchOffline", `Offline: ${errorText}`, DriverErrorType.offlineError);
|
|
147
|
+
} else {
|
|
148
|
+
throw new RetryableError("OdspFetchError", `Fetch error: ${errorText}`, DriverErrorType.fetchFailure);
|
|
149
|
+
}
|
|
150
150
|
});
|
|
151
151
|
}
|
|
152
152
|
|
|
@@ -181,30 +181,31 @@ export async function fetchAndParseAsJSONHelper<T>(
|
|
|
181
181
|
requestInit: RequestInit | undefined,
|
|
182
182
|
): Promise<IOdspResponse<T>> {
|
|
183
183
|
const { content, headers, commonSpoHeaders, duration } = await fetchHelper(requestInfo, requestInit);
|
|
184
|
-
|
|
185
|
-
// tokens... It fails for me with "Unexpected end of JSON input" quite often - an attempt to download big file
|
|
186
|
-
// (many ops) almost always ends up with this error - I'd guess 1% of op request end up here... It always
|
|
187
|
-
// succeeds on retry.
|
|
184
|
+
let text: string | undefined;
|
|
188
185
|
try {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
commonSpoHeaders.bodySize = text.length;
|
|
192
|
-
const res = {
|
|
193
|
-
headers,
|
|
194
|
-
content: JSON.parse(text),
|
|
195
|
-
commonSpoHeaders,
|
|
196
|
-
duration,
|
|
197
|
-
};
|
|
198
|
-
return res;
|
|
186
|
+
text = await content.text();
|
|
199
187
|
} catch (e) {
|
|
188
|
+
// JSON.parse() can fail and message would container full request URI, including
|
|
189
|
+
// tokens... It fails for me with "Unexpected end of JSON input" quite often - an attempt to download big file
|
|
190
|
+
// (many ops) almost always ends up with this error - I'd guess 1% of op request end up here... It always
|
|
191
|
+
// succeeds on retry.
|
|
192
|
+
// So do not log error object itself.
|
|
200
193
|
throwOdspNetworkError(
|
|
201
194
|
"errorWhileParsingFetchResponse",
|
|
202
195
|
fetchIncorrectResponse,
|
|
203
|
-
content,
|
|
204
|
-
|
|
205
|
-
{ error: Object(e) },
|
|
196
|
+
content, // response
|
|
197
|
+
text,
|
|
206
198
|
);
|
|
207
199
|
}
|
|
200
|
+
|
|
201
|
+
commonSpoHeaders.bodySize = text.length;
|
|
202
|
+
const res = {
|
|
203
|
+
headers,
|
|
204
|
+
content: JSON.parse(text),
|
|
205
|
+
commonSpoHeaders,
|
|
206
|
+
duration,
|
|
207
|
+
};
|
|
208
|
+
return res;
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
export interface INewFileInfo {
|
|
@@ -293,13 +294,21 @@ export function toInstrumentedOdspTokenFetcher(
|
|
|
293
294
|
event.end({ fromCache: isTokenFromCache(tokenResponse), isNull: token === null });
|
|
294
295
|
}
|
|
295
296
|
if (token === null && throwOnNullToken) {
|
|
296
|
-
|
|
297
|
+
throw new NonRetryableError(
|
|
298
|
+
"storageTokenIsNull",
|
|
299
|
+
`Token is null for ${name} call`,
|
|
300
|
+
OdspErrorType.fetchTokenError,
|
|
301
|
+
{ method: name });
|
|
297
302
|
}
|
|
298
303
|
return token;
|
|
299
304
|
}, (error) => {
|
|
300
305
|
const tokenError = wrapError(
|
|
301
306
|
error,
|
|
302
|
-
(errorMessage) =>
|
|
307
|
+
(errorMessage) => new NonRetryableError(
|
|
308
|
+
"tokenFetcherFailed",
|
|
309
|
+
errorMessage,
|
|
310
|
+
OdspErrorType.fetchTokenError,
|
|
311
|
+
{ method: name }));
|
|
303
312
|
throw tokenError;
|
|
304
313
|
}),
|
|
305
314
|
{ cancel: "generic" });
|
package/src/packageVersion.ts
CHANGED
package/src/vroom.ts
CHANGED
|
@@ -88,11 +88,16 @@ export async function fetchJoinSession(
|
|
|
88
88
|
logger,
|
|
89
89
|
);
|
|
90
90
|
|
|
91
|
+
const socketUrl = response.content.deltaStreamSocketUrl;
|
|
92
|
+
// expecting socketUrl to be something like https://{hostName}/...
|
|
93
|
+
const webSocketHostName = socketUrl.split("/")[2];
|
|
94
|
+
|
|
91
95
|
// TODO SPO-specific telemetry
|
|
92
96
|
event.end({
|
|
93
97
|
...response.commonSpoHeaders,
|
|
94
98
|
// pushV2 websocket urls will contain pushf
|
|
95
|
-
pushv2:
|
|
99
|
+
pushv2: socketUrl.includes("pushf"),
|
|
100
|
+
webSocketHostName,
|
|
96
101
|
});
|
|
97
102
|
|
|
98
103
|
if (response.content.runtimeTenantId && !response.content.tenantId) {
|