@fluidframework/container-loader 2.0.0-internal.5.1.0 → 2.0.0-internal.5.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/CHANGELOG.md +16 -0
- package/dist/catchUpMonitor.d.ts +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +3 -0
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +10 -8
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +20 -27
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +164 -104
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +22 -58
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +27 -200
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +1 -1
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/noopHeuristic.d.ts +23 -0
- package/dist/noopHeuristic.d.ts.map +1 -0
- package/dist/{collabWindowTracker.js → noopHeuristic.js} +30 -42
- package/dist/noopHeuristic.js.map +1 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts +1 -12
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +0 -18
- package/dist/protocol.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +1 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +3 -0
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +10 -8
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +20 -27
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +166 -106
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +22 -58
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +27 -200
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +1 -1
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/noopHeuristic.d.ts +23 -0
- package/lib/noopHeuristic.d.ts.map +1 -0
- package/lib/{collabWindowTracker.js → noopHeuristic.js} +30 -42
- package/lib/noopHeuristic.js.map +1 -0
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts +1 -12
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +0 -18
- package/lib/protocol.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/package.json +11 -11
- package/src/catchUpMonitor.ts +1 -1
- package/src/connectionManager.ts +2 -1
- package/src/connectionStateHandler.ts +14 -9
- package/src/container.ts +247 -126
- package/src/containerContext.ts +32 -318
- package/src/containerStorageAdapter.ts +1 -1
- package/src/{collabWindowTracker.ts → noopHeuristic.ts} +37 -47
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +0 -39
- package/src/protocolTreeDocumentStorageService.ts +1 -1
- package/src/retriableDocumentStorageService.ts +2 -1
- package/dist/collabWindowTracker.d.ts +0 -19
- package/dist/collabWindowTracker.d.ts.map +0 -1
- package/dist/collabWindowTracker.js.map +0 -1
- package/lib/collabWindowTracker.d.ts +0 -19
- package/lib/collabWindowTracker.d.ts.map +0 -1
- package/lib/collabWindowTracker.js.map +0 -1
package/lib/container.js
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
// eslint-disable-next-line import/no-internal-modules
|
|
6
6
|
import merge from "lodash/merge";
|
|
7
7
|
import { v4 as uuid } from "uuid";
|
|
8
|
-
import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
|
|
8
|
+
import { TypedEventEmitter, 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, combineAppAndProtocolSummary, runWithRetry, isCombinedAppAndProtocolSummary, } from "@fluidframework/driver-utils";
|
|
11
|
+
import { readAndParse, OnlineStatus, isOnline, combineAppAndProtocolSummary, runWithRetry, isCombinedAppAndProtocolSummary, MessageType2, canBeCoalescedByService, } from "@fluidframework/driver-utils";
|
|
12
12
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
13
13
|
import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
|
|
14
14
|
import { Audience } from "./audience";
|
|
@@ -22,13 +22,14 @@ import { ContainerStorageAdapter, getBlobContentsFromTree, getBlobContentsFromTr
|
|
|
22
22
|
import { createConnectionStateHandler } from "./connectionStateHandler";
|
|
23
23
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
24
24
|
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy, } from "./quorum";
|
|
25
|
-
import {
|
|
25
|
+
import { NoopHeuristic } from "./noopHeuristic";
|
|
26
26
|
import { ConnectionManager } from "./connectionManager";
|
|
27
27
|
import { ConnectionState } from "./connectionState";
|
|
28
28
|
import { OnlyValidTermValue, ProtocolHandler, } from "./protocol";
|
|
29
29
|
const detachedContainerRefSeqNumber = 0;
|
|
30
30
|
const dirtyContainerEvent = "dirty";
|
|
31
31
|
const savedContainerEvent = "saved";
|
|
32
|
+
const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
|
|
32
33
|
/**
|
|
33
34
|
* Waits until container connects to delta storage and gets up-to-date.
|
|
34
35
|
*
|
|
@@ -156,8 +157,23 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
156
157
|
this.attachStarted = false;
|
|
157
158
|
this._dirtyContainer = false;
|
|
158
159
|
this.savedOps = [];
|
|
160
|
+
this.clientsWhoShouldHaveLeft = new Set();
|
|
159
161
|
this.setAutoReconnectTime = performance.now();
|
|
162
|
+
this._lifecycleEvents = new TypedEventEmitter();
|
|
160
163
|
this._disposed = false;
|
|
164
|
+
this.getAbsoluteUrl = async (relativeUrl) => {
|
|
165
|
+
if (this.resolvedUrl === undefined) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, getPackageName(this._loadedCodeDetails));
|
|
169
|
+
};
|
|
170
|
+
this.updateDirtyContainerState = (dirty) => {
|
|
171
|
+
if (this._dirtyContainer === dirty) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this._dirtyContainer = dirty;
|
|
175
|
+
this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
|
|
176
|
+
};
|
|
161
177
|
const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
|
|
162
178
|
this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
|
|
163
179
|
const pendingLocalState = loadProps === null || loadProps === void 0 ? void 0 : loadProps.pendingLocalState;
|
|
@@ -202,8 +218,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
202
218
|
dmInitialSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.initialSequenceNumber; },
|
|
203
219
|
dmLastProcessedSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastSequenceNumber; },
|
|
204
220
|
dmLastKnownSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastKnownSeqNumber; },
|
|
205
|
-
containerLoadedFromVersionId: () => { var _a; return (_a = this.
|
|
206
|
-
containerLoadedFromVersionDate: () => { var _a; return (_a = this.
|
|
221
|
+
containerLoadedFromVersionId: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
|
|
222
|
+
containerLoadedFromVersionDate: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
|
|
207
223
|
// message information to associate errors with the specific execution state
|
|
208
224
|
// dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
|
|
209
225
|
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; },
|
|
@@ -254,6 +270,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
254
270
|
this.connect();
|
|
255
271
|
}
|
|
256
272
|
},
|
|
273
|
+
clientShouldHaveLeft: (clientId) => {
|
|
274
|
+
this.clientsWhoShouldHaveLeft.add(clientId);
|
|
275
|
+
},
|
|
257
276
|
}, this.deltaManager, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId);
|
|
258
277
|
this.on(savedContainerEvent, () => {
|
|
259
278
|
this.connectionStateHandler.containerSaved();
|
|
@@ -296,6 +315,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
296
315
|
static async load(loadProps, createProps) {
|
|
297
316
|
const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
|
|
298
317
|
const container = new Container(createProps, loadProps);
|
|
318
|
+
const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
|
|
299
319
|
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
|
|
300
320
|
const defaultMode = { opsBeforeReturn: "cached" };
|
|
301
321
|
// if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
|
|
@@ -323,7 +343,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
323
343
|
container.close(err);
|
|
324
344
|
onClosed(err);
|
|
325
345
|
});
|
|
326
|
-
}), { start: true, end: true, cancel: "generic" });
|
|
346
|
+
}), { start: true, end: true, cancel: "generic" }, disableRecordHeapSize !== true /* recordHeapSize */);
|
|
327
347
|
}
|
|
328
348
|
/**
|
|
329
349
|
* Create a new container in a detached state.
|
|
@@ -362,14 +382,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
362
382
|
this._lifecycleState === "disposing" ||
|
|
363
383
|
this._lifecycleState === "disposed");
|
|
364
384
|
}
|
|
365
|
-
get
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
get context() {
|
|
369
|
-
if (this._context === undefined) {
|
|
370
|
-
throw new GenericError("Attempted to access context before it was defined");
|
|
385
|
+
get runtime() {
|
|
386
|
+
if (this._runtime === undefined) {
|
|
387
|
+
throw new Error("Attempted to access runtime before it was defined");
|
|
371
388
|
}
|
|
372
|
-
return this.
|
|
389
|
+
return this._runtime;
|
|
373
390
|
}
|
|
374
391
|
get protocolHandler() {
|
|
375
392
|
if (this._protocolHandler === undefined) {
|
|
@@ -398,15 +415,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
398
415
|
*/
|
|
399
416
|
return (_a = this.service) === null || _a === void 0 ? void 0 : _a.resolvedUrl;
|
|
400
417
|
}
|
|
401
|
-
get loadedFromVersion() {
|
|
402
|
-
return this._loadedFromVersion;
|
|
403
|
-
}
|
|
404
418
|
get readOnlyInfo() {
|
|
405
419
|
return this._deltaManager.readOnlyInfo;
|
|
406
420
|
}
|
|
407
|
-
get closeSignal() {
|
|
408
|
-
return this._deltaManager.closeAbortController.signal;
|
|
409
|
-
}
|
|
410
421
|
/**
|
|
411
422
|
* Tracks host requiring read-only mode.
|
|
412
423
|
*/
|
|
@@ -422,13 +433,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
422
433
|
get connected() {
|
|
423
434
|
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
424
435
|
}
|
|
425
|
-
/**
|
|
426
|
-
* Service configuration details. If running in offline mode will be undefined otherwise will contain service
|
|
427
|
-
* configuration details returned as part of the initial connection.
|
|
428
|
-
*/
|
|
429
|
-
get serviceConfiguration() {
|
|
430
|
-
return this._deltaManager.serviceConfiguration;
|
|
431
|
-
}
|
|
432
436
|
/**
|
|
433
437
|
* The server provided id of the client.
|
|
434
438
|
* Set once this.connected is true, otherwise undefined
|
|
@@ -436,21 +440,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
436
440
|
get clientId() {
|
|
437
441
|
return this._clientId;
|
|
438
442
|
}
|
|
439
|
-
/**
|
|
440
|
-
* The server provided claims of the client.
|
|
441
|
-
* Set once this.connected is true, otherwise undefined
|
|
442
|
-
*/
|
|
443
|
-
get scopes() {
|
|
444
|
-
return this._deltaManager.connectionManager.scopes;
|
|
445
|
-
}
|
|
446
|
-
get clientDetails() {
|
|
447
|
-
return this._deltaManager.clientDetails;
|
|
448
|
-
}
|
|
449
443
|
get offlineLoadEnabled() {
|
|
450
444
|
var _a, _b;
|
|
451
445
|
const enabled = (_a = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad")) !== null && _a !== void 0 ? _a : ((_b = this.options) === null || _b === void 0 ? void 0 : _b.enableOfflineLoad) === true;
|
|
452
446
|
// summarizer will not have any pending state we want to save
|
|
453
|
-
return enabled && this.clientDetails.capabilities.interactive;
|
|
447
|
+
return enabled && this.deltaManager.clientDetails.capabilities.interactive;
|
|
454
448
|
}
|
|
455
449
|
/**
|
|
456
450
|
* Get the code details that are currently specified for the container.
|
|
@@ -465,8 +459,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
465
459
|
* loaded.
|
|
466
460
|
*/
|
|
467
461
|
getLoadedCodeDetails() {
|
|
468
|
-
|
|
469
|
-
return (_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails;
|
|
462
|
+
return this._loadedCodeDetails;
|
|
470
463
|
}
|
|
471
464
|
/**
|
|
472
465
|
* Retrieves the audience associated with the document
|
|
@@ -487,32 +480,26 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
487
480
|
*/
|
|
488
481
|
async getEntryPoint() {
|
|
489
482
|
var _a, _b;
|
|
490
|
-
|
|
491
|
-
|
|
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");
|
|
483
|
+
if (this._disposed) {
|
|
484
|
+
throw new UsageError("The context is already disposed");
|
|
495
485
|
}
|
|
496
|
-
|
|
497
|
-
|
|
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 */);
|
|
486
|
+
if (this._runtime !== undefined) {
|
|
487
|
+
return (_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
512
488
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
489
|
+
return new Promise((resolve, reject) => {
|
|
490
|
+
const runtimeInstantiatedHandler = () => {
|
|
491
|
+
var _a, _b;
|
|
492
|
+
assert(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
|
|
493
|
+
resolve((_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
494
|
+
this._lifecycleEvents.off("disposed", disposedHandler);
|
|
495
|
+
};
|
|
496
|
+
const disposedHandler = () => {
|
|
497
|
+
reject(new Error("ContainerContext was disposed"));
|
|
498
|
+
this._lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
499
|
+
};
|
|
500
|
+
this._lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
501
|
+
this._lifecycleEvents.once("disposed", disposedHandler);
|
|
502
|
+
});
|
|
516
503
|
}
|
|
517
504
|
/**
|
|
518
505
|
* Retrieves the quorum associated with the document
|
|
@@ -579,7 +566,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
579
566
|
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
580
567
|
this.mc.logger.sendTelemetryEvent({
|
|
581
568
|
eventName: "ContainerDispose",
|
|
582
|
-
|
|
569
|
+
// Only log error if container isn't closed
|
|
570
|
+
category: !this.closed && error !== undefined ? "error" : "generic",
|
|
583
571
|
}, error);
|
|
584
572
|
// ! Progressing from "closed" to "disposing" is not allowed
|
|
585
573
|
if (this._lifecycleState !== "closed") {
|
|
@@ -587,7 +575,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
587
575
|
}
|
|
588
576
|
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
589
577
|
this.connectionStateHandler.dispose();
|
|
590
|
-
|
|
578
|
+
const maybeError = error !== undefined ? new Error(error.message) : undefined;
|
|
579
|
+
(_b = this._runtime) === null || _b === void 0 ? void 0 : _b.dispose(maybeError);
|
|
591
580
|
this.storageAdapter.dispose();
|
|
592
581
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
593
582
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
@@ -605,6 +594,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
605
594
|
}
|
|
606
595
|
finally {
|
|
607
596
|
this._lifecycleState = "disposed";
|
|
597
|
+
this._lifecycleEvents.emit("disposed");
|
|
608
598
|
}
|
|
609
599
|
}
|
|
610
600
|
closeAndGetPendingLocalState() {
|
|
@@ -624,7 +614,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
624
614
|
assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
625
615
|
assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
626
616
|
const pendingState = {
|
|
627
|
-
pendingRuntimeState: this.
|
|
617
|
+
pendingRuntimeState: this.runtime.getPendingLocalState(),
|
|
628
618
|
baseSnapshot: this.baseSnapshot,
|
|
629
619
|
snapshotBlobs: this.baseSnapshotBlobs,
|
|
630
620
|
savedOps: this.savedOps,
|
|
@@ -640,7 +630,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
640
630
|
}
|
|
641
631
|
serialize() {
|
|
642
632
|
assert(this.attachState === AttachState.Detached, 0x0d3 /* "Should only be called in detached container" */);
|
|
643
|
-
const appSummary = this.
|
|
633
|
+
const appSummary = this.runtime.createSummary();
|
|
644
634
|
const protocolSummary = this.captureProtocolSummary();
|
|
645
635
|
const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
646
636
|
if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
|
|
@@ -669,7 +659,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
669
659
|
if (!hasAttachmentBlobs) {
|
|
670
660
|
// Get the document state post attach - possibly can just call attach but we need to change the
|
|
671
661
|
// semantics around what the attach means as far as async code goes.
|
|
672
|
-
const appSummary = this.
|
|
662
|
+
const appSummary = this.runtime.createSummary();
|
|
673
663
|
const protocolSummary = this.captureProtocolSummary();
|
|
674
664
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
675
665
|
// Set the state as attaching as we are starting the process of attaching container.
|
|
@@ -677,6 +667,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
677
667
|
// starting to attach the container to storage.
|
|
678
668
|
// Also, this should only be fired in detached container.
|
|
679
669
|
this._attachState = AttachState.Attaching;
|
|
670
|
+
this.runtime.setAttachState(AttachState.Attaching);
|
|
680
671
|
this.emit("attaching");
|
|
681
672
|
if (this.offlineLoadEnabled) {
|
|
682
673
|
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
@@ -691,7 +682,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
691
682
|
assert(this.client.details.type !== summarizerClientType &&
|
|
692
683
|
createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
|
|
693
684
|
this.service = await runWithRetry(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
|
|
694
|
-
cancel: this.
|
|
685
|
+
cancel: this._deltaManager.closeAbortController.signal,
|
|
695
686
|
});
|
|
696
687
|
}
|
|
697
688
|
await this.storageAdapter.connectToService(this.service);
|
|
@@ -713,10 +704,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
713
704
|
}
|
|
714
705
|
}
|
|
715
706
|
// take summary and upload
|
|
716
|
-
const appSummary = this.
|
|
707
|
+
const appSummary = this.runtime.createSummary(redirectTable);
|
|
717
708
|
const protocolSummary = this.captureProtocolSummary();
|
|
718
709
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
719
710
|
this._attachState = AttachState.Attaching;
|
|
711
|
+
this.runtime.setAttachState(AttachState.Attaching);
|
|
720
712
|
this.emit("attaching");
|
|
721
713
|
if (this.offlineLoadEnabled) {
|
|
722
714
|
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
@@ -731,6 +723,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
731
723
|
});
|
|
732
724
|
}
|
|
733
725
|
this._attachState = AttachState.Attached;
|
|
726
|
+
this.runtime.setAttachState(AttachState.Attached);
|
|
734
727
|
this.emit("attached");
|
|
735
728
|
if (!this.closed) {
|
|
736
729
|
this.resumeInternal({
|
|
@@ -749,7 +742,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
749
742
|
}, { start: true, end: true, cancel: "generic" });
|
|
750
743
|
}
|
|
751
744
|
async request(path) {
|
|
752
|
-
return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.
|
|
745
|
+
return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
|
|
753
746
|
}
|
|
754
747
|
setAutoReconnectInternal(mode) {
|
|
755
748
|
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
@@ -815,13 +808,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
815
808
|
// Ensure connection to web socket
|
|
816
809
|
this.connectToDeltaStream(args);
|
|
817
810
|
}
|
|
818
|
-
async getAbsoluteUrl(relativeUrl) {
|
|
819
|
-
var _a;
|
|
820
|
-
if (this.resolvedUrl === undefined) {
|
|
821
|
-
return undefined;
|
|
822
|
-
}
|
|
823
|
-
return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, getPackageName((_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails));
|
|
824
|
-
}
|
|
825
811
|
async proposeCodeDetails(codeDetails) {
|
|
826
812
|
if (!isFluidCodeDetails(codeDetails)) {
|
|
827
813
|
throw new Error("Provided codeDetails are not IFluidCodeDetails");
|
|
@@ -843,7 +829,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
843
829
|
this.deltaManager.inbound.pause(),
|
|
844
830
|
this.deltaManager.inboundSignal.pause(),
|
|
845
831
|
]);
|
|
846
|
-
if ((await this.
|
|
832
|
+
if ((await this.satisfies(codeDetails)) === true) {
|
|
847
833
|
this.deltaManager.inbound.resume();
|
|
848
834
|
this.deltaManager.inboundSignal.resume();
|
|
849
835
|
return;
|
|
@@ -852,6 +838,38 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
852
838
|
const error = new GenericError("Existing context does not satisfy incoming proposal");
|
|
853
839
|
this.close(error);
|
|
854
840
|
}
|
|
841
|
+
/**
|
|
842
|
+
* Determines if the currently loaded module satisfies the incoming constraint code details
|
|
843
|
+
*/
|
|
844
|
+
async satisfies(constraintCodeDetails) {
|
|
845
|
+
var _a, _b;
|
|
846
|
+
// If we have no module, it can't satisfy anything.
|
|
847
|
+
if (this._loadedModule === undefined) {
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
const comparers = [];
|
|
851
|
+
const maybeCompareCodeLoader = this.codeLoader;
|
|
852
|
+
if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
|
|
853
|
+
comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
|
|
854
|
+
}
|
|
855
|
+
const maybeCompareExport = (_a = this._loadedModule) === null || _a === void 0 ? void 0 : _a.module.fluidExport;
|
|
856
|
+
if ((maybeCompareExport === null || maybeCompareExport === void 0 ? void 0 : maybeCompareExport.IFluidCodeDetailsComparer) !== undefined) {
|
|
857
|
+
comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
|
|
858
|
+
}
|
|
859
|
+
// If there are no comparers, then it's impossible to know if the currently loaded package satisfies
|
|
860
|
+
// the incoming constraint, so we return false. Assuming it does not satisfy is safer, to force a reload
|
|
861
|
+
// rather than potentially running with incompatible code.
|
|
862
|
+
if (comparers.length === 0) {
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
for (const comparer of comparers) {
|
|
866
|
+
const satisfies = await comparer.satisfies((_b = this._loadedModule) === null || _b === void 0 ? void 0 : _b.details, constraintCodeDetails);
|
|
867
|
+
if (satisfies === false) {
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
855
873
|
async getVersion(version) {
|
|
856
874
|
const versions = await this.storageAdapter.getVersions(version, 1);
|
|
857
875
|
return versions[0];
|
|
@@ -869,7 +887,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
869
887
|
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
870
888
|
*/
|
|
871
889
|
async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
|
|
872
|
-
var _a;
|
|
890
|
+
var _a, _b, _c;
|
|
873
891
|
this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
|
|
874
892
|
// Ideally we always connect as "read" by default.
|
|
875
893
|
// Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
|
|
@@ -913,7 +931,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
913
931
|
if (this.offlineLoadEnabled) {
|
|
914
932
|
this.baseSnapshot = snapshot;
|
|
915
933
|
// Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
|
|
916
|
-
this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.
|
|
934
|
+
this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storageAdapter);
|
|
917
935
|
}
|
|
918
936
|
}
|
|
919
937
|
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
|
|
@@ -949,7 +967,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
949
967
|
for (const message of pendingLocalState.savedOps) {
|
|
950
968
|
this.processRemoteMessage(message);
|
|
951
969
|
// allow runtime to apply stashed ops at this op's sequence number
|
|
952
|
-
await this.
|
|
970
|
+
await ((_c = (_b = this.runtime).notifyOpReplay) === null || _c === void 0 ? void 0 : _c.call(_b, message));
|
|
953
971
|
}
|
|
954
972
|
pendingLocalState.savedOps = [];
|
|
955
973
|
// now set clientId to stashed clientId so live ops are correctly processed as local
|
|
@@ -1188,7 +1206,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1188
1206
|
});
|
|
1189
1207
|
deltaManager.on("disconnect", (reason, error) => {
|
|
1190
1208
|
var _a;
|
|
1191
|
-
(_a = this.
|
|
1209
|
+
(_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyDisconnect();
|
|
1192
1210
|
if (!this.closed) {
|
|
1193
1211
|
this.connectionStateHandler.receivedDisconnectEvent(reason, error);
|
|
1194
1212
|
}
|
|
@@ -1332,7 +1350,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1332
1350
|
return -1;
|
|
1333
1351
|
}
|
|
1334
1352
|
this.messageCountAfterDisconnection += 1;
|
|
1335
|
-
(_a = this.
|
|
1353
|
+
(_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyMessageSent();
|
|
1336
1354
|
return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
|
|
1337
1355
|
}
|
|
1338
1356
|
processRemoteMessage(message) {
|
|
@@ -1340,24 +1358,51 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1340
1358
|
this.savedOps.push(message);
|
|
1341
1359
|
}
|
|
1342
1360
|
const local = this.clientId === message.clientId;
|
|
1361
|
+
// Check and report if we're getting messages from a clientId that we previously
|
|
1362
|
+
// flagged should have left, or from a client that's not in the quorum but should be
|
|
1363
|
+
if (message.clientId != null) {
|
|
1364
|
+
const client = this.protocolHandler.quorum.getMember(message.clientId);
|
|
1365
|
+
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
1366
|
+
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
1367
|
+
throw new Error("Remote message's clientId is missing from the quorum");
|
|
1368
|
+
}
|
|
1369
|
+
// Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
|
|
1370
|
+
// It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
|
|
1371
|
+
// document we don't need to blow up aggressively.
|
|
1372
|
+
if (this.clientsWhoShouldHaveLeft.has(message.clientId) &&
|
|
1373
|
+
!canBeCoalescedByService(message)) {
|
|
1374
|
+
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
1375
|
+
throw new Error("Remote message's clientId already should have left");
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1343
1378
|
// Allow the protocol handler to process the message
|
|
1344
1379
|
const result = this.protocolHandler.processMessage(message, local);
|
|
1345
1380
|
// Forward messages to the loaded runtime for processing
|
|
1346
|
-
this.
|
|
1381
|
+
this.runtime.process(message, local);
|
|
1347
1382
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
1348
1383
|
if (this.activeConnection()) {
|
|
1349
|
-
if (this.
|
|
1384
|
+
if (this.noopHeuristic === undefined) {
|
|
1385
|
+
const serviceConfiguration = this.deltaManager.serviceConfiguration;
|
|
1350
1386
|
// Note that config from first connection will be used for this container's lifetime.
|
|
1351
1387
|
// That means that if relay service changes settings, such changes will impact only newly booted
|
|
1352
1388
|
// clients.
|
|
1353
1389
|
// All existing will continue to use settings they got earlier.
|
|
1354
|
-
assert(
|
|
1355
|
-
this.
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1390
|
+
assert(serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
|
|
1391
|
+
this.noopHeuristic = new NoopHeuristic(serviceConfiguration.noopTimeFrequency, serviceConfiguration.noopCountFrequency);
|
|
1392
|
+
this.noopHeuristic.on("wantsNoop", () => {
|
|
1393
|
+
// On disconnect we notify the heuristic which should prevent it from wanting a noop.
|
|
1394
|
+
// Hitting this assert would imply we lost activeConnection between notifying the heuristic of a processed message and
|
|
1395
|
+
// running the microtask that the heuristic queued in response.
|
|
1396
|
+
assert(this.activeConnection(), 0x241 /* "Trying to send noop without active connection" */);
|
|
1397
|
+
this.submitMessage(MessageType.NoOp);
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
this.noopHeuristic.notifyMessageProcessed(message);
|
|
1401
|
+
// The contract with the protocolHandler is that returning "immediateNoOp" is equivalent to "please immediately accept the proposal I just processed".
|
|
1402
|
+
if (result.immediateNoOp === true) {
|
|
1403
|
+
// ADO:1385: Remove cast and use MessageType once definition changes propagate
|
|
1404
|
+
this.submitMessage(MessageType2.Accept);
|
|
1359
1405
|
}
|
|
1360
|
-
this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
|
|
1361
1406
|
}
|
|
1362
1407
|
this.emit("op", message);
|
|
1363
1408
|
}
|
|
@@ -1371,7 +1416,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1371
1416
|
}
|
|
1372
1417
|
else {
|
|
1373
1418
|
const local = this.clientId === message.clientId;
|
|
1374
|
-
this.
|
|
1419
|
+
this.runtime.processSignal(message, local);
|
|
1375
1420
|
}
|
|
1376
1421
|
}
|
|
1377
1422
|
/**
|
|
@@ -1404,21 +1449,37 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1404
1449
|
await this.instantiateContext(existing, codeDetails, snapshot);
|
|
1405
1450
|
}
|
|
1406
1451
|
async instantiateContext(existing, codeDetails, snapshot, pendingLocalState) {
|
|
1407
|
-
var _a;
|
|
1408
|
-
assert(((_a = this.
|
|
1452
|
+
var _a, _b;
|
|
1453
|
+
assert(((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
1409
1454
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1410
1455
|
// are set. Global requests will still go directly to the loader
|
|
1411
1456
|
const maybeLoader = this.scope;
|
|
1412
1457
|
const loader = new RelativeLoader(this, maybeLoader.ILoader);
|
|
1413
|
-
|
|
1414
|
-
this.
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1458
|
+
const loadCodeResult = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "CodeLoad" }, async () => this.codeLoader.load(codeDetails));
|
|
1459
|
+
this._loadedModule = {
|
|
1460
|
+
module: loadCodeResult.module,
|
|
1461
|
+
// An older interface ICodeLoader could return an IFluidModule which didn't have details.
|
|
1462
|
+
// If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
|
|
1463
|
+
// TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
|
|
1464
|
+
details: (_b = loadCodeResult.details) !== null && _b !== void 0 ? _b : codeDetails,
|
|
1465
|
+
};
|
|
1466
|
+
const fluidExport = this._loadedModule.module.fluidExport;
|
|
1467
|
+
const runtimeFactory = fluidExport === null || fluidExport === void 0 ? void 0 : fluidExport.IRuntimeFactory;
|
|
1468
|
+
if (runtimeFactory === undefined) {
|
|
1469
|
+
throw new Error(packageNotFactoryError);
|
|
1419
1470
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1471
|
+
const deltaManagerProxy = new DeltaManagerProxy(this._deltaManager);
|
|
1472
|
+
const quorumProxy = new QuorumProxy(this.protocolHandler.quorum);
|
|
1473
|
+
const context = new ContainerContext(this.options, this.scope, snapshot, this._loadedFromVersion, deltaManagerProxy, this.storageAdapter, quorumProxy, this.protocolHandler.audience, 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) => this.dispose(error), (error) => this.close(error), this.updateDirtyContainerState, this.getAbsoluteUrl, () => { var _a; return (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.id; }, () => this.clientId, () => deltaManagerProxy.serviceConfiguration, () => this.attachState, () => this.connected, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState);
|
|
1474
|
+
this._lifecycleEvents.once("disposed", () => {
|
|
1475
|
+
context.dispose();
|
|
1476
|
+
quorumProxy.dispose();
|
|
1477
|
+
deltaManagerProxy.dispose();
|
|
1478
|
+
});
|
|
1479
|
+
this._runtime = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
|
|
1480
|
+
this._lifecycleEvents.emit("runtimeInstantiated");
|
|
1481
|
+
this._loadedCodeDetails = codeDetails;
|
|
1482
|
+
this.emit("contextChanged", codeDetails);
|
|
1422
1483
|
}
|
|
1423
1484
|
/**
|
|
1424
1485
|
* Set the connected state of the ContainerContext
|
|
@@ -1428,16 +1489,15 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1428
1489
|
*/
|
|
1429
1490
|
setContextConnectedState(state, readonly) {
|
|
1430
1491
|
var _a;
|
|
1431
|
-
if (((_a = this.
|
|
1492
|
+
if (((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
|
|
1432
1493
|
/**
|
|
1433
1494
|
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
1434
1495
|
* ops getting through to the DeltaManager.
|
|
1435
1496
|
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
1436
1497
|
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
1437
1498
|
*/
|
|
1438
|
-
this.
|
|
1499
|
+
this.runtime.setConnectionState(state && !readonly, this.clientId);
|
|
1439
1500
|
}
|
|
1440
1501
|
}
|
|
1441
1502
|
}
|
|
1442
|
-
Container.version = "^0.1.0";
|
|
1443
1503
|
//# sourceMappingURL=container.js.map
|