@fluidframework/container-loader 2.0.0-dev.3.1.0.125672 → 2.0.0-dev.4.1.0.148229
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/README.md +7 -4
- package/closeAndGetPendingLocalState.md +51 -0
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +43 -11
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +4 -4
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +7 -0
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +44 -4
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +152 -93
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +18 -8
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +47 -4
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +41 -2
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +87 -11
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +2 -2
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +3 -2
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +4 -2
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts +10 -22
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaManagerProxy.js +14 -50
- package/dist/deltaManagerProxy.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +10 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +22 -16
- package/dist/loader.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/protocolTreeDocumentStorageService.d.ts +6 -2
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js +7 -4
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -1
- package/dist/utils.js.map +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +44 -12
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +4 -4
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +7 -0
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +44 -4
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +155 -96
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +18 -8
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +48 -5
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +41 -2
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +85 -11
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +2 -2
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +3 -2
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +4 -2
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts +10 -22
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaManagerProxy.js +14 -50
- package/lib/deltaManagerProxy.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +10 -1
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +21 -16
- package/lib/loader.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/protocolTreeDocumentStorageService.d.ts +6 -2
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js +7 -4
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +2 -1
- package/lib/utils.js.map +1 -1
- package/package.json +64 -56
- package/src/connectionManager.ts +48 -17
- package/src/connectionStateHandler.ts +17 -5
- package/src/container.ts +224 -116
- package/src/containerContext.ts +74 -11
- package/src/containerStorageAdapter.ts +113 -9
- package/src/contracts.ts +2 -2
- package/src/deltaManager.ts +9 -4
- package/src/deltaManagerProxy.ts +18 -73
- package/src/index.ts +2 -3
- package/src/loader.ts +28 -26
- package/src/packageVersion.ts +1 -1
- package/src/protocolTreeDocumentStorageService.ts +6 -3
- package/src/utils.ts +7 -4
package/lib/container.js
CHANGED
|
@@ -8,9 +8,9 @@ import { v4 as uuid } from "uuid";
|
|
|
8
8
|
import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
|
|
9
9
|
import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
|
|
10
10
|
import { GenericError, UsageError } from "@fluidframework/container-utils";
|
|
11
|
-
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, } from "@fluidframework/driver-utils";
|
|
11
|
+
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, isCombinedAppAndProtocolSummary, } from "@fluidframework/driver-utils";
|
|
12
12
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
13
|
-
import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName,
|
|
13
|
+
import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
|
|
14
14
|
import { Audience } from "./audience";
|
|
15
15
|
import { ContainerContext } from "./containerContext";
|
|
16
16
|
import { ReconnectMode, getPackageName } from "./contracts";
|
|
@@ -18,7 +18,7 @@ import { DeltaManager } from "./deltaManager";
|
|
|
18
18
|
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
19
19
|
import { RelativeLoader } from "./loader";
|
|
20
20
|
import { pkgVersion } from "./packageVersion";
|
|
21
|
-
import { ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
21
|
+
import { ContainerStorageAdapter, getBlobContentsFromTree, getBlobContentsFromTreeWithBlobContents, } from "./containerStorageAdapter";
|
|
22
22
|
import { createConnectionStateHandler } from "./connectionStateHandler";
|
|
23
23
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
24
24
|
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy, } from "./quorum";
|
|
@@ -119,9 +119,15 @@ export async function ReportIfTooLong(logger, eventName, action) {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
const summarizerClientType = "summarizer";
|
|
122
|
+
/**
|
|
123
|
+
* @deprecated - In the next release Container will no longer be exported, IContainer should be used in its place.
|
|
124
|
+
*/
|
|
122
125
|
export class Container extends EventEmitterWithErrorHandling {
|
|
126
|
+
/**
|
|
127
|
+
* @internal
|
|
128
|
+
*/
|
|
123
129
|
constructor(loader, config, protocolHandlerBuilder) {
|
|
124
|
-
var _a, _b;
|
|
130
|
+
var _a, _b, _c;
|
|
125
131
|
super((name, error) => {
|
|
126
132
|
this.mc.logger.sendErrorEvent({
|
|
127
133
|
eventName: "ContainerEventHandlerException",
|
|
@@ -157,6 +163,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
157
163
|
this.messageCountAfterDisconnection = 0;
|
|
158
164
|
this.attachStarted = false;
|
|
159
165
|
this._dirtyContainer = false;
|
|
166
|
+
this.savedOps = [];
|
|
160
167
|
this.setAutoReconnectTime = performance.now();
|
|
161
168
|
this._disposed = false;
|
|
162
169
|
this.clientDetailsOverride = config.clientDetailsOverride;
|
|
@@ -195,15 +202,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
195
202
|
dmLastMsqSeqNumber: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.sequenceNumber; },
|
|
196
203
|
dmLastMsqSeqTimestamp: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.timestamp; },
|
|
197
204
|
dmLastMsqSeqClientId: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId; },
|
|
205
|
+
dmLastMsgClientSeq: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientSequenceNumber; },
|
|
198
206
|
connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
199
207
|
},
|
|
200
208
|
});
|
|
201
209
|
// Prefix all events in this file with container-loader
|
|
202
210
|
this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
|
|
203
|
-
|
|
204
|
-
this.options = Object.assign(Object.assign({}, this.loader.services.options), { summarizeProtocolTree });
|
|
211
|
+
this.options = Object.assign({}, this.loader.services.options);
|
|
205
212
|
this._deltaManager = this.createDeltaManager();
|
|
206
|
-
this._clientId = (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId;
|
|
207
213
|
this.connectionStateHandler = createConnectionStateHandler({
|
|
208
214
|
logger: this.mc.logger,
|
|
209
215
|
connectionStateChanged: (value, oldState, reason) => {
|
|
@@ -242,13 +248,20 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
242
248
|
this.connect();
|
|
243
249
|
}
|
|
244
250
|
},
|
|
245
|
-
}, this.deltaManager,
|
|
251
|
+
}, this.deltaManager, (_a = config.serializedContainerState) === null || _a === void 0 ? void 0 : _a.clientId);
|
|
246
252
|
this.on(savedContainerEvent, () => {
|
|
247
253
|
this.connectionStateHandler.containerSaved();
|
|
248
254
|
});
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
255
|
+
// We expose our storage publicly, so it's possible others may call uploadSummaryWithContext() with a
|
|
256
|
+
// non-combined summary tree (in particular, ContainerRuntime.submitSummary). We'll intercept those calls
|
|
257
|
+
// using this callback and fix them up.
|
|
258
|
+
const addProtocolSummaryIfMissing = (summaryTree) => isCombinedAppAndProtocolSummary(summaryTree) === true
|
|
259
|
+
? summaryTree
|
|
260
|
+
: combineAppAndProtocolSummary(summaryTree, this.captureProtocolSummary());
|
|
261
|
+
// Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
|
|
262
|
+
// Even if not forced on via this flag, combined summaries may still be enabled by service policy.
|
|
263
|
+
const forceEnableSummarizeProtocolTree = (_b = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2")) !== null && _b !== void 0 ? _b : this.loader.services.options.summarizeProtocolTree;
|
|
264
|
+
this.storageAdapter = new ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, (_c = config.serializedContainerState) === null || _c === void 0 ? void 0 : _c.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
|
|
252
265
|
const isDomAvailable = typeof document === "object" &&
|
|
253
266
|
document !== null &&
|
|
254
267
|
typeof document.addEventListener === "function" &&
|
|
@@ -269,45 +282,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
269
282
|
};
|
|
270
283
|
document.addEventListener("visibilitychange", this.visibilityEventHandler);
|
|
271
284
|
}
|
|
272
|
-
// We observed that most users of platform do not check Container.connected event on load, causing bugs.
|
|
273
|
-
// As such, we are raising events when new listener pops up.
|
|
274
|
-
// Note that we can raise both "disconnected" & "connect" events at the same time,
|
|
275
|
-
// if we are in connecting stage.
|
|
276
|
-
this.on("newListener", (event, listener) => {
|
|
277
|
-
// Fire events on the end of JS turn, giving a chance for caller to be in consistent state.
|
|
278
|
-
Promise.resolve()
|
|
279
|
-
.then(() => {
|
|
280
|
-
switch (event) {
|
|
281
|
-
case dirtyContainerEvent:
|
|
282
|
-
if (this._dirtyContainer) {
|
|
283
|
-
listener();
|
|
284
|
-
}
|
|
285
|
-
break;
|
|
286
|
-
case savedContainerEvent:
|
|
287
|
-
if (!this._dirtyContainer) {
|
|
288
|
-
listener();
|
|
289
|
-
}
|
|
290
|
-
break;
|
|
291
|
-
case connectedEventName:
|
|
292
|
-
if (this.connected) {
|
|
293
|
-
listener(this.clientId);
|
|
294
|
-
}
|
|
295
|
-
break;
|
|
296
|
-
case disconnectedEventName:
|
|
297
|
-
if (!this.connected) {
|
|
298
|
-
listener();
|
|
299
|
-
}
|
|
300
|
-
break;
|
|
301
|
-
default:
|
|
302
|
-
}
|
|
303
|
-
})
|
|
304
|
-
.catch((error) => {
|
|
305
|
-
this.mc.logger.sendErrorEvent({ eventName: "RaiseConnectedEventError" }, error);
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
285
|
}
|
|
309
286
|
/**
|
|
310
287
|
* Load an existing container.
|
|
288
|
+
* @internal
|
|
311
289
|
*/
|
|
312
290
|
static async load(loader, loadOptions, pendingLocalState, protocolHandlerBuilder) {
|
|
313
291
|
const container = new Container(loader, {
|
|
@@ -385,7 +363,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
385
363
|
this._lifecycleState === "disposed");
|
|
386
364
|
}
|
|
387
365
|
get storage() {
|
|
388
|
-
return this.
|
|
366
|
+
return this.storageAdapter;
|
|
389
367
|
}
|
|
390
368
|
get context() {
|
|
391
369
|
if (this._context === undefined) {
|
|
@@ -456,6 +434,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
456
434
|
get clientDetails() {
|
|
457
435
|
return this._deltaManager.clientDetails;
|
|
458
436
|
}
|
|
437
|
+
get offlineLoadEnabled() {
|
|
438
|
+
var _a;
|
|
439
|
+
// summarizer will not have any pending state we want to save
|
|
440
|
+
return (((_a = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad")) !== null && _a !== void 0 ? _a : false) &&
|
|
441
|
+
this.clientDetails.capabilities.interactive);
|
|
442
|
+
}
|
|
459
443
|
/**
|
|
460
444
|
* Get the code details that are currently specified for the container.
|
|
461
445
|
* @returns The current code details if any are specified, undefined if none are specified.
|
|
@@ -498,6 +482,38 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
498
482
|
get codeLoader() {
|
|
499
483
|
return this.loader.services.codeLoader;
|
|
500
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
|
|
487
|
+
*/
|
|
488
|
+
async getEntryPoint() {
|
|
489
|
+
var _a, _b;
|
|
490
|
+
// Only the disposing/disposed lifecycle states should prevent access to the entryPoint; closing/closed should still
|
|
491
|
+
// allow it since they mean a kind of read-only state for the Container.
|
|
492
|
+
// Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
|
|
493
|
+
if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
|
|
494
|
+
throw new UsageError("The container is disposing or disposed");
|
|
495
|
+
}
|
|
496
|
+
while (this._context === undefined) {
|
|
497
|
+
await new Promise((resolve, reject) => {
|
|
498
|
+
const contextChangedHandler = () => {
|
|
499
|
+
resolve();
|
|
500
|
+
this.off("disposed", disposedHandler);
|
|
501
|
+
};
|
|
502
|
+
const disposedHandler = (error) => {
|
|
503
|
+
reject(error !== null && error !== void 0 ? error : "The Container is disposed");
|
|
504
|
+
this.off("contextChanged", contextChangedHandler);
|
|
505
|
+
};
|
|
506
|
+
this.once("contextChanged", contextChangedHandler);
|
|
507
|
+
this.once("disposed", disposedHandler);
|
|
508
|
+
});
|
|
509
|
+
// The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
|
|
510
|
+
// should have set this._context; making sure.
|
|
511
|
+
assert(this._context !== undefined, 0x5a2 /* Context still not defined after contextChanged event */);
|
|
512
|
+
}
|
|
513
|
+
// Disable lint rule for the sake of more complete stack traces
|
|
514
|
+
// eslint-disable-next-line no-return-await
|
|
515
|
+
return await ((_b = (_a = this._context).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
516
|
+
}
|
|
501
517
|
/**
|
|
502
518
|
* Retrieves the quorum associated with the document
|
|
503
519
|
*/
|
|
@@ -528,15 +544,19 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
528
544
|
try {
|
|
529
545
|
// Raise event first, to ensure we capture _lifecycleState before transition.
|
|
530
546
|
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
547
|
+
// Log generic events instead of error events if container is in loading state, as most errors are not really FF errors
|
|
548
|
+
// which can pollute telemetry for real bugs
|
|
531
549
|
this.mc.logger.sendTelemetryEvent({
|
|
532
550
|
eventName: "ContainerClose",
|
|
533
|
-
category:
|
|
551
|
+
category: this._lifecycleState !== "loading" && error !== undefined
|
|
552
|
+
? "error"
|
|
553
|
+
: "generic",
|
|
534
554
|
}, error);
|
|
535
555
|
this._lifecycleState = "closing";
|
|
536
556
|
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
537
557
|
this.connectionStateHandler.dispose();
|
|
538
558
|
(_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
539
|
-
this.
|
|
559
|
+
this.storageAdapter.dispose();
|
|
540
560
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
541
561
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
542
562
|
// Driver need to ensure all caches are cleared on critical errors
|
|
@@ -565,7 +585,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
565
585
|
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
566
586
|
this.mc.logger.sendTelemetryEvent({
|
|
567
587
|
eventName: "ContainerDispose",
|
|
568
|
-
category:
|
|
588
|
+
category: "generic",
|
|
569
589
|
}, error);
|
|
570
590
|
// ! Progressing from "closed" to "disposing" is not allowed
|
|
571
591
|
if (this._lifecycleState !== "closed") {
|
|
@@ -574,7 +594,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
574
594
|
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
575
595
|
this.connectionStateHandler.dispose();
|
|
576
596
|
(_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
577
|
-
this.
|
|
597
|
+
this.storageAdapter.dispose();
|
|
578
598
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
579
599
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
580
600
|
// Driver need to ensure all caches are cleared on critical errors
|
|
@@ -597,14 +617,21 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
597
617
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
598
618
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
599
619
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
620
|
+
if (!this.offlineLoadEnabled) {
|
|
621
|
+
throw new UsageError("Can't get pending local state unless offline load is enabled");
|
|
622
|
+
}
|
|
600
623
|
assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
|
|
601
624
|
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
602
625
|
assert(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
|
|
603
626
|
assert(this._protocolHandler.attributes.term !== undefined, 0x37e /* Must have a valid protocol handler instance */);
|
|
627
|
+
assert(!!this.baseSnapshot, "no base snapshot");
|
|
628
|
+
assert(!!this.baseSnapshotBlobs, "no snapshot blobs");
|
|
604
629
|
const pendingState = {
|
|
605
630
|
pendingRuntimeState: this.context.getPendingLocalState(),
|
|
631
|
+
baseSnapshot: this.baseSnapshot,
|
|
632
|
+
snapshotBlobs: this.baseSnapshotBlobs,
|
|
633
|
+
savedOps: this.savedOps,
|
|
606
634
|
url: this.resolvedUrl.url,
|
|
607
|
-
protocol: this.protocolHandler.getProtocolState(),
|
|
608
635
|
term: this._protocolHandler.attributes.term,
|
|
609
636
|
clientId: this.clientId,
|
|
610
637
|
};
|
|
@@ -657,7 +684,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
657
684
|
// starting to attach the container to storage.
|
|
658
685
|
// Also, this should only be fired in detached container.
|
|
659
686
|
this._attachState = AttachState.Attaching;
|
|
660
|
-
this.
|
|
687
|
+
this.emit("attaching");
|
|
688
|
+
if (this.offlineLoadEnabled) {
|
|
689
|
+
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
690
|
+
this.baseSnapshot = snapshot;
|
|
691
|
+
this.baseSnapshotBlobs =
|
|
692
|
+
getBlobContentsFromTreeWithBlobContents(snapshot);
|
|
693
|
+
}
|
|
661
694
|
}
|
|
662
695
|
// Actually go and create the resolved document
|
|
663
696
|
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
@@ -671,7 +704,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
671
704
|
const resolvedUrl = this.service.resolvedUrl;
|
|
672
705
|
ensureFluidResolvedUrl(resolvedUrl);
|
|
673
706
|
this._resolvedUrl = resolvedUrl;
|
|
674
|
-
await this.
|
|
707
|
+
await this.storageAdapter.connectToService(this.service);
|
|
675
708
|
if (hasAttachmentBlobs) {
|
|
676
709
|
// upload blobs to storage
|
|
677
710
|
assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
|
|
@@ -685,7 +718,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
685
718
|
.filter((id) => !redirectTable.has(id));
|
|
686
719
|
for (const id of newIds) {
|
|
687
720
|
const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
|
|
688
|
-
const response = await this.
|
|
721
|
+
const response = await this.storageAdapter.createBlob(blob);
|
|
689
722
|
redirectTable.set(id, response.id);
|
|
690
723
|
}
|
|
691
724
|
}
|
|
@@ -694,8 +727,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
694
727
|
const protocolSummary = this.captureProtocolSummary();
|
|
695
728
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
696
729
|
this._attachState = AttachState.Attaching;
|
|
697
|
-
this.
|
|
698
|
-
|
|
730
|
+
this.emit("attaching");
|
|
731
|
+
if (this.offlineLoadEnabled) {
|
|
732
|
+
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
733
|
+
this.baseSnapshot = snapshot;
|
|
734
|
+
this.baseSnapshotBlobs =
|
|
735
|
+
getBlobContentsFromTreeWithBlobContents(snapshot);
|
|
736
|
+
}
|
|
737
|
+
await this.storageAdapter.uploadSummaryWithContext(summary, {
|
|
699
738
|
referenceSequenceNumber: 0,
|
|
700
739
|
ackHandle: undefined,
|
|
701
740
|
proposalHandle: undefined,
|
|
@@ -830,7 +869,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
830
869
|
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
831
870
|
}
|
|
832
871
|
async getVersion(version) {
|
|
833
|
-
const versions = await this.
|
|
872
|
+
const versions = await this.storageAdapter.getVersions(version, 1);
|
|
834
873
|
return versions[0];
|
|
835
874
|
}
|
|
836
875
|
recordConnectStartTime() {
|
|
@@ -852,6 +891,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
852
891
|
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
853
892
|
*/
|
|
854
893
|
async load(specifiedVersion, loadMode, pendingLocalState) {
|
|
894
|
+
var _a;
|
|
855
895
|
if (this._resolvedUrl === undefined) {
|
|
856
896
|
throw new Error("Attempting to load without a resolved url");
|
|
857
897
|
}
|
|
@@ -872,15 +912,15 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
872
912
|
};
|
|
873
913
|
// Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
|
|
874
914
|
// DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
|
|
875
|
-
if (loadMode.deltaConnection === undefined) {
|
|
915
|
+
if (loadMode.deltaConnection === undefined && !pendingLocalState) {
|
|
876
916
|
this.connectToDeltaStream(connectionArgs);
|
|
877
917
|
}
|
|
878
918
|
if (!pendingLocalState) {
|
|
879
|
-
await this.
|
|
919
|
+
await this.storageAdapter.connectToService(this.service);
|
|
880
920
|
}
|
|
881
921
|
else {
|
|
882
922
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
883
|
-
this.
|
|
923
|
+
this.storageAdapter.connectToService(this.service).catch((error) => {
|
|
884
924
|
var _a;
|
|
885
925
|
this.close(error);
|
|
886
926
|
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
@@ -890,15 +930,23 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
890
930
|
// Fetch specified snapshot.
|
|
891
931
|
const { snapshot, versionId } = pendingLocalState === undefined
|
|
892
932
|
? await this.fetchSnapshotTree(specifiedVersion)
|
|
893
|
-
: { snapshot:
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
933
|
+
: { snapshot: pendingLocalState.baseSnapshot, versionId: undefined };
|
|
934
|
+
if (pendingLocalState) {
|
|
935
|
+
this.baseSnapshot = pendingLocalState.baseSnapshot;
|
|
936
|
+
this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
assert(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
|
|
940
|
+
if (this.offlineLoadEnabled) {
|
|
941
|
+
this.baseSnapshot = snapshot;
|
|
942
|
+
// Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
|
|
943
|
+
this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storage);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
|
|
947
|
+
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
948
|
+
const sequenceNumber = (_a = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.savedOps[pendingLocalState.savedOps.length - 1]) === null || _a === void 0 ? void 0 : _a.sequenceNumber;
|
|
949
|
+
const dmAttributes = sequenceNumber !== undefined ? Object.assign(Object.assign({}, attributes), { sequenceNumber }) : attributes;
|
|
902
950
|
let opsBeforeReturnP;
|
|
903
951
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
904
952
|
// Kick off any ops fetching if required.
|
|
@@ -906,32 +954,35 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
906
954
|
case undefined:
|
|
907
955
|
// Start prefetch, but not set opsBeforeReturnP - boot is not blocked by it!
|
|
908
956
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
909
|
-
this.attachDeltaManagerOpHandler(
|
|
957
|
+
this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
|
|
910
958
|
break;
|
|
911
959
|
case "cached":
|
|
912
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
960
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
|
|
913
961
|
break;
|
|
914
962
|
case "all":
|
|
915
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
963
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "all");
|
|
916
964
|
break;
|
|
917
965
|
default:
|
|
918
966
|
unreachableCase(loadMode.opsBeforeReturn);
|
|
919
967
|
}
|
|
920
968
|
// ...load in the existing quorum
|
|
921
969
|
// Initialize the protocol handler
|
|
922
|
-
|
|
923
|
-
await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot);
|
|
924
|
-
}
|
|
925
|
-
else {
|
|
926
|
-
this.initializeProtocolState(attributes, {
|
|
927
|
-
members: pendingLocalState.protocol.members,
|
|
928
|
-
proposals: pendingLocalState.protocol.proposals,
|
|
929
|
-
values: pendingLocalState.protocol.values,
|
|
930
|
-
});
|
|
931
|
-
}
|
|
970
|
+
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
|
|
932
971
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
933
972
|
await this.instantiateContext(true, // existing
|
|
934
973
|
codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
|
|
974
|
+
// replay saved ops
|
|
975
|
+
if (pendingLocalState) {
|
|
976
|
+
for (const message of pendingLocalState.savedOps) {
|
|
977
|
+
this.processRemoteMessage(message);
|
|
978
|
+
// allow runtime to apply stashed ops at this op's sequence number
|
|
979
|
+
await this.context.notifyOpReplay(message);
|
|
980
|
+
}
|
|
981
|
+
pendingLocalState.savedOps = [];
|
|
982
|
+
// now set clientId to stashed clientId so live ops are correctly processed as local
|
|
983
|
+
assert(this.clientId === undefined, "Unexpected clientId when setting stashed clientId");
|
|
984
|
+
this._clientId = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId;
|
|
985
|
+
}
|
|
935
986
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
936
987
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
937
988
|
if (!this.closed) {
|
|
@@ -944,6 +995,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
944
995
|
}
|
|
945
996
|
switch (loadMode.deltaConnection) {
|
|
946
997
|
case undefined:
|
|
998
|
+
if (pendingLocalState) {
|
|
999
|
+
// connect to delta stream now since we did not before
|
|
1000
|
+
this.connectToDeltaStream(connectionArgs);
|
|
1001
|
+
}
|
|
1002
|
+
// intentional fallthrough
|
|
947
1003
|
case "delayed":
|
|
948
1004
|
assert(this.inboundQueuePausedFromInit, 0x346 /* inboundQueuePausedFromInit should be true */);
|
|
949
1005
|
this.inboundQueuePausedFromInit = false;
|
|
@@ -998,12 +1054,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
998
1054
|
delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
|
|
999
1055
|
}
|
|
1000
1056
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
1001
|
-
this.
|
|
1002
|
-
const attributes = await this.getDocumentAttributes(this.
|
|
1057
|
+
this.storageAdapter.loadSnapshotForRehydratingContainer(snapshotTree);
|
|
1058
|
+
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshotTree);
|
|
1003
1059
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1004
1060
|
// Initialize the protocol handler
|
|
1005
1061
|
const baseTree = getProtocolSnapshotTree(snapshotTree);
|
|
1006
|
-
const qValues = await readAndParse(this.
|
|
1062
|
+
const qValues = await readAndParse(this.storageAdapter, baseTree.blobs.quorumValues);
|
|
1007
1063
|
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
1008
1064
|
this.initializeProtocolState(attributes, {
|
|
1009
1065
|
members: [],
|
|
@@ -1280,16 +1336,16 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1280
1336
|
}
|
|
1281
1337
|
}
|
|
1282
1338
|
/** @returns clientSequenceNumber of last message in a batch */
|
|
1283
|
-
submitBatch(batch) {
|
|
1339
|
+
submitBatch(batch, referenceSequenceNumber) {
|
|
1284
1340
|
let clientSequenceNumber = -1;
|
|
1285
1341
|
for (const message of batch) {
|
|
1286
1342
|
clientSequenceNumber = this.submitMessage(MessageType.Operation, message.contents, true, // batch
|
|
1287
|
-
message.metadata, message.compression);
|
|
1343
|
+
message.metadata, message.compression, referenceSequenceNumber);
|
|
1288
1344
|
}
|
|
1289
1345
|
this._deltaManager.flush();
|
|
1290
1346
|
return clientSequenceNumber;
|
|
1291
1347
|
}
|
|
1292
|
-
submitSummaryMessage(summary) {
|
|
1348
|
+
submitSummaryMessage(summary, referenceSequenceNumber) {
|
|
1293
1349
|
// github #6451: this is only needed for staging so the server
|
|
1294
1350
|
// know when the protocol tree is included
|
|
1295
1351
|
// this can be removed once all clients send
|
|
@@ -1297,10 +1353,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1297
1353
|
if (summary.details === undefined) {
|
|
1298
1354
|
summary.details = {};
|
|
1299
1355
|
}
|
|
1300
|
-
summary.details.includesProtocolTree = this.
|
|
1301
|
-
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch
|
|
1356
|
+
summary.details.includesProtocolTree = this.storageAdapter.summarizeProtocolTree;
|
|
1357
|
+
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
|
|
1302
1358
|
}
|
|
1303
|
-
submitMessage(type, contents, batch, metadata, compression) {
|
|
1359
|
+
submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
|
|
1304
1360
|
var _a;
|
|
1305
1361
|
if (this.connectionState !== ConnectionState.Connected) {
|
|
1306
1362
|
this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
|
|
@@ -1308,9 +1364,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1308
1364
|
}
|
|
1309
1365
|
this.messageCountAfterDisconnection += 1;
|
|
1310
1366
|
(_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
|
|
1311
|
-
return this._deltaManager.submit(type, contents, batch, metadata, compression);
|
|
1367
|
+
return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
|
|
1312
1368
|
}
|
|
1313
1369
|
processRemoteMessage(message) {
|
|
1370
|
+
if (this.offlineLoadEnabled) {
|
|
1371
|
+
this.savedOps.push(message);
|
|
1372
|
+
}
|
|
1314
1373
|
const local = this.clientId === message.clientId;
|
|
1315
1374
|
// Allow the protocol handler to process the message
|
|
1316
1375
|
const result = this.protocolHandler.processMessage(message, local);
|
|
@@ -1362,7 +1421,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1362
1421
|
});
|
|
1363
1422
|
}
|
|
1364
1423
|
this._loadedFromVersion = version;
|
|
1365
|
-
const snapshot = (_a = (await this.
|
|
1424
|
+
const snapshot = (_a = (await this.storageAdapter.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
|
|
1366
1425
|
if (snapshot === undefined && version !== undefined) {
|
|
1367
1426
|
this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
|
|
1368
1427
|
}
|
|
@@ -1381,7 +1440,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1381
1440
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1382
1441
|
// are set. Global requests will still go directly to the loader
|
|
1383
1442
|
const loader = new RelativeLoader(this, this.loader);
|
|
1384
|
-
this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp) => this.submitSummaryMessage(summaryOp), (batch) => this.submitBatch(batch), (message) => this.submitSignal(message), (error) => { var _a; return (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error); }, (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
|
|
1443
|
+
this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (message) => this.submitSignal(message), (error) => { var _a; return (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error); }, (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
|
|
1385
1444
|
this.emit("contextChanged", codeDetails);
|
|
1386
1445
|
}
|
|
1387
1446
|
updateDirtyContainerState(dirty) {
|