@fluidframework/odsp-driver 1.1.0 → 1.2.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/checkUrl.d.ts +1 -1
- package/dist/checkUrl.js +1 -1
- package/dist/checkUrl.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/contractsPublic.d.ts +1 -0
- package/dist/contractsPublic.d.ts.map +1 -1
- package/dist/contractsPublic.js.map +1 -1
- package/dist/getFileLink.js +0 -4
- package/dist/getFileLink.js.map +1 -1
- package/dist/localOdspDriver/localOdspDocumentService.d.ts +26 -0
- package/dist/localOdspDriver/localOdspDocumentService.d.ts.map +1 -0
- package/dist/localOdspDriver/localOdspDocumentService.js +39 -0
- package/dist/localOdspDriver/localOdspDocumentService.js.map +1 -0
- package/dist/localOdspDriver/localOdspDocumentServiceFactory.d.ts +24 -0
- package/dist/localOdspDriver/localOdspDocumentServiceFactory.d.ts.map +1 -0
- package/dist/localOdspDriver/localOdspDocumentServiceFactory.js +45 -0
- package/dist/localOdspDriver/localOdspDocumentServiceFactory.js.map +1 -0
- package/dist/localOdspDriver/localOdspDocumentStorageManager.d.ts +27 -0
- package/dist/localOdspDriver/localOdspDocumentStorageManager.d.ts.map +1 -0
- package/dist/localOdspDriver/localOdspDocumentStorageManager.js +66 -0
- package/dist/localOdspDriver/localOdspDocumentStorageManager.js.map +1 -0
- package/dist/odspDeltaStorageService.js.map +1 -1
- package/dist/odspDocumentService.d.ts.map +1 -1
- package/dist/odspDocumentService.js.map +1 -1
- package/dist/odspDocumentServiceFactory.d.ts +2 -1
- package/dist/odspDocumentServiceFactory.d.ts.map +1 -1
- package/dist/odspDocumentServiceFactory.js +7 -1
- package/dist/odspDocumentServiceFactory.js.map +1 -1
- package/dist/odspDocumentServiceFactoryCore.d.ts +3 -1
- package/dist/odspDocumentServiceFactoryCore.d.ts.map +1 -1
- package/dist/odspDocumentServiceFactoryCore.js.map +1 -1
- package/dist/odspDocumentStorageManager.d.ts +5 -23
- package/dist/odspDocumentStorageManager.d.ts.map +1 -1
- package/dist/odspDocumentStorageManager.js +52 -245
- package/dist/odspDocumentStorageManager.js.map +1 -1
- package/dist/odspDocumentStorageServiceBase.d.ts +58 -0
- package/dist/odspDocumentStorageServiceBase.d.ts.map +1 -0
- package/dist/odspDocumentStorageServiceBase.js +216 -0
- package/dist/odspDocumentStorageServiceBase.js.map +1 -0
- package/dist/odspDriverUrlResolver.js +1 -1
- package/dist/odspDriverUrlResolver.js.map +1 -1
- package/dist/odspDriverUrlResolverForShareLink.d.ts +8 -2
- package/dist/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
- package/dist/odspDriverUrlResolverForShareLink.js +11 -3
- package/dist/odspDriverUrlResolverForShareLink.js.map +1 -1
- package/dist/odspFluidFileLink.d.ts.map +1 -1
- package/dist/odspFluidFileLink.js +27 -21
- package/dist/odspFluidFileLink.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/lib/checkUrl.d.ts +1 -1
- package/lib/checkUrl.js +1 -1
- package/lib/checkUrl.js.map +1 -1
- package/lib/constants.d.ts +1 -1
- package/lib/constants.d.ts.map +1 -1
- package/lib/constants.js +1 -1
- package/lib/constants.js.map +1 -1
- package/lib/contractsPublic.d.ts +1 -0
- package/lib/contractsPublic.d.ts.map +1 -1
- package/lib/contractsPublic.js.map +1 -1
- package/lib/getFileLink.js +0 -4
- package/lib/getFileLink.js.map +1 -1
- package/lib/localOdspDriver/localOdspDocumentService.d.ts +26 -0
- package/lib/localOdspDriver/localOdspDocumentService.d.ts.map +1 -0
- package/lib/localOdspDriver/localOdspDocumentService.js +35 -0
- package/lib/localOdspDriver/localOdspDocumentService.js.map +1 -0
- package/lib/localOdspDriver/localOdspDocumentServiceFactory.d.ts +24 -0
- package/lib/localOdspDriver/localOdspDocumentServiceFactory.d.ts.map +1 -0
- package/lib/localOdspDriver/localOdspDocumentServiceFactory.js +41 -0
- package/lib/localOdspDriver/localOdspDocumentServiceFactory.js.map +1 -0
- package/lib/localOdspDriver/localOdspDocumentStorageManager.d.ts +27 -0
- package/lib/localOdspDriver/localOdspDocumentStorageManager.d.ts.map +1 -0
- package/lib/localOdspDriver/localOdspDocumentStorageManager.js +62 -0
- package/lib/localOdspDriver/localOdspDocumentStorageManager.js.map +1 -0
- package/lib/odspDeltaStorageService.js.map +1 -1
- package/lib/odspDocumentService.d.ts.map +1 -1
- package/lib/odspDocumentService.js +1 -1
- package/lib/odspDocumentService.js.map +1 -1
- package/lib/odspDocumentServiceFactory.d.ts +2 -1
- package/lib/odspDocumentServiceFactory.d.ts.map +1 -1
- package/lib/odspDocumentServiceFactory.js +5 -0
- package/lib/odspDocumentServiceFactory.js.map +1 -1
- package/lib/odspDocumentServiceFactoryCore.d.ts +3 -1
- package/lib/odspDocumentServiceFactoryCore.d.ts.map +1 -1
- package/lib/odspDocumentServiceFactoryCore.js.map +1 -1
- package/lib/odspDocumentStorageManager.d.ts +5 -23
- package/lib/odspDocumentStorageManager.d.ts.map +1 -1
- package/lib/odspDocumentStorageManager.js +53 -246
- package/lib/odspDocumentStorageManager.js.map +1 -1
- package/lib/odspDocumentStorageServiceBase.d.ts +58 -0
- package/lib/odspDocumentStorageServiceBase.d.ts.map +1 -0
- package/lib/odspDocumentStorageServiceBase.js +212 -0
- package/lib/odspDocumentStorageServiceBase.js.map +1 -0
- package/lib/odspDriverUrlResolver.js +1 -1
- package/lib/odspDriverUrlResolver.js.map +1 -1
- package/lib/odspDriverUrlResolverForShareLink.d.ts +8 -2
- package/lib/odspDriverUrlResolverForShareLink.d.ts.map +1 -1
- package/lib/odspDriverUrlResolverForShareLink.js +11 -3
- package/lib/odspDriverUrlResolverForShareLink.js.map +1 -1
- package/lib/odspFluidFileLink.d.ts.map +1 -1
- package/lib/odspFluidFileLink.js +27 -21
- package/lib/odspFluidFileLink.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/package.json +17 -12
- package/src/checkUrl.ts +1 -1
- package/src/constants.ts +1 -1
- package/src/contractsPublic.ts +1 -0
- package/src/getFileLink.ts +0 -5
- package/src/localOdspDriver/localOdspDocumentService.ts +54 -0
- package/src/localOdspDriver/localOdspDocumentServiceFactory.ts +67 -0
- package/src/localOdspDriver/localOdspDocumentStorageManager.ts +83 -0
- package/src/odspDeltaStorageService.ts +1 -1
- package/src/odspDocumentService.ts +5 -1
- package/src/odspDocumentServiceFactory.ts +7 -3
- package/src/odspDocumentServiceFactoryCore.ts +1 -1
- package/src/odspDocumentStorageManager.ts +81 -312
- package/src/odspDocumentStorageServiceBase.ts +268 -0
- package/src/odspDriverUrlResolver.ts +1 -1
- package/src/odspDriverUrlResolverForShareLink.ts +13 -1
- package/src/odspFluidFileLink.ts +26 -22
- package/src/packageVersion.ts +1 -1
|
@@ -15,8 +15,6 @@ import {
|
|
|
15
15
|
import * as api from "@fluidframework/protocol-definitions";
|
|
16
16
|
import {
|
|
17
17
|
ISummaryContext,
|
|
18
|
-
IDocumentStorageService,
|
|
19
|
-
LoaderCachingPolicy,
|
|
20
18
|
DriverErrorType,
|
|
21
19
|
} from "@fluidframework/driver-definitions";
|
|
22
20
|
import { RateLimiter, NonRetryableError } from "@fluidframework/driver-utils";
|
|
@@ -40,10 +38,11 @@ import {
|
|
|
40
38
|
getWithRetryForTokenRefresh,
|
|
41
39
|
} from "./odspUtils";
|
|
42
40
|
import { ISnapshotContents } from "./odspPublicUtils";
|
|
43
|
-
import {
|
|
41
|
+
import { EpochTracker } from "./epochTracker";
|
|
44
42
|
import { OdspSummaryUploadManager } from "./odspSummaryUploadManager";
|
|
45
43
|
import { FlushResult } from "./odspDocumentDeltaConnection";
|
|
46
44
|
import { pkgVersion as driverVersion } from "./packageVersion";
|
|
45
|
+
import { OdspDocumentStorageServiceBase } from "./odspDocumentStorageServiceBase";
|
|
47
46
|
|
|
48
47
|
export const defaultSummarizerCacheExpiryTimeout: number = 60 * 1000; // 60 seconds.
|
|
49
48
|
|
|
@@ -58,134 +57,15 @@ async function promiseRaceWithWinner<T>(promises: Promise<T>[]): Promise<{ index
|
|
|
58
57
|
});
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
class BlobCache {
|
|
62
|
-
// Save the timeout so we can cancel and reschedule it as needed
|
|
63
|
-
private blobCacheTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
64
|
-
// If the defer flag is set when the timeout fires, we'll reschedule rather than clear immediately
|
|
65
|
-
// This deferral approach is used (rather than clearing/resetting the timer) as current calling patterns trigger
|
|
66
|
-
// too many calls to setTimeout/clearTimeout.
|
|
67
|
-
private deferBlobCacheClear: boolean = false;
|
|
68
|
-
|
|
69
|
-
private readonly _blobCache: Map<string, ArrayBuffer> = new Map();
|
|
70
|
-
|
|
71
|
-
// Tracks all blob IDs evicted from cache
|
|
72
|
-
private readonly blobsEvicted: Set<string> = new Set();
|
|
73
|
-
|
|
74
|
-
// Initial time-out to purge data from cache
|
|
75
|
-
// If this time out is very small, then we purge blobs from cache too soon and that results in a lot of
|
|
76
|
-
// requests to storage, which brings down perf and may trip protection limits causing 429s
|
|
77
|
-
private blobCacheTimeoutDuration = 2 * 60 * 1000;
|
|
78
|
-
|
|
79
|
-
// SPO does not keep old snapshots around for long, so we are running chances of not
|
|
80
|
-
// being able to rehydrate data store / DDS in the future if we purge anything (and with blob de-duping,
|
|
81
|
-
// even if blob read by runtime, it could be read again in the future)
|
|
82
|
-
// So for now, purging is disabled.
|
|
83
|
-
private readonly purgeEnabled = false;
|
|
84
|
-
|
|
85
|
-
public get value() {
|
|
86
|
-
return this._blobCache;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public addBlobs(blobs: Map<string, ArrayBuffer>) {
|
|
90
|
-
blobs.forEach((value, blobId) => {
|
|
91
|
-
this._blobCache.set(blobId, value);
|
|
92
|
-
});
|
|
93
|
-
// Reset the timer on cache set
|
|
94
|
-
this.scheduleClearBlobsCache();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Schedule a timer for clearing the blob cache or defer the current one.
|
|
99
|
-
*/
|
|
100
|
-
private scheduleClearBlobsCache() {
|
|
101
|
-
if (this.blobCacheTimeout !== undefined) {
|
|
102
|
-
// If we already have an outstanding timer, just signal that we should defer the clear
|
|
103
|
-
this.deferBlobCacheClear = true;
|
|
104
|
-
} else if (this.purgeEnabled) {
|
|
105
|
-
// If we don't have an outstanding timer, set a timer
|
|
106
|
-
// When the timer runs out, we'll decide whether to proceed with the cache clear or reset the timer
|
|
107
|
-
const clearCacheOrDefer = () => {
|
|
108
|
-
this.blobCacheTimeout = undefined;
|
|
109
|
-
if (this.deferBlobCacheClear) {
|
|
110
|
-
this.deferBlobCacheClear = false;
|
|
111
|
-
this.scheduleClearBlobsCache();
|
|
112
|
-
} else {
|
|
113
|
-
// NOTE: Slightly better algorithm here would be to purge either only big blobs,
|
|
114
|
-
// or sort them by size and purge enough big blobs to leave only 256Kb of small blobs in cache
|
|
115
|
-
// Purging is optimizing memory footprint. But count controls potential number of storage requests
|
|
116
|
-
// We want to optimize both - memory footprint and number of future requests to storage.
|
|
117
|
-
// Note that Container can realize data store or DDS on-demand at any point in time, so we do not
|
|
118
|
-
// control when blobs will be used.
|
|
119
|
-
this._blobCache.forEach((_, blobId) => this.blobsEvicted.add(blobId));
|
|
120
|
-
this._blobCache.clear();
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
this.blobCacheTimeout = setTimeout(clearCacheOrDefer, this.blobCacheTimeoutDuration);
|
|
124
|
-
// any future storage reads that get into the cache should be cleared from cache rather quickly -
|
|
125
|
-
// there is not much value in keeping them longer
|
|
126
|
-
this.blobCacheTimeoutDuration = 10 * 1000;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
public getBlob(blobId: string) {
|
|
131
|
-
// Reset the timer on attempted cache read
|
|
132
|
-
this.scheduleClearBlobsCache();
|
|
133
|
-
const blobContent = this._blobCache.get(blobId);
|
|
134
|
-
const evicted = this.blobsEvicted.has(blobId);
|
|
135
|
-
return { blobContent, evicted };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
public setBlob(blobId: string, blob: ArrayBuffer) {
|
|
139
|
-
// This API is called as result of cache miss and reading blob from storage.
|
|
140
|
-
// Runtime never reads same blob twice.
|
|
141
|
-
// The only reason we may get read request for same blob is blob de-duping in summaries.
|
|
142
|
-
// Note that the bigger the size, the less likely blobs are the same, so there is very little benefit of caching big blobs.
|
|
143
|
-
// Images are the only exception - user may insert same image twice. But we currently do not de-dup them - only snapshot
|
|
144
|
-
// blobs are de-duped.
|
|
145
|
-
const size = blob.byteLength;
|
|
146
|
-
if (size < 256 * 1024) {
|
|
147
|
-
// Reset the timer on cache set
|
|
148
|
-
this.scheduleClearBlobsCache();
|
|
149
|
-
return this._blobCache.set(blobId, blob);
|
|
150
|
-
} else {
|
|
151
|
-
// we evicted it here by not caching.
|
|
152
|
-
this.blobsEvicted.add(blobId);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
60
|
interface GetVersionsTelemetryProps {
|
|
158
61
|
cacheEntryAge?: number;
|
|
159
62
|
cacheSummarizerExpired?: boolean;
|
|
160
63
|
}
|
|
161
|
-
export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
162
|
-
readonly policies = {
|
|
163
|
-
// By default, ODSP tells the container not to prefetch/cache.
|
|
164
|
-
caching: LoaderCachingPolicy.NoCaching,
|
|
165
|
-
|
|
166
|
-
// ODSP storage works better if it has less number of blobs / edges
|
|
167
|
-
// Runtime creating many small blobs results in sub-optimal perf.
|
|
168
|
-
// 2K seems like the sweat spot:
|
|
169
|
-
// The smaller the number, less blobs we aggregate. Most storages are very likely to have notion
|
|
170
|
-
// of minimal "cluster" size, so having small blobs is wasteful
|
|
171
|
-
// At the same time increasing the limit ensure that more blobs with user content are aggregated,
|
|
172
|
-
// reducing possibility for de-duping of same blobs (i.e. .attributes rolled into aggregate blob
|
|
173
|
-
// are not reused across data stores, or even within data store, resulting in duplication of content)
|
|
174
|
-
// Note that duplication of content should not have significant impact for bytes over wire as
|
|
175
|
-
// compression of http payload mostly takes care of it, but it does impact storage size and in-memory sizes.
|
|
176
|
-
minBlobSize: 2048,
|
|
177
|
-
maximumCacheDurationMs: defaultCacheExpiryTimeoutMs,
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
private readonly commitCache: Map<string, api.ISnapshotTree> = new Map();
|
|
181
|
-
|
|
182
|
-
private readonly attributesBlobHandles: Set<string> = new Set();
|
|
183
64
|
|
|
65
|
+
export class OdspDocumentStorageService extends OdspDocumentStorageServiceBase {
|
|
184
66
|
private readonly odspSummaryUploadManager: OdspSummaryUploadManager;
|
|
185
|
-
private _ops: api.ISequencedDocumentMessage[] | undefined;
|
|
186
67
|
|
|
187
68
|
private firstVersionCall = true;
|
|
188
|
-
private _snapshotSequenceNumber: number | undefined;
|
|
189
69
|
|
|
190
70
|
private readonly documentId: string;
|
|
191
71
|
private readonly snapshotUrl: string | undefined;
|
|
@@ -203,21 +83,6 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
203
83
|
// limits the amount of parallel "attachment" blob uploads
|
|
204
84
|
private readonly createBlobRateLimiter = new RateLimiter(1);
|
|
205
85
|
|
|
206
|
-
private readonly blobCache = new BlobCache();
|
|
207
|
-
|
|
208
|
-
public set ops(ops: api.ISequencedDocumentMessage[] | undefined) {
|
|
209
|
-
assert(this._ops === undefined, 0x0a5 /* "Trying to set ops when they are already set!" */);
|
|
210
|
-
this._ops = ops;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
public get ops(): api.ISequencedDocumentMessage[] | undefined {
|
|
214
|
-
return this._ops;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
public get snapshotSequenceNumber() {
|
|
218
|
-
return this._snapshotSequenceNumber;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
86
|
constructor(
|
|
222
87
|
private readonly odspResolvedUrl: IOdspResolvedUrl,
|
|
223
88
|
private readonly getStorageToken: InstrumentedStorageTokenFetcher,
|
|
@@ -229,10 +94,13 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
229
94
|
private readonly flushCallback: () => Promise<FlushResult>,
|
|
230
95
|
private readonly snapshotFormatFetchType?: SnapshotFormatSupportType,
|
|
231
96
|
) {
|
|
97
|
+
super();
|
|
98
|
+
|
|
232
99
|
this.documentId = this.odspResolvedUrl.hashedDocumentId;
|
|
233
100
|
this.snapshotUrl = this.odspResolvedUrl.endpoints.snapshotStorageUrl;
|
|
234
101
|
this.attachmentPOSTUrl = this.odspResolvedUrl.endpoints.attachmentPOSTStorageUrl;
|
|
235
102
|
this.attachmentGETUrl = this.odspResolvedUrl.endpoints.attachmentGETStorageUrl;
|
|
103
|
+
|
|
236
104
|
this.odspSummaryUploadManager = new OdspSummaryUploadManager(
|
|
237
105
|
this.snapshotUrl,
|
|
238
106
|
getStorageToken,
|
|
@@ -242,10 +110,6 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
242
110
|
);
|
|
243
111
|
}
|
|
244
112
|
|
|
245
|
-
public get repositoryUrl(): string {
|
|
246
|
-
return "";
|
|
247
|
-
}
|
|
248
|
-
|
|
249
113
|
public async createBlob(file: ArrayBufferLike): Promise<api.ICreateBlobResponse> {
|
|
250
114
|
this.checkAttachmentPOSTUrl();
|
|
251
115
|
|
|
@@ -288,95 +152,56 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
288
152
|
return response.content;
|
|
289
153
|
}
|
|
290
154
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
let blob = blobContent;
|
|
294
|
-
|
|
295
|
-
if (blob === undefined) {
|
|
296
|
-
this.checkAttachmentGETUrl();
|
|
155
|
+
protected async fetchBlobFromStorage(blobId: string, evicted: boolean): Promise<ArrayBuffer> {
|
|
156
|
+
this.checkAttachmentGETUrl();
|
|
297
157
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
158
|
+
const blob = await getWithRetryForTokenRefresh(async (options) => {
|
|
159
|
+
const storageToken = await this.getStorageToken(options, "GetBlob");
|
|
160
|
+
const unAuthedUrl = `${this.attachmentGETUrl}/${encodeURIComponent(blobId)}/content`;
|
|
161
|
+
const { url, headers } = getUrlAndHeadersWithAuth(
|
|
162
|
+
unAuthedUrl,
|
|
163
|
+
storageToken,
|
|
164
|
+
!!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
|
|
165
|
+
);
|
|
306
166
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
167
|
+
return PerformanceEvent.timedExecAsync(
|
|
168
|
+
this.logger,
|
|
169
|
+
{
|
|
170
|
+
eventName: "readDataBlob",
|
|
171
|
+
blobId,
|
|
172
|
+
evicted,
|
|
173
|
+
headers: Object.keys(headers).length !== 0 ? true : undefined,
|
|
174
|
+
waitQueueLength: this.epochTracker.rateLimiter.waitQueueLength,
|
|
175
|
+
},
|
|
176
|
+
async (event) => {
|
|
177
|
+
const res = await this.epochTracker.fetchArray(url, { headers }, "blob");
|
|
178
|
+
event.end({
|
|
314
179
|
waitQueueLength: this.epochTracker.rateLimiter.waitQueueLength,
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
180
|
+
...res.propsToLog,
|
|
181
|
+
attempts: options.refresh ? 2 : 1,
|
|
182
|
+
});
|
|
183
|
+
const cacheControl = res.headers.get("cache-control");
|
|
184
|
+
if (cacheControl === undefined || !(cacheControl.includes("private") || cacheControl.includes("public"))) {
|
|
185
|
+
this.logger.sendErrorEvent({
|
|
186
|
+
eventName: "NonCacheableBlob",
|
|
187
|
+
cacheControl,
|
|
188
|
+
blobId,
|
|
320
189
|
...res.propsToLog,
|
|
321
|
-
attempts: options.refresh ? 2 : 1,
|
|
322
190
|
});
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
...res.propsToLog,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
return res.content;
|
|
333
|
-
},
|
|
334
|
-
);
|
|
335
|
-
});
|
|
336
|
-
this.blobCache.setBlob(blobId, blob);
|
|
337
|
-
}
|
|
338
|
-
|
|
191
|
+
}
|
|
192
|
+
return res.content;
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
this.blobCache.setBlob(blobId, blob);
|
|
339
197
|
return blob;
|
|
340
198
|
}
|
|
341
199
|
|
|
342
|
-
public async readBlob(blobId: string): Promise<ArrayBufferLike> {
|
|
343
|
-
return this.readBlobCore(blobId);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
200
|
public async getSnapshotTree(version?: api.IVersion, scenarioName?: string): Promise<api.ISnapshotTree | null> {
|
|
347
201
|
if (!this.snapshotUrl) {
|
|
348
202
|
return null;
|
|
349
203
|
}
|
|
350
|
-
|
|
351
|
-
let id: string;
|
|
352
|
-
if (!version || !version.id) {
|
|
353
|
-
const versions = await this.getVersions(null, 1, scenarioName);
|
|
354
|
-
if (!versions || versions.length === 0) {
|
|
355
|
-
return null;
|
|
356
|
-
}
|
|
357
|
-
id = versions[0].id;
|
|
358
|
-
} else {
|
|
359
|
-
id = version.id;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const snapshotTree = await this.readTree(id, scenarioName);
|
|
363
|
-
if (!snapshotTree) {
|
|
364
|
-
return null;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (snapshotTree.blobs) {
|
|
368
|
-
const attributesBlob = snapshotTree.blobs.attributes;
|
|
369
|
-
if (attributesBlob) {
|
|
370
|
-
this.attributesBlobHandles.add(attributesBlob);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// When we upload the container snapshot, we upload appTree in ".app" and protocol tree in ".protocol"
|
|
375
|
-
// So when we request the snapshot we get ".app" as tree and not as commit node as in the case just above.
|
|
376
|
-
const appTree = snapshotTree.trees[".app"];
|
|
377
|
-
const protocolTree = snapshotTree.trees[".protocol"];
|
|
378
|
-
|
|
379
|
-
return this.combineProtocolAndAppSnapshotTree(appTree, protocolTree);
|
|
204
|
+
return super.getSnapshotTree(version, scenarioName);
|
|
380
205
|
}
|
|
381
206
|
|
|
382
207
|
public async getVersions(blobid: string | null, count: number, scenarioName?: string): Promise<api.IVersion[]> {
|
|
@@ -491,21 +316,8 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
491
316
|
|
|
492
317
|
// Successful call, make network calls only
|
|
493
318
|
this.firstVersionCall = false;
|
|
319
|
+
const id = this.initializeFromSnapshot(odspSnapshotCacheValue);
|
|
494
320
|
|
|
495
|
-
this._snapshotSequenceNumber = odspSnapshotCacheValue.sequenceNumber;
|
|
496
|
-
const { snapshotTree, blobs, ops } = odspSnapshotCacheValue;
|
|
497
|
-
// id should be undefined in case of just ops in snapshot.
|
|
498
|
-
let id: string | undefined;
|
|
499
|
-
if (snapshotTree) {
|
|
500
|
-
id = snapshotTree.id;
|
|
501
|
-
assert(id !== undefined, 0x221 /* "Root tree should contain the id" */);
|
|
502
|
-
this.setRootTree(id, snapshotTree);
|
|
503
|
-
}
|
|
504
|
-
if (blobs) {
|
|
505
|
-
this.initBlobsCache(blobs);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
this.ops = ops;
|
|
509
321
|
return id ? [{ id, treeId: undefined! }] : [];
|
|
510
322
|
}
|
|
511
323
|
|
|
@@ -648,15 +460,14 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
648
460
|
|
|
649
461
|
// Enable flushing only if we have single commit summary and this is not the initial summary for an empty file
|
|
650
462
|
if (".protocol" in summary.tree && context.ackHandle !== undefined) {
|
|
651
|
-
let retry =
|
|
652
|
-
for (
|
|
463
|
+
let retry = 1;
|
|
464
|
+
for (;;) {
|
|
653
465
|
const result = await this.flushCallback();
|
|
654
466
|
const seq = result.lastPersistedSequenceNumber;
|
|
655
467
|
if (seq !== undefined && seq >= context.referenceSequenceNumber) {
|
|
656
468
|
break;
|
|
657
469
|
}
|
|
658
470
|
|
|
659
|
-
retry++;
|
|
660
471
|
if (retry > 3) {
|
|
661
472
|
this.logger.sendErrorEvent({
|
|
662
473
|
eventName: "FlushFailure",
|
|
@@ -674,6 +485,7 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
674
485
|
referenceSequenceNumber: context.referenceSequenceNumber,
|
|
675
486
|
});
|
|
676
487
|
|
|
488
|
+
retry++;
|
|
677
489
|
await delay(1000 * (result.retryAfter ?? 1));
|
|
678
490
|
}
|
|
679
491
|
}
|
|
@@ -682,18 +494,6 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
682
494
|
return id;
|
|
683
495
|
}
|
|
684
496
|
|
|
685
|
-
public async downloadSummary(commit: api.ISummaryHandle): Promise<api.ISummaryTree> {
|
|
686
|
-
throw new Error("Not implemented yet");
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
private setRootTree(id: string, tree: api.ISnapshotTree) {
|
|
690
|
-
this.commitCache.set(id, tree);
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
private initBlobsCache(blobs: Map<string, ArrayBuffer>) {
|
|
694
|
-
this.blobCache.addBlobs(blobs);
|
|
695
|
-
}
|
|
696
|
-
|
|
697
497
|
private checkSnapshotUrl() {
|
|
698
498
|
if (!this.snapshotUrl) {
|
|
699
499
|
throw new NonRetryableError(
|
|
@@ -721,71 +521,40 @@ export class OdspDocumentStorageService implements IDocumentStorageService {
|
|
|
721
521
|
}
|
|
722
522
|
}
|
|
723
523
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
url,
|
|
735
|
-
fetchOptions,
|
|
736
|
-
"snapshotTree",
|
|
737
|
-
undefined,
|
|
738
|
-
scenarioName,
|
|
739
|
-
);
|
|
740
|
-
};
|
|
741
|
-
const snapshot = await fetchSnapshot(
|
|
742
|
-
this.snapshotUrl!,
|
|
743
|
-
storageToken,
|
|
744
|
-
id,
|
|
745
|
-
this.fetchFullSnapshot,
|
|
746
|
-
!!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
|
|
747
|
-
this.logger,
|
|
748
|
-
snapshotDownloader,
|
|
524
|
+
protected async fetchTreeFromSnapshot(id: string, scenarioName?: string): Promise<api.ISnapshotTree | undefined> {
|
|
525
|
+
return getWithRetryForTokenRefresh(async (options) => {
|
|
526
|
+
const storageToken = await this.getStorageToken(options, "ReadCommit");
|
|
527
|
+
const snapshotDownloader = async (url: string, fetchOptions: { [index: string]: any; }) => {
|
|
528
|
+
return this.epochTracker.fetchAndParseAsJSON(
|
|
529
|
+
url,
|
|
530
|
+
fetchOptions,
|
|
531
|
+
"snapshotTree",
|
|
532
|
+
undefined,
|
|
533
|
+
scenarioName,
|
|
749
534
|
);
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
hierarchicalAppTree: api.ISnapshotTree,
|
|
774
|
-
hierarchicalProtocolTree: api.ISnapshotTree,
|
|
775
|
-
) {
|
|
776
|
-
const summarySnapshotTree: api.ISnapshotTree = {
|
|
777
|
-
blobs: {
|
|
778
|
-
...hierarchicalAppTree.blobs,
|
|
779
|
-
},
|
|
780
|
-
trees: {
|
|
781
|
-
...hierarchicalAppTree.trees,
|
|
782
|
-
// the app tree could have a .protocol
|
|
783
|
-
// in that case we want to server protocol to override it
|
|
784
|
-
".protocol": hierarchicalProtocolTree,
|
|
785
|
-
},
|
|
786
|
-
};
|
|
787
|
-
|
|
788
|
-
return summarySnapshotTree;
|
|
535
|
+
};
|
|
536
|
+
const snapshot = await fetchSnapshot(
|
|
537
|
+
this.snapshotUrl!,
|
|
538
|
+
storageToken,
|
|
539
|
+
id,
|
|
540
|
+
this.fetchFullSnapshot,
|
|
541
|
+
!!this.hostPolicy.sessionOptions?.forceAccessTokenViaAuthorizationHeader,
|
|
542
|
+
this.logger,
|
|
543
|
+
snapshotDownloader,
|
|
544
|
+
);
|
|
545
|
+
let treeId = "";
|
|
546
|
+
if (snapshot.snapshotTree) {
|
|
547
|
+
assert(snapshot.snapshotTree.id !== undefined, 0x222 /* "Root tree should contain the id!!" */);
|
|
548
|
+
treeId = snapshot.snapshotTree.id;
|
|
549
|
+
this.setRootTree(treeId, snapshot.snapshotTree);
|
|
550
|
+
}
|
|
551
|
+
if (snapshot.blobs) {
|
|
552
|
+
this.initBlobsCache(snapshot.blobs);
|
|
553
|
+
}
|
|
554
|
+
// If the version id doesn't match with the id of the tree, then use the id of first tree which in that case
|
|
555
|
+
// will be the actual id of tree to be fetched.
|
|
556
|
+
return this.commitCache.get(id) ?? this.commitCache.get(treeId);
|
|
557
|
+
});
|
|
789
558
|
}
|
|
790
559
|
}
|
|
791
560
|
|