@fluidframework/container-loader 2.0.0-dev.5.2.0.169897 → 2.0.0-dev.6.4.0.191258
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 +162 -0
- package/README.md +10 -6
- package/dist/audience.d.ts +1 -0
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +5 -3
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.d.ts +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js +2 -2
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/connectionManager.d.ts +6 -6
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +97 -93
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +19 -15
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +59 -59
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +48 -38
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +447 -325
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +22 -70
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +24 -221
- 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 +47 -16
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +21 -10
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +3 -3
- package/dist/contracts.js.map +1 -1
- package/dist/debugLogger.d.ts +30 -0
- package/dist/debugLogger.d.ts.map +1 -0
- package/dist/debugLogger.js +95 -0
- package/dist/debugLogger.js.map +1 -0
- package/dist/deltaManager.d.ts +21 -9
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +114 -66
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaQueue.d.ts +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +10 -10
- package/dist/deltaQueue.js.map +1 -1
- package/dist/disposal.d.ts +13 -0
- package/dist/disposal.d.ts.map +1 -0
- package/dist/disposal.js +25 -0
- package/dist/disposal.js.map +1 -0
- package/dist/error.d.ts +23 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +32 -0
- package/dist/error.js.map +1 -0
- package/dist/loader.d.ts +23 -5
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +82 -51
- package/dist/loader.js.map +1 -1
- package/dist/noopHeuristic.d.ts +23 -0
- package/dist/noopHeuristic.d.ts.map +1 -0
- package/dist/noopHeuristic.js +90 -0
- 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 +9 -12
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +26 -7
- 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/quorum.d.ts +1 -14
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js +1 -29
- package/dist/quorum.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +1 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +4 -4
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts +8 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +30 -11
- package/dist/utils.js.map +1 -1
- package/lib/audience.d.ts +1 -0
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js +4 -2
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/connectionManager.d.ts +6 -6
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +74 -67
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +19 -15
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +36 -36
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +48 -38
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +414 -292
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +22 -70
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +24 -221
- 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 +43 -12
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +21 -10
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js +3 -3
- package/lib/contracts.js.map +1 -1
- package/lib/debugLogger.d.ts +30 -0
- package/lib/debugLogger.d.ts.map +1 -0
- package/lib/debugLogger.js +91 -0
- package/lib/debugLogger.js.map +1 -0
- package/lib/deltaManager.d.ts +21 -9
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +88 -37
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaQueue.d.ts +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +3 -3
- package/lib/deltaQueue.js.map +1 -1
- package/lib/disposal.d.ts +13 -0
- package/lib/disposal.d.ts.map +1 -0
- package/lib/disposal.js +21 -0
- package/lib/disposal.js.map +1 -0
- package/lib/error.d.ts +23 -0
- package/lib/error.d.ts.map +1 -0
- package/lib/error.js +28 -0
- package/lib/error.js.map +1 -0
- package/lib/loader.d.ts +23 -5
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +82 -51
- package/lib/loader.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} +31 -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 +9 -12
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +24 -6
- 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/quorum.d.ts +1 -14
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js +0 -26
- package/lib/quorum.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +2 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts +8 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +25 -7
- package/lib/utils.js.map +1 -1
- package/package.json +26 -28
- package/src/audience.ts +7 -1
- package/src/catchUpMonitor.ts +2 -2
- package/src/connectionManager.ts +76 -52
- package/src/connectionStateHandler.ts +46 -48
- package/src/container.ts +561 -326
- package/src/containerContext.ts +31 -349
- package/src/containerStorageAdapter.ts +49 -6
- package/src/contracts.ts +27 -13
- package/src/debugLogger.ts +113 -0
- package/src/deltaManager.ts +93 -36
- package/src/deltaQueue.ts +2 -1
- package/src/disposal.ts +25 -0
- package/src/error.ts +44 -0
- package/src/loader.ts +84 -36
- package/src/{collabWindowTracker.ts → noopHeuristic.ts} +38 -47
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +26 -16
- package/src/protocolTreeDocumentStorageService.ts +1 -1
- package/src/quorum.ts +1 -40
- package/src/retriableDocumentStorageService.ts +3 -4
- package/src/utils.ts +33 -8
- package/dist/collabWindowTracker.d.ts +0 -19
- package/dist/collabWindowTracker.d.ts.map +0 -1
- package/dist/collabWindowTracker.js +0 -101
- package/dist/collabWindowTracker.js.map +0 -1
- package/dist/deltaManagerProxy.d.ts +0 -42
- package/dist/deltaManagerProxy.d.ts.map +0 -1
- package/dist/deltaManagerProxy.js +0 -79
- package/dist/deltaManagerProxy.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/deltaManagerProxy.d.ts +0 -42
- package/lib/deltaManagerProxy.d.ts.map +0 -1
- package/lib/deltaManagerProxy.js +0 -74
- package/lib/deltaManagerProxy.js.map +0 -1
- package/src/deltaManagerProxy.ts +0 -109
package/dist/container.js
CHANGED
|
@@ -11,9 +11,10 @@ exports.Container = exports.ReportIfTooLong = exports.waitContainerToCatchUp = v
|
|
|
11
11
|
// eslint-disable-next-line import/no-internal-modules
|
|
12
12
|
const merge_1 = __importDefault(require("lodash/merge"));
|
|
13
13
|
const uuid_1 = require("uuid");
|
|
14
|
-
const
|
|
14
|
+
const core_utils_1 = require("@fluidframework/core-utils");
|
|
15
|
+
const client_utils_1 = require("@fluid-internal/client-utils");
|
|
16
|
+
const core_interfaces_1 = require("@fluidframework/core-interfaces");
|
|
15
17
|
const container_definitions_1 = require("@fluidframework/container-definitions");
|
|
16
|
-
const container_utils_1 = require("@fluidframework/container-utils");
|
|
17
18
|
const driver_utils_1 = require("@fluidframework/driver-utils");
|
|
18
19
|
const protocol_definitions_1 = require("@fluidframework/protocol-definitions");
|
|
19
20
|
const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
|
|
@@ -21,20 +22,20 @@ const audience_1 = require("./audience");
|
|
|
21
22
|
const containerContext_1 = require("./containerContext");
|
|
22
23
|
const contracts_1 = require("./contracts");
|
|
23
24
|
const deltaManager_1 = require("./deltaManager");
|
|
24
|
-
const deltaManagerProxy_1 = require("./deltaManagerProxy");
|
|
25
25
|
const loader_1 = require("./loader");
|
|
26
26
|
const packageVersion_1 = require("./packageVersion");
|
|
27
27
|
const containerStorageAdapter_1 = require("./containerStorageAdapter");
|
|
28
28
|
const connectionStateHandler_1 = require("./connectionStateHandler");
|
|
29
29
|
const utils_1 = require("./utils");
|
|
30
30
|
const quorum_1 = require("./quorum");
|
|
31
|
-
const
|
|
31
|
+
const noopHeuristic_1 = require("./noopHeuristic");
|
|
32
32
|
const connectionManager_1 = require("./connectionManager");
|
|
33
33
|
const connectionState_1 = require("./connectionState");
|
|
34
34
|
const protocol_1 = require("./protocol");
|
|
35
35
|
const detachedContainerRefSeqNumber = 0;
|
|
36
36
|
const dirtyContainerEvent = "dirty";
|
|
37
37
|
const savedContainerEvent = "saved";
|
|
38
|
+
const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
|
|
38
39
|
/**
|
|
39
40
|
* Waits until container connects to delta storage and gets up-to-date.
|
|
40
41
|
*
|
|
@@ -54,7 +55,7 @@ const savedContainerEvent = "saved";
|
|
|
54
55
|
async function waitContainerToCatchUp(container) {
|
|
55
56
|
// Make sure we stop waiting if container is closed.
|
|
56
57
|
if (container.closed) {
|
|
57
|
-
throw new
|
|
58
|
+
throw new telemetry_utils_1.UsageError("waitContainerToCatchUp: Container closed");
|
|
58
59
|
}
|
|
59
60
|
return new Promise((resolve, reject) => {
|
|
60
61
|
const deltaManager = container.deltaManager;
|
|
@@ -62,8 +63,8 @@ async function waitContainerToCatchUp(container) {
|
|
|
62
63
|
container.off("closed", closedCallback);
|
|
63
64
|
const baseMessage = "Container closed while waiting to catch up";
|
|
64
65
|
reject(err !== undefined
|
|
65
|
-
? (0, telemetry_utils_1.wrapError)(err, (innerMessage) => new
|
|
66
|
-
: new
|
|
66
|
+
? (0, telemetry_utils_1.wrapError)(err, (innerMessage) => new telemetry_utils_1.GenericError(`${baseMessage}: ${innerMessage}`))
|
|
67
|
+
: new telemetry_utils_1.GenericError(baseMessage));
|
|
67
68
|
};
|
|
68
69
|
container.on("closed", closedCallback);
|
|
69
70
|
// Depending on config, transition to "connected" state may include the guarantee
|
|
@@ -71,11 +72,11 @@ async function waitContainerToCatchUp(container) {
|
|
|
71
72
|
// Waiting for "connected" state in either case gets us at least to our own Join op
|
|
72
73
|
// which is a reasonable approximation of "caught up"
|
|
73
74
|
const waitForOps = () => {
|
|
74
|
-
(0,
|
|
75
|
+
(0, core_utils_1.assert)(container.connectionState === connectionState_1.ConnectionState.CatchingUp ||
|
|
75
76
|
container.connectionState === connectionState_1.ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
|
|
76
77
|
const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
|
|
77
78
|
const connectionOpSeqNumber = deltaManager.lastKnownSeqNumber;
|
|
78
|
-
(0,
|
|
79
|
+
(0, core_utils_1.assert)(deltaManager.lastSequenceNumber <= connectionOpSeqNumber, 0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
|
|
79
80
|
if (deltaManager.lastSequenceNumber === connectionOpSeqNumber) {
|
|
80
81
|
container.off("closed", closedCallback);
|
|
81
82
|
resolve(hasCheckpointSequenceNumber);
|
|
@@ -111,7 +112,7 @@ async function waitContainerToCatchUp(container) {
|
|
|
111
112
|
exports.waitContainerToCatchUp = waitContainerToCatchUp;
|
|
112
113
|
const getCodeProposal =
|
|
113
114
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
114
|
-
(quorum) =>
|
|
115
|
+
(quorum) => quorum.get("code") ?? quorum.get("code2");
|
|
115
116
|
/**
|
|
116
117
|
* Helper function to report to telemetry cases where operation takes longer than expected (200ms)
|
|
117
118
|
* @param logger - logger to use
|
|
@@ -132,7 +133,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
132
133
|
* @internal
|
|
133
134
|
*/
|
|
134
135
|
constructor(createProps, loadProps) {
|
|
135
|
-
var _a;
|
|
136
136
|
super((name, error) => {
|
|
137
137
|
this.mc.logger.sendErrorEvent({
|
|
138
138
|
eventName: "ContainerEventHandlerException",
|
|
@@ -160,16 +160,31 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
160
160
|
this.inboundQueuePausedFromInit = true;
|
|
161
161
|
this.firstConnection = true;
|
|
162
162
|
this.connectionTransitionTimes = [];
|
|
163
|
-
this.messageCountAfterDisconnection = 0;
|
|
164
163
|
this.attachStarted = false;
|
|
165
164
|
this._dirtyContainer = false;
|
|
166
165
|
this.savedOps = [];
|
|
167
|
-
this.
|
|
166
|
+
this.clientsWhoShouldHaveLeft = new Set();
|
|
167
|
+
this.setAutoReconnectTime = client_utils_1.performance.now();
|
|
168
|
+
this._lifecycleEvents = new client_utils_1.TypedEventEmitter();
|
|
168
169
|
this._disposed = false;
|
|
170
|
+
this.getAbsoluteUrl = async (relativeUrl) => {
|
|
171
|
+
if (this.resolvedUrl === undefined) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, (0, contracts_1.getPackageName)(this._loadedCodeDetails));
|
|
175
|
+
};
|
|
176
|
+
this.updateDirtyContainerState = (dirty) => {
|
|
177
|
+
if (this._dirtyContainer === dirty) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this._dirtyContainer = dirty;
|
|
181
|
+
this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
|
|
182
|
+
};
|
|
169
183
|
const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
|
|
170
|
-
this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected] =
|
|
171
|
-
const pendingLocalState = loadProps
|
|
172
|
-
this.
|
|
184
|
+
this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected] = client_utils_1.performance.now();
|
|
185
|
+
const pendingLocalState = loadProps?.pendingLocalState;
|
|
186
|
+
this._clientId = pendingLocalState?.clientId;
|
|
187
|
+
this._canReconnect = canReconnect ?? true;
|
|
173
188
|
this.clientDetailsOverride = clientDetailsOverride;
|
|
174
189
|
this.urlResolver = urlResolver;
|
|
175
190
|
this.serviceFactory = documentServiceFactory;
|
|
@@ -177,14 +192,18 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
177
192
|
// Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
|
|
178
193
|
// all clients that were loaded from the same loader (including summarizer clients).
|
|
179
194
|
// Tracking alternative ways to handle this in AB#4129.
|
|
180
|
-
this.options =
|
|
195
|
+
this.options = { ...options };
|
|
181
196
|
this.scope = scope;
|
|
182
197
|
this.detachedBlobStorage = detachedBlobStorage;
|
|
183
198
|
this.protocolHandlerBuilder =
|
|
184
|
-
protocolHandlerBuilder
|
|
199
|
+
protocolHandlerBuilder ??
|
|
200
|
+
((attributes, quorumSnapshot, sendProposal) => new protocol_1.ProtocolHandler(attributes, quorumSnapshot, sendProposal, new audience_1.Audience(), (clientId) => this.clientsWhoShouldHaveLeft.has(clientId)));
|
|
185
201
|
// Note that we capture the createProps here so we can replicate the creation call when we want to clone.
|
|
186
202
|
this.clone = async (_loadProps, createParamOverrides) => {
|
|
187
|
-
return Container.load(_loadProps,
|
|
203
|
+
return Container.load(_loadProps, {
|
|
204
|
+
...createProps,
|
|
205
|
+
...createParamOverrides,
|
|
206
|
+
});
|
|
188
207
|
};
|
|
189
208
|
// Create logger for data stores to use
|
|
190
209
|
const type = this.client.details.type;
|
|
@@ -192,45 +211,50 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
192
211
|
const clientType = `${interactive ? "interactive" : "noninteractive"}${type !== undefined && type !== "" ? `/${type}` : ""}`;
|
|
193
212
|
// Need to use the property getter for docId because for detached flow we don't have the docId initially.
|
|
194
213
|
// We assign the id later so property getter is used.
|
|
195
|
-
this.subLogger = telemetry_utils_1.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
214
|
+
this.subLogger = (0, telemetry_utils_1.createChildLogger)({
|
|
215
|
+
logger: subLogger,
|
|
216
|
+
properties: {
|
|
217
|
+
all: {
|
|
218
|
+
clientType,
|
|
219
|
+
containerId: (0, uuid_1.v4)(),
|
|
220
|
+
docId: () => this.resolvedUrl?.id,
|
|
221
|
+
containerAttachState: () => this._attachState,
|
|
222
|
+
containerLifecycleState: () => this._lifecycleState,
|
|
223
|
+
containerConnectionState: () => connectionState_1.ConnectionState[this.connectionState],
|
|
224
|
+
serializedContainer: pendingLocalState !== undefined,
|
|
225
|
+
},
|
|
226
|
+
// we need to be judicious with our logging here to avoid generating too much data
|
|
227
|
+
// all data logged here should be broadly applicable, and not specific to a
|
|
228
|
+
// specific error or class of errors
|
|
229
|
+
error: {
|
|
230
|
+
// load information to associate errors with the specific load point
|
|
231
|
+
dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
|
|
232
|
+
dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
|
|
233
|
+
dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
|
|
234
|
+
containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
|
|
235
|
+
containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
|
|
236
|
+
// message information to associate errors with the specific execution state
|
|
237
|
+
// dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
|
|
238
|
+
dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
|
|
239
|
+
dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
|
|
240
|
+
dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId === null
|
|
241
|
+
? "null"
|
|
242
|
+
: this.deltaManager?.lastMessage?.clientId,
|
|
243
|
+
dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
|
|
244
|
+
connectionStateDuration: () => client_utils_1.performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
245
|
+
},
|
|
222
246
|
},
|
|
223
247
|
});
|
|
224
248
|
// Prefix all events in this file with container-loader
|
|
225
|
-
this.mc = (0, telemetry_utils_1.
|
|
249
|
+
this.mc = (0, telemetry_utils_1.createChildMonitoringContext)({ logger: this.subLogger, namespace: "Container" });
|
|
226
250
|
this._deltaManager = this.createDeltaManager();
|
|
227
251
|
this.connectionStateHandler = (0, connectionStateHandler_1.createConnectionStateHandler)({
|
|
228
252
|
logger: this.mc.logger,
|
|
229
|
-
connectionStateChanged: (value, oldState, reason
|
|
253
|
+
connectionStateChanged: (value, oldState, reason) => {
|
|
230
254
|
if (value === connectionState_1.ConnectionState.Connected) {
|
|
231
255
|
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
232
256
|
}
|
|
233
|
-
this.logConnectionStateChangeTelemetry(value, oldState, reason
|
|
257
|
+
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
234
258
|
if (this._lifecycleState === "loaded") {
|
|
235
259
|
this.propagateConnectionState(false /* initial transition */, value === connectionState_1.ConnectionState.Disconnected
|
|
236
260
|
? reason
|
|
@@ -246,9 +270,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
246
270
|
// Report issues only if we already loaded container - op processing is paused while container is loading,
|
|
247
271
|
// so we always time-out processing of join op in cases where fetching snapshot takes a minute.
|
|
248
272
|
// It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
|
|
249
|
-
this._deltaManager.logConnectionIssue(
|
|
250
|
-
|
|
251
|
-
|
|
273
|
+
this._deltaManager.logConnectionIssue({
|
|
274
|
+
eventName,
|
|
275
|
+
mode,
|
|
276
|
+
category: this._lifecycleState === "loading" ? "generic" : category,
|
|
277
|
+
duration: client_utils_1.performance.now() -
|
|
278
|
+
this.connectionTransitionTimes[connectionState_1.ConnectionState.CatchingUp],
|
|
279
|
+
...(details === undefined ? {} : { details: JSON.stringify(details) }),
|
|
280
|
+
});
|
|
252
281
|
// If this is "write" connection, it took too long to receive join op. But in most cases that's due
|
|
253
282
|
// to very slow op fetches and we will eventually get there.
|
|
254
283
|
// For "read" connections, we get here due to self join signal not arriving on time. We will need to
|
|
@@ -258,11 +287,15 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
258
287
|
// Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
|
|
259
288
|
// to call this.applyForConnectedState("addMemberEvent") for "read" connections)
|
|
260
289
|
if (mode === "read") {
|
|
261
|
-
|
|
262
|
-
this.
|
|
290
|
+
const reason = { text: "NoJoinSignal" };
|
|
291
|
+
this.disconnectInternal(reason);
|
|
292
|
+
this.connectInternal({ reason, fetchOpsFromStorage: false });
|
|
263
293
|
}
|
|
264
294
|
},
|
|
265
|
-
|
|
295
|
+
clientShouldHaveLeft: (clientId) => {
|
|
296
|
+
this.clientsWhoShouldHaveLeft.add(clientId);
|
|
297
|
+
},
|
|
298
|
+
}, this.deltaManager, pendingLocalState?.clientId);
|
|
266
299
|
this.on(savedContainerEvent, () => {
|
|
267
300
|
this.connectionStateHandler.containerSaved();
|
|
268
301
|
});
|
|
@@ -271,21 +304,22 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
271
304
|
// using this callback and fix them up.
|
|
272
305
|
const addProtocolSummaryIfMissing = (summaryTree) => (0, driver_utils_1.isCombinedAppAndProtocolSummary)(summaryTree) === true
|
|
273
306
|
? summaryTree
|
|
274
|
-
: (0,
|
|
307
|
+
: (0, utils_1.combineAppAndProtocolSummary)(summaryTree, this.captureProtocolSummary());
|
|
275
308
|
// Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
|
|
276
309
|
// Even if not forced on via this flag, combined summaries may still be enabled by service policy.
|
|
277
|
-
const forceEnableSummarizeProtocolTree =
|
|
278
|
-
|
|
310
|
+
const forceEnableSummarizeProtocolTree = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
|
|
311
|
+
options.summarizeProtocolTree;
|
|
312
|
+
this.storageAdapter = new containerStorageAdapter_1.ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
|
|
279
313
|
const isDomAvailable = typeof document === "object" &&
|
|
280
314
|
document !== null &&
|
|
281
315
|
typeof document.addEventListener === "function" &&
|
|
282
316
|
document.addEventListener !== null;
|
|
283
|
-
// keep track of last time page was visible for telemetry
|
|
284
|
-
if (isDomAvailable) {
|
|
285
|
-
this.lastVisible = document.hidden ?
|
|
317
|
+
// keep track of last time page was visible for telemetry (on interactive clients only)
|
|
318
|
+
if (isDomAvailable && interactive) {
|
|
319
|
+
this.lastVisible = document.hidden ? client_utils_1.performance.now() : undefined;
|
|
286
320
|
this.visibilityEventHandler = () => {
|
|
287
321
|
if (document.hidden) {
|
|
288
|
-
this.lastVisible =
|
|
322
|
+
this.lastVisible = client_utils_1.performance.now();
|
|
289
323
|
}
|
|
290
324
|
else {
|
|
291
325
|
// settimeout so this will hopefully fire after disconnect event if being hidden caused it
|
|
@@ -302,7 +336,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
302
336
|
* @internal
|
|
303
337
|
*/
|
|
304
338
|
static async load(loadProps, createProps) {
|
|
305
|
-
const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
|
|
339
|
+
const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } = loadProps;
|
|
306
340
|
const container = new Container(createProps, loadProps);
|
|
307
341
|
const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
|
|
308
342
|
return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
|
|
@@ -310,26 +344,31 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
310
344
|
// if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
|
|
311
345
|
// to return container, so ignore this value and use undefined for opsBeforeReturn
|
|
312
346
|
const mode = pendingLocalState
|
|
313
|
-
?
|
|
347
|
+
? { ...(loadMode ?? defaultMode), opsBeforeReturn: undefined }
|
|
348
|
+
: loadMode ?? defaultMode;
|
|
314
349
|
const onClosed = (err) => {
|
|
315
350
|
// pre-0.58 error message: containerClosedWithoutErrorDuringLoad
|
|
316
|
-
reject(err
|
|
351
|
+
reject(err ?? new telemetry_utils_1.GenericError("Container closed without error during load"));
|
|
317
352
|
};
|
|
318
353
|
container.on("closed", onClosed);
|
|
319
354
|
container
|
|
320
|
-
.load(version, mode, resolvedUrl, pendingLocalState)
|
|
355
|
+
.load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
|
|
321
356
|
.finally(() => {
|
|
322
357
|
container.removeListener("closed", onClosed);
|
|
323
358
|
})
|
|
324
359
|
.then((props) => {
|
|
325
|
-
event.end(
|
|
360
|
+
event.end({ ...props, ...loadMode });
|
|
326
361
|
resolve(container);
|
|
327
362
|
}, (error) => {
|
|
328
363
|
const err = (0, telemetry_utils_1.normalizeError)(error);
|
|
329
364
|
// Depending where error happens, we can be attempting to connect to web socket
|
|
330
365
|
// and continuously retrying (consider offline mode)
|
|
331
366
|
// Host has no container to close, so it's prudent to do it here
|
|
367
|
+
// Note: We could only dispose the container instead of just close but that would
|
|
368
|
+
// the telemetry where users sometimes search for ContainerClose event to look
|
|
369
|
+
// for load failures. So not removing this at this time.
|
|
332
370
|
container.close(err);
|
|
371
|
+
container.dispose(err);
|
|
333
372
|
onClosed(err);
|
|
334
373
|
});
|
|
335
374
|
}), { start: true, end: true, cancel: "generic" }, disableRecordHeapSize !== true /* recordHeapSize */);
|
|
@@ -366,19 +405,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
366
405
|
}
|
|
367
406
|
}
|
|
368
407
|
get closed() {
|
|
369
|
-
return (this._lifecycleState === "closing" ||
|
|
370
|
-
this._lifecycleState === "closed" ||
|
|
371
|
-
this._lifecycleState === "disposing" ||
|
|
372
|
-
this._lifecycleState === "disposed");
|
|
408
|
+
return (this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed);
|
|
373
409
|
}
|
|
374
|
-
get
|
|
375
|
-
return this.
|
|
410
|
+
get disposed() {
|
|
411
|
+
return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
|
|
376
412
|
}
|
|
377
|
-
get
|
|
378
|
-
if (this.
|
|
379
|
-
throw new
|
|
413
|
+
get runtime() {
|
|
414
|
+
if (this._runtime === undefined) {
|
|
415
|
+
throw new Error("Attempted to access runtime before it was defined");
|
|
380
416
|
}
|
|
381
|
-
return this.
|
|
417
|
+
return this._runtime;
|
|
382
418
|
}
|
|
383
419
|
get protocolHandler() {
|
|
384
420
|
if (this._protocolHandler === undefined) {
|
|
@@ -393,7 +429,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
393
429
|
return this;
|
|
394
430
|
}
|
|
395
431
|
get resolvedUrl() {
|
|
396
|
-
var _a;
|
|
397
432
|
/**
|
|
398
433
|
* All attached containers will have a document service,
|
|
399
434
|
* this is required, as attached containers are attached to
|
|
@@ -405,17 +440,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
405
440
|
* is always the same as the containers, as we had to
|
|
406
441
|
* obtain the resolved url, and then create the service from it.
|
|
407
442
|
*/
|
|
408
|
-
return
|
|
409
|
-
}
|
|
410
|
-
get loadedFromVersion() {
|
|
411
|
-
return this._loadedFromVersion;
|
|
443
|
+
return this.service?.resolvedUrl;
|
|
412
444
|
}
|
|
413
445
|
get readOnlyInfo() {
|
|
414
446
|
return this._deltaManager.readOnlyInfo;
|
|
415
447
|
}
|
|
416
|
-
get closeSignal() {
|
|
417
|
-
return this._deltaManager.closeAbortController.signal;
|
|
418
|
-
}
|
|
419
448
|
/**
|
|
420
449
|
* Tracks host requiring read-only mode.
|
|
421
450
|
*/
|
|
@@ -431,13 +460,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
431
460
|
get connected() {
|
|
432
461
|
return this.connectionStateHandler.connectionState === connectionState_1.ConnectionState.Connected;
|
|
433
462
|
}
|
|
434
|
-
/**
|
|
435
|
-
* Service configuration details. If running in offline mode will be undefined otherwise will contain service
|
|
436
|
-
* configuration details returned as part of the initial connection.
|
|
437
|
-
*/
|
|
438
|
-
get serviceConfiguration() {
|
|
439
|
-
return this._deltaManager.serviceConfiguration;
|
|
440
|
-
}
|
|
441
463
|
/**
|
|
442
464
|
* The server provided id of the client.
|
|
443
465
|
* Set once this.connected is true, otherwise undefined
|
|
@@ -445,21 +467,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
445
467
|
get clientId() {
|
|
446
468
|
return this._clientId;
|
|
447
469
|
}
|
|
448
|
-
/**
|
|
449
|
-
* The server provided claims of the client.
|
|
450
|
-
* Set once this.connected is true, otherwise undefined
|
|
451
|
-
*/
|
|
452
|
-
get scopes() {
|
|
453
|
-
return this._deltaManager.connectionManager.scopes;
|
|
454
|
-
}
|
|
455
|
-
get clientDetails() {
|
|
456
|
-
return this._deltaManager.clientDetails;
|
|
457
|
-
}
|
|
458
470
|
get offlineLoadEnabled() {
|
|
459
|
-
|
|
460
|
-
|
|
471
|
+
const enabled = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ??
|
|
472
|
+
this.options?.enableOfflineLoad === true;
|
|
461
473
|
// summarizer will not have any pending state we want to save
|
|
462
|
-
return enabled && this.clientDetails.capabilities.interactive;
|
|
474
|
+
return enabled && this.deltaManager.clientDetails.capabilities.interactive;
|
|
463
475
|
}
|
|
464
476
|
/**
|
|
465
477
|
* Get the code details that are currently specified for the container.
|
|
@@ -474,8 +486,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
474
486
|
* loaded.
|
|
475
487
|
*/
|
|
476
488
|
getLoadedCodeDetails() {
|
|
477
|
-
|
|
478
|
-
return (_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails;
|
|
489
|
+
return this._loadedCodeDetails;
|
|
479
490
|
}
|
|
480
491
|
/**
|
|
481
492
|
* Retrieves the audience associated with the document
|
|
@@ -495,33 +506,25 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
495
506
|
* {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
|
|
496
507
|
*/
|
|
497
508
|
async getEntryPoint() {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
// allow it since they mean a kind of read-only state for the Container.
|
|
501
|
-
// Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
|
|
502
|
-
if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
|
|
503
|
-
throw new container_utils_1.UsageError("The container is disposing or disposed");
|
|
509
|
+
if (this._disposed) {
|
|
510
|
+
throw new telemetry_utils_1.UsageError("The context is already disposed");
|
|
504
511
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const contextChangedHandler = () => {
|
|
508
|
-
resolve();
|
|
509
|
-
this.off("disposed", disposedHandler);
|
|
510
|
-
};
|
|
511
|
-
const disposedHandler = (error) => {
|
|
512
|
-
reject(error !== null && error !== void 0 ? error : "The Container is disposed");
|
|
513
|
-
this.off("contextChanged", contextChangedHandler);
|
|
514
|
-
};
|
|
515
|
-
this.once("contextChanged", contextChangedHandler);
|
|
516
|
-
this.once("disposed", disposedHandler);
|
|
517
|
-
});
|
|
518
|
-
// The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
|
|
519
|
-
// should have set this._context; making sure.
|
|
520
|
-
(0, common_utils_1.assert)(this._context !== undefined, 0x5a2 /* Context still not defined after contextChanged event */);
|
|
512
|
+
if (this._runtime !== undefined) {
|
|
513
|
+
return this._runtime.getEntryPoint?.();
|
|
521
514
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
515
|
+
return new Promise((resolve, reject) => {
|
|
516
|
+
const runtimeInstantiatedHandler = () => {
|
|
517
|
+
(0, core_utils_1.assert)(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
|
|
518
|
+
resolve(this._runtime.getEntryPoint?.());
|
|
519
|
+
this._lifecycleEvents.off("disposed", disposedHandler);
|
|
520
|
+
};
|
|
521
|
+
const disposedHandler = () => {
|
|
522
|
+
reject(new Error("ContainerContext was disposed"));
|
|
523
|
+
this._lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
524
|
+
};
|
|
525
|
+
this._lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
|
|
526
|
+
this._lifecycleEvents.once("disposed", disposedHandler);
|
|
527
|
+
});
|
|
525
528
|
}
|
|
526
529
|
/**
|
|
527
530
|
* Retrieves the quorum associated with the document
|
|
@@ -542,12 +545,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
542
545
|
this.verifyClosed();
|
|
543
546
|
}
|
|
544
547
|
verifyClosed() {
|
|
545
|
-
(0,
|
|
546
|
-
(0,
|
|
548
|
+
(0, core_utils_1.assert)(this.connectionState === connectionState_1.ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
|
|
549
|
+
(0, core_utils_1.assert)(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
|
|
547
550
|
}
|
|
548
551
|
closeCore(error) {
|
|
549
|
-
|
|
550
|
-
(0, common_utils_1.assert)(!this.closed, 0x315 /* re-entrancy */);
|
|
552
|
+
(0, core_utils_1.assert)(!this.closed, 0x315 /* re-entrancy */);
|
|
551
553
|
try {
|
|
552
554
|
// Ensure that we raise all key events even if one of these throws
|
|
553
555
|
try {
|
|
@@ -562,7 +564,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
562
564
|
: "generic",
|
|
563
565
|
}, error);
|
|
564
566
|
this._lifecycleState = "closing";
|
|
565
|
-
|
|
567
|
+
this._protocolHandler?.close();
|
|
566
568
|
this.connectionStateHandler.dispose();
|
|
567
569
|
}
|
|
568
570
|
catch (exception) {
|
|
@@ -575,11 +577,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
575
577
|
}
|
|
576
578
|
finally {
|
|
577
579
|
this._lifecycleState = "closed";
|
|
580
|
+
// There is no user for summarizer, so we need to ensure dispose is called
|
|
581
|
+
if (this.client.details.type === summarizerClientType) {
|
|
582
|
+
this.dispose(error);
|
|
583
|
+
}
|
|
578
584
|
}
|
|
579
585
|
}
|
|
580
586
|
disposeCore(error) {
|
|
581
|
-
|
|
582
|
-
(0, common_utils_1.assert)(!this._disposed, 0x54c /* Container already disposed */);
|
|
587
|
+
(0, core_utils_1.assert)(!this._disposed, 0x54c /* Container already disposed */);
|
|
583
588
|
this._disposed = true;
|
|
584
589
|
try {
|
|
585
590
|
// Ensure that we raise all key events even if one of these throws
|
|
@@ -588,20 +593,22 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
588
593
|
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
589
594
|
this.mc.logger.sendTelemetryEvent({
|
|
590
595
|
eventName: "ContainerDispose",
|
|
591
|
-
|
|
596
|
+
// Only log error if container isn't closed
|
|
597
|
+
category: !this.closed && error !== undefined ? "error" : "generic",
|
|
592
598
|
}, error);
|
|
593
599
|
// ! Progressing from "closed" to "disposing" is not allowed
|
|
594
600
|
if (this._lifecycleState !== "closed") {
|
|
595
601
|
this._lifecycleState = "disposing";
|
|
596
602
|
}
|
|
597
|
-
|
|
603
|
+
this._protocolHandler?.close();
|
|
598
604
|
this.connectionStateHandler.dispose();
|
|
599
|
-
|
|
605
|
+
const maybeError = error !== undefined ? new Error(error.message) : undefined;
|
|
606
|
+
this._runtime?.dispose(maybeError);
|
|
600
607
|
this.storageAdapter.dispose();
|
|
601
608
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
602
609
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
603
610
|
// Driver need to ensure all caches are cleared on critical errors
|
|
604
|
-
|
|
611
|
+
this.service?.dispose(error);
|
|
605
612
|
}
|
|
606
613
|
catch (exception) {
|
|
607
614
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerDisposeException" }, exception);
|
|
@@ -614,44 +621,60 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
614
621
|
}
|
|
615
622
|
finally {
|
|
616
623
|
this._lifecycleState = "disposed";
|
|
624
|
+
this._lifecycleEvents.emit("disposed");
|
|
617
625
|
}
|
|
618
626
|
}
|
|
619
|
-
closeAndGetPendingLocalState() {
|
|
627
|
+
async closeAndGetPendingLocalState() {
|
|
620
628
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
621
629
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
622
630
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
623
|
-
|
|
631
|
+
this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
|
|
632
|
+
const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
|
|
624
633
|
this.close();
|
|
625
634
|
return pendingState;
|
|
626
635
|
}
|
|
627
|
-
getPendingLocalState() {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const pendingState = {
|
|
636
|
-
pendingRuntimeState: this.context.getPendingLocalState(),
|
|
637
|
-
baseSnapshot: this.baseSnapshot,
|
|
638
|
-
snapshotBlobs: this.baseSnapshotBlobs,
|
|
639
|
-
savedOps: this.savedOps,
|
|
640
|
-
url: this.resolvedUrl.url,
|
|
641
|
-
term: protocol_1.OnlyValidTermValue,
|
|
636
|
+
async getPendingLocalState() {
|
|
637
|
+
return this.getPendingLocalStateCore({ notifyImminentClosure: false });
|
|
638
|
+
}
|
|
639
|
+
async getPendingLocalStateCore(props) {
|
|
640
|
+
return telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, {
|
|
641
|
+
eventName: "getPendingLocalState",
|
|
642
|
+
notifyImminentClosure: props.notifyImminentClosure,
|
|
643
|
+
savedOpsSize: this.savedOps.length,
|
|
642
644
|
clientId: this.clientId,
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
|
|
645
|
+
}, async () => {
|
|
646
|
+
if (!this.offlineLoadEnabled) {
|
|
647
|
+
throw new telemetry_utils_1.UsageError("Can't get pending local state unless offline load is enabled");
|
|
648
|
+
}
|
|
649
|
+
if (this.closed || this._disposed) {
|
|
650
|
+
throw new telemetry_utils_1.UsageError("Pending state cannot be retried if the container is closed or disposed");
|
|
651
|
+
}
|
|
652
|
+
(0, core_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
|
|
653
|
+
(0, core_utils_1.assert)(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
654
|
+
(0, core_utils_1.assert)(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
|
|
655
|
+
(0, core_utils_1.assert)(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
|
|
656
|
+
const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
|
|
657
|
+
const pendingState = {
|
|
658
|
+
pendingRuntimeState,
|
|
659
|
+
baseSnapshot: this.baseSnapshot,
|
|
660
|
+
snapshotBlobs: this.baseSnapshotBlobs,
|
|
661
|
+
savedOps: this.savedOps,
|
|
662
|
+
url: this.resolvedUrl.url,
|
|
663
|
+
term: protocol_1.OnlyValidTermValue,
|
|
664
|
+
// no need to save this if there is no pending runtime state
|
|
665
|
+
clientId: pendingRuntimeState !== undefined ? this.clientId : undefined,
|
|
666
|
+
};
|
|
667
|
+
return JSON.stringify(pendingState);
|
|
668
|
+
});
|
|
646
669
|
}
|
|
647
670
|
get attachState() {
|
|
648
671
|
return this._attachState;
|
|
649
672
|
}
|
|
650
673
|
serialize() {
|
|
651
|
-
(0,
|
|
652
|
-
const appSummary = this.
|
|
674
|
+
(0, core_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Detached, 0x0d3 /* "Should only be called in detached container" */);
|
|
675
|
+
const appSummary = this.runtime.createSummary();
|
|
653
676
|
const protocolSummary = this.captureProtocolSummary();
|
|
654
|
-
const combinedSummary = (0,
|
|
677
|
+
const combinedSummary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
|
|
655
678
|
if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
|
|
656
679
|
combinedSummary.tree[".hasAttachmentBlobs"] = {
|
|
657
680
|
type: protocol_definitions_1.SummaryType.Blob,
|
|
@@ -660,32 +683,32 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
660
683
|
}
|
|
661
684
|
return JSON.stringify(combinedSummary);
|
|
662
685
|
}
|
|
663
|
-
async attach(request) {
|
|
686
|
+
async attach(request, attachProps) {
|
|
664
687
|
await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
|
|
665
|
-
var _a;
|
|
666
688
|
if (this._lifecycleState !== "loaded") {
|
|
667
689
|
// pre-0.58 error message: containerNotValidForAttach
|
|
668
|
-
throw new
|
|
690
|
+
throw new telemetry_utils_1.UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
|
|
669
691
|
}
|
|
670
692
|
// If container is already attached or attach is in progress, throw an error.
|
|
671
|
-
(0,
|
|
693
|
+
(0, core_utils_1.assert)(this._attachState === container_definitions_1.AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
|
|
672
694
|
this.attachStarted = true;
|
|
673
695
|
// If attachment blobs were uploaded in detached state we will go through a different attach flow
|
|
674
696
|
const hasAttachmentBlobs = this.detachedBlobStorage !== undefined && this.detachedBlobStorage.size > 0;
|
|
675
697
|
try {
|
|
676
|
-
(0,
|
|
698
|
+
(0, core_utils_1.assert)(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
|
|
677
699
|
let summary;
|
|
678
700
|
if (!hasAttachmentBlobs) {
|
|
679
701
|
// Get the document state post attach - possibly can just call attach but we need to change the
|
|
680
702
|
// semantics around what the attach means as far as async code goes.
|
|
681
|
-
const appSummary = this.
|
|
703
|
+
const appSummary = this.runtime.createSummary();
|
|
682
704
|
const protocolSummary = this.captureProtocolSummary();
|
|
683
|
-
summary = (0,
|
|
705
|
+
summary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
|
|
684
706
|
// Set the state as attaching as we are starting the process of attaching container.
|
|
685
707
|
// This should be fired after taking the summary because it is the place where we are
|
|
686
708
|
// starting to attach the container to storage.
|
|
687
709
|
// Also, this should only be fired in detached container.
|
|
688
710
|
this._attachState = container_definitions_1.AttachState.Attaching;
|
|
711
|
+
this.runtime.setAttachState(container_definitions_1.AttachState.Attaching);
|
|
689
712
|
this.emit("attaching");
|
|
690
713
|
if (this.offlineLoadEnabled) {
|
|
691
714
|
const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
|
|
@@ -697,16 +720,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
697
720
|
// Actually go and create the resolved document
|
|
698
721
|
if (this.service === undefined) {
|
|
699
722
|
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
700
|
-
(0,
|
|
723
|
+
(0, core_utils_1.assert)(this.client.details.type !== summarizerClientType &&
|
|
701
724
|
createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
|
|
702
725
|
this.service = await (0, driver_utils_1.runWithRetry)(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
|
|
703
|
-
cancel: this.
|
|
726
|
+
cancel: this._deltaManager.closeAbortController.signal,
|
|
704
727
|
});
|
|
705
728
|
}
|
|
706
729
|
await this.storageAdapter.connectToService(this.service);
|
|
707
730
|
if (hasAttachmentBlobs) {
|
|
708
731
|
// upload blobs to storage
|
|
709
|
-
(0,
|
|
732
|
+
(0, core_utils_1.assert)(!!this.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
|
|
710
733
|
// build a table mapping IDs assigned locally to IDs assigned by storage and pass it to runtime to
|
|
711
734
|
// support blob handles that only know about the local IDs
|
|
712
735
|
const redirectTable = new Map();
|
|
@@ -722,10 +745,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
722
745
|
}
|
|
723
746
|
}
|
|
724
747
|
// take summary and upload
|
|
725
|
-
const appSummary = this.
|
|
748
|
+
const appSummary = this.runtime.createSummary(redirectTable);
|
|
726
749
|
const protocolSummary = this.captureProtocolSummary();
|
|
727
|
-
summary = (0,
|
|
750
|
+
summary = (0, utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
|
|
728
751
|
this._attachState = container_definitions_1.AttachState.Attaching;
|
|
752
|
+
this.runtime.setAttachState(container_definitions_1.AttachState.Attaching);
|
|
729
753
|
this.emit("attaching");
|
|
730
754
|
if (this.offlineLoadEnabled) {
|
|
731
755
|
const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
|
|
@@ -740,32 +764,33 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
740
764
|
});
|
|
741
765
|
}
|
|
742
766
|
this._attachState = container_definitions_1.AttachState.Attached;
|
|
767
|
+
this.runtime.setAttachState(container_definitions_1.AttachState.Attached);
|
|
743
768
|
this.emit("attached");
|
|
744
769
|
if (!this.closed) {
|
|
745
|
-
this.
|
|
770
|
+
this.handleDeltaConnectionArg({
|
|
746
771
|
fetchOpsFromStorage: false,
|
|
747
|
-
reason: "createDetached",
|
|
748
|
-
});
|
|
772
|
+
reason: { text: "createDetached" },
|
|
773
|
+
}, attachProps?.deltaConnection);
|
|
749
774
|
}
|
|
750
775
|
}
|
|
751
776
|
catch (error) {
|
|
752
777
|
// add resolved URL on error object so that host has the ability to find this document and delete it
|
|
753
778
|
const newError = (0, telemetry_utils_1.normalizeError)(error);
|
|
754
|
-
newError.addTelemetryProperties({ resolvedUrl:
|
|
779
|
+
newError.addTelemetryProperties({ resolvedUrl: this.resolvedUrl?.url });
|
|
755
780
|
this.close(newError);
|
|
756
781
|
throw newError;
|
|
757
782
|
}
|
|
758
783
|
}, { start: true, end: true, cancel: "generic" });
|
|
759
784
|
}
|
|
760
785
|
async request(path) {
|
|
761
|
-
return telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.
|
|
786
|
+
return telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
|
|
762
787
|
}
|
|
763
|
-
setAutoReconnectInternal(mode) {
|
|
788
|
+
setAutoReconnectInternal(mode, reason) {
|
|
764
789
|
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
765
790
|
if (currentMode === mode) {
|
|
766
791
|
return;
|
|
767
792
|
}
|
|
768
|
-
const now =
|
|
793
|
+
const now = client_utils_1.performance.now();
|
|
769
794
|
const duration = now - this.setAutoReconnectTime;
|
|
770
795
|
this.setAutoReconnectTime = now;
|
|
771
796
|
this.mc.logger.sendTelemetryEvent({
|
|
@@ -774,47 +799,50 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
774
799
|
connectionState: connectionState_1.ConnectionState[this.connectionState],
|
|
775
800
|
duration,
|
|
776
801
|
});
|
|
777
|
-
this._deltaManager.connectionManager.setAutoReconnect(mode);
|
|
802
|
+
this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
|
|
778
803
|
}
|
|
779
804
|
connect() {
|
|
780
805
|
if (this.closed) {
|
|
781
|
-
throw new
|
|
806
|
+
throw new telemetry_utils_1.UsageError(`The Container is closed and cannot be connected`);
|
|
782
807
|
}
|
|
783
808
|
else if (this._attachState !== container_definitions_1.AttachState.Attached) {
|
|
784
|
-
throw new
|
|
809
|
+
throw new telemetry_utils_1.UsageError(`The Container is not attached and cannot be connected`);
|
|
785
810
|
}
|
|
786
811
|
else if (!this.connected) {
|
|
787
812
|
// Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
|
|
788
813
|
// If there is gap, we will learn about it once connected, but the gap should be small (if any),
|
|
789
814
|
// assuming that connect() is called quickly after initial container boot.
|
|
790
|
-
this.connectInternal({
|
|
815
|
+
this.connectInternal({
|
|
816
|
+
reason: { text: "DocumentConnect" },
|
|
817
|
+
fetchOpsFromStorage: false,
|
|
818
|
+
});
|
|
791
819
|
}
|
|
792
820
|
}
|
|
793
821
|
connectInternal(args) {
|
|
794
|
-
(0,
|
|
795
|
-
(0,
|
|
822
|
+
(0, core_utils_1.assert)(!this.closed, 0x2c5 /* "Attempting to connect() a closed Container" */);
|
|
823
|
+
(0, core_utils_1.assert)(this._attachState === container_definitions_1.AttachState.Attached, 0x2c6 /* "Attempting to connect() a container that is not attached" */);
|
|
796
824
|
// Resume processing ops and connect to delta stream
|
|
797
825
|
this.resumeInternal(args);
|
|
798
826
|
// Set Auto Reconnect Mode
|
|
799
827
|
const mode = contracts_1.ReconnectMode.Enabled;
|
|
800
|
-
this.setAutoReconnectInternal(mode);
|
|
828
|
+
this.setAutoReconnectInternal(mode, args.reason);
|
|
801
829
|
}
|
|
802
830
|
disconnect() {
|
|
803
831
|
if (this.closed) {
|
|
804
|
-
throw new
|
|
832
|
+
throw new telemetry_utils_1.UsageError(`The Container is closed and cannot be disconnected`);
|
|
805
833
|
}
|
|
806
834
|
else {
|
|
807
|
-
this.disconnectInternal();
|
|
835
|
+
this.disconnectInternal({ text: "DocumentDisconnect" });
|
|
808
836
|
}
|
|
809
837
|
}
|
|
810
|
-
disconnectInternal() {
|
|
811
|
-
(0,
|
|
838
|
+
disconnectInternal(reason) {
|
|
839
|
+
(0, core_utils_1.assert)(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
|
|
812
840
|
// Set Auto Reconnect Mode
|
|
813
841
|
const mode = contracts_1.ReconnectMode.Disabled;
|
|
814
|
-
this.setAutoReconnectInternal(mode);
|
|
842
|
+
this.setAutoReconnectInternal(mode, reason);
|
|
815
843
|
}
|
|
816
844
|
resumeInternal(args) {
|
|
817
|
-
(0,
|
|
845
|
+
(0, core_utils_1.assert)(!this.closed, 0x0d9 /* "Attempting to connect() a closed DeltaManager" */);
|
|
818
846
|
// Resume processing ops
|
|
819
847
|
if (this.inboundQueuePausedFromInit) {
|
|
820
848
|
this.inboundQueuePausedFromInit = false;
|
|
@@ -824,13 +852,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
824
852
|
// Ensure connection to web socket
|
|
825
853
|
this.connectToDeltaStream(args);
|
|
826
854
|
}
|
|
827
|
-
async getAbsoluteUrl(relativeUrl) {
|
|
828
|
-
var _a;
|
|
829
|
-
if (this.resolvedUrl === undefined) {
|
|
830
|
-
return undefined;
|
|
831
|
-
}
|
|
832
|
-
return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, (0, contracts_1.getPackageName)((_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails));
|
|
833
|
-
}
|
|
834
855
|
async proposeCodeDetails(codeDetails) {
|
|
835
856
|
if (!(0, container_definitions_1.isFluidCodeDetails)(codeDetails)) {
|
|
836
857
|
throw new Error("Provided codeDetails are not IFluidCodeDetails");
|
|
@@ -852,15 +873,46 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
852
873
|
this.deltaManager.inbound.pause(),
|
|
853
874
|
this.deltaManager.inboundSignal.pause(),
|
|
854
875
|
]);
|
|
855
|
-
if ((await this.
|
|
876
|
+
if ((await this.satisfies(codeDetails)) === true) {
|
|
856
877
|
this.deltaManager.inbound.resume();
|
|
857
878
|
this.deltaManager.inboundSignal.resume();
|
|
858
879
|
return;
|
|
859
880
|
}
|
|
860
881
|
// pre-0.58 error message: existingContextDoesNotSatisfyIncomingProposal
|
|
861
|
-
const error = new
|
|
882
|
+
const error = new telemetry_utils_1.GenericError("Existing context does not satisfy incoming proposal");
|
|
862
883
|
this.close(error);
|
|
863
884
|
}
|
|
885
|
+
/**
|
|
886
|
+
* Determines if the currently loaded module satisfies the incoming constraint code details
|
|
887
|
+
*/
|
|
888
|
+
async satisfies(constraintCodeDetails) {
|
|
889
|
+
// If we have no module, it can't satisfy anything.
|
|
890
|
+
if (this._loadedModule === undefined) {
|
|
891
|
+
return false;
|
|
892
|
+
}
|
|
893
|
+
const comparers = [];
|
|
894
|
+
const maybeCompareCodeLoader = this.codeLoader;
|
|
895
|
+
if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
|
|
896
|
+
comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
|
|
897
|
+
}
|
|
898
|
+
const maybeCompareExport = this._loadedModule?.module.fluidExport;
|
|
899
|
+
if (maybeCompareExport?.IFluidCodeDetailsComparer !== undefined) {
|
|
900
|
+
comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
|
|
901
|
+
}
|
|
902
|
+
// If there are no comparers, then it's impossible to know if the currently loaded package satisfies
|
|
903
|
+
// the incoming constraint, so we return false. Assuming it does not satisfy is safer, to force a reload
|
|
904
|
+
// rather than potentially running with incompatible code.
|
|
905
|
+
if (comparers.length === 0) {
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
for (const comparer of comparers) {
|
|
909
|
+
const satisfies = await comparer.satisfies(this._loadedModule?.details, constraintCodeDetails);
|
|
910
|
+
if (satisfies === false) {
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return true;
|
|
915
|
+
}
|
|
864
916
|
async getVersion(version) {
|
|
865
917
|
const versions = await this.storageAdapter.getVersions(version, 1);
|
|
866
918
|
return versions[0];
|
|
@@ -877,8 +929,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
877
929
|
*
|
|
878
930
|
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
879
931
|
*/
|
|
880
|
-
async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
|
|
881
|
-
|
|
932
|
+
async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState, loadToSequenceNumber) {
|
|
933
|
+
const timings = { phase1: client_utils_1.performance.now() };
|
|
882
934
|
this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
|
|
883
935
|
// Ideally we always connect as "read" by default.
|
|
884
936
|
// Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
|
|
@@ -890,7 +942,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
890
942
|
// A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
|
|
891
943
|
// B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
|
|
892
944
|
const connectionArgs = {
|
|
893
|
-
reason: "DocumentOpen",
|
|
945
|
+
reason: { text: "DocumentOpen" },
|
|
894
946
|
mode: "write",
|
|
895
947
|
fetchOpsFromStorage: false,
|
|
896
948
|
};
|
|
@@ -909,6 +961,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
909
961
|
});
|
|
910
962
|
}
|
|
911
963
|
this._attachState = container_definitions_1.AttachState.Attached;
|
|
964
|
+
timings.phase2 = client_utils_1.performance.now();
|
|
912
965
|
// Fetch specified snapshot.
|
|
913
966
|
const { snapshot, versionId } = pendingLocalState === undefined
|
|
914
967
|
? await this.fetchSnapshotTree(specifiedVersion)
|
|
@@ -918,18 +971,60 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
918
971
|
this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
|
|
919
972
|
}
|
|
920
973
|
else {
|
|
921
|
-
(0,
|
|
974
|
+
(0, core_utils_1.assert)(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
|
|
922
975
|
if (this.offlineLoadEnabled) {
|
|
923
976
|
this.baseSnapshot = snapshot;
|
|
924
977
|
// Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
|
|
925
|
-
this.baseSnapshotBlobs = await (0, containerStorageAdapter_1.getBlobContentsFromTree)(snapshot, this.
|
|
978
|
+
this.baseSnapshotBlobs = await (0, containerStorageAdapter_1.getBlobContentsFromTree)(snapshot, this.storageAdapter);
|
|
926
979
|
}
|
|
927
980
|
}
|
|
928
981
|
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
|
|
929
982
|
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
930
|
-
const sequenceNumber =
|
|
931
|
-
const dmAttributes = sequenceNumber !== undefined ?
|
|
983
|
+
const sequenceNumber = pendingLocalState?.savedOps[pendingLocalState.savedOps.length - 1]?.sequenceNumber;
|
|
984
|
+
const dmAttributes = sequenceNumber !== undefined ? { ...attributes, sequenceNumber } : attributes;
|
|
932
985
|
let opsBeforeReturnP;
|
|
986
|
+
if (loadMode.pauseAfterLoad === true) {
|
|
987
|
+
// If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
|
|
988
|
+
if (loadMode.opsBeforeReturn === "sequenceNumber") {
|
|
989
|
+
(0, core_utils_1.assert)(loadToSequenceNumber !== undefined, 0x727 /* sequenceNumber should be defined */);
|
|
990
|
+
// Note: It is possible that we think the latest snapshot is newer than the specified sequence number
|
|
991
|
+
// due to saved ops that may be replayed after the snapshot.
|
|
992
|
+
// https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
|
|
993
|
+
if (dmAttributes.sequenceNumber > loadToSequenceNumber) {
|
|
994
|
+
throw new Error("Cannot satisfy request to pause the container at the specified sequence number. Most recent snapshot is newer than the specified sequence number.");
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
// Force readonly mode - this will ensure we don't receive an error for the lack of join op
|
|
998
|
+
this.forceReadonly(true);
|
|
999
|
+
// We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
|
|
1000
|
+
const opHandler = () => {
|
|
1001
|
+
if (loadToSequenceNumber === undefined) {
|
|
1002
|
+
// If there is no specified sequence number, pause after the inbound queue is empty.
|
|
1003
|
+
if (this.deltaManager.inbound.length !== 0) {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
else {
|
|
1008
|
+
// If there is a specified sequence number, keep processing until we reach it.
|
|
1009
|
+
if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
// Pause op processing once we have processed the desired number of ops.
|
|
1014
|
+
void this.deltaManager.inbound.pause();
|
|
1015
|
+
void this.deltaManager.outbound.pause();
|
|
1016
|
+
this.off("op", opHandler);
|
|
1017
|
+
};
|
|
1018
|
+
if ((loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
|
|
1019
|
+
this.deltaManager.lastSequenceNumber === loadToSequenceNumber) {
|
|
1020
|
+
// If we have already reached the desired sequence number, call opHandler() to pause immediately.
|
|
1021
|
+
opHandler();
|
|
1022
|
+
}
|
|
1023
|
+
else {
|
|
1024
|
+
// If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
|
|
1025
|
+
this.on("op", opHandler);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
933
1028
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
934
1029
|
// Kick off any ops fetching if required.
|
|
935
1030
|
switch (loadMode.opsBeforeReturn) {
|
|
@@ -938,32 +1033,30 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
938
1033
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
939
1034
|
this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
|
|
940
1035
|
break;
|
|
1036
|
+
case "sequenceNumber":
|
|
941
1037
|
case "cached":
|
|
942
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
|
|
943
|
-
break;
|
|
944
1038
|
case "all":
|
|
945
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes,
|
|
1039
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, loadMode.opsBeforeReturn);
|
|
946
1040
|
break;
|
|
947
1041
|
default:
|
|
948
|
-
(0,
|
|
1042
|
+
(0, core_utils_1.unreachableCase)(loadMode.opsBeforeReturn);
|
|
949
1043
|
}
|
|
950
1044
|
// ...load in the existing quorum
|
|
951
1045
|
// Initialize the protocol handler
|
|
952
1046
|
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
|
|
1047
|
+
timings.phase3 = client_utils_1.performance.now();
|
|
953
1048
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
954
|
-
await this.
|
|
955
|
-
|
|
1049
|
+
await this.instantiateRuntime(codeDetails, snapshot,
|
|
1050
|
+
// give runtime a dummy value so it knows we're loading from a stash blob
|
|
1051
|
+
pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined);
|
|
956
1052
|
// replay saved ops
|
|
957
1053
|
if (pendingLocalState) {
|
|
958
1054
|
for (const message of pendingLocalState.savedOps) {
|
|
959
1055
|
this.processRemoteMessage(message);
|
|
960
1056
|
// allow runtime to apply stashed ops at this op's sequence number
|
|
961
|
-
await this.
|
|
1057
|
+
await this.runtime.notifyOpReplay?.(message);
|
|
962
1058
|
}
|
|
963
1059
|
pendingLocalState.savedOps = [];
|
|
964
|
-
// now set clientId to stashed clientId so live ops are correctly processed as local
|
|
965
|
-
(0, common_utils_1.assert)(this.clientId === undefined, 0x5d6 /* Unexpected clientId when setting stashed clientId */);
|
|
966
|
-
this._clientId = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId;
|
|
967
1060
|
}
|
|
968
1061
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
969
1062
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
@@ -975,24 +1068,20 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
975
1068
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
976
1069
|
this._deltaManager.inbound.pause();
|
|
977
1070
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1071
|
+
this.handleDeltaConnectionArg(connectionArgs, loadMode.deltaConnection, pendingLocalState !== undefined);
|
|
1072
|
+
}
|
|
1073
|
+
// If we have not yet reached `loadToSequenceNumber`, we will wait for ops to arrive until we reach it
|
|
1074
|
+
if (loadToSequenceNumber !== undefined &&
|
|
1075
|
+
this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
|
|
1076
|
+
await new Promise((resolve, reject) => {
|
|
1077
|
+
const opHandler = (message) => {
|
|
1078
|
+
if (message.sequenceNumber >= loadToSequenceNumber) {
|
|
1079
|
+
resolve();
|
|
1080
|
+
this.off("op", opHandler);
|
|
983
1081
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
this.inboundQueuePausedFromInit = false;
|
|
988
|
-
this._deltaManager.inbound.resume();
|
|
989
|
-
this._deltaManager.inboundSignal.resume();
|
|
990
|
-
break;
|
|
991
|
-
case "none":
|
|
992
|
-
break;
|
|
993
|
-
default:
|
|
994
|
-
(0, common_utils_1.unreachableCase)(loadMode.deltaConnection);
|
|
995
|
-
}
|
|
1082
|
+
};
|
|
1083
|
+
this.on("op", opHandler);
|
|
1084
|
+
});
|
|
996
1085
|
}
|
|
997
1086
|
// Safety net: static version of Container.load() should have learned about it through "closed" handler.
|
|
998
1087
|
// But if that did not happen for some reason, fail load for sure.
|
|
@@ -1004,6 +1093,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1004
1093
|
}
|
|
1005
1094
|
// Internal context is fully loaded at this point
|
|
1006
1095
|
this.setLoaded();
|
|
1096
|
+
timings.end = client_utils_1.performance.now();
|
|
1097
|
+
this.subLogger.sendTelemetryEvent({
|
|
1098
|
+
eventName: "LoadStagesTimings",
|
|
1099
|
+
details: JSON.stringify(timings),
|
|
1100
|
+
}, undefined, core_interfaces_1.LogLevel.verbose);
|
|
1007
1101
|
return {
|
|
1008
1102
|
sequenceNumber: attributes.sequenceNumber,
|
|
1009
1103
|
version: versionId,
|
|
@@ -1011,7 +1105,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1011
1105
|
dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
|
|
1012
1106
|
};
|
|
1013
1107
|
}
|
|
1014
|
-
async createDetached(
|
|
1108
|
+
async createDetached(codeDetails) {
|
|
1015
1109
|
const attributes = {
|
|
1016
1110
|
sequenceNumber: detachedContainerRefSeqNumber,
|
|
1017
1111
|
term: protocol_1.OnlyValidTermValue,
|
|
@@ -1019,19 +1113,18 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1019
1113
|
};
|
|
1020
1114
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
1021
1115
|
// Need to just seed the source data in the code quorum. Quorum itself is empty
|
|
1022
|
-
const qValues = (0, quorum_1.initQuorumValuesFromCodeDetails)(
|
|
1116
|
+
const qValues = (0, quorum_1.initQuorumValuesFromCodeDetails)(codeDetails);
|
|
1023
1117
|
this.initializeProtocolState(attributes, {
|
|
1024
1118
|
members: [],
|
|
1025
1119
|
proposals: [],
|
|
1026
1120
|
values: qValues,
|
|
1027
1121
|
});
|
|
1028
|
-
|
|
1029
|
-
await this.instantiateContextDetached(false);
|
|
1122
|
+
await this.instantiateRuntime(codeDetails, undefined);
|
|
1030
1123
|
this.setLoaded();
|
|
1031
1124
|
}
|
|
1032
1125
|
async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
|
|
1033
1126
|
if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
|
|
1034
|
-
(0,
|
|
1127
|
+
(0, core_utils_1.assert)(!!this.detachedBlobStorage && this.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
|
|
1035
1128
|
delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
|
|
1036
1129
|
}
|
|
1037
1130
|
const snapshotTree = (0, utils_1.getSnapshotTreeFromSerializedContainer)(detachedContainerSnapshot);
|
|
@@ -1041,14 +1134,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1041
1134
|
// Initialize the protocol handler
|
|
1042
1135
|
const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshotTree);
|
|
1043
1136
|
const qValues = await (0, driver_utils_1.readAndParse)(this.storageAdapter, baseTree.blobs.quorumValues);
|
|
1044
|
-
const codeDetails = (0, quorum_1.getCodeDetailsFromQuorumValues)(qValues);
|
|
1045
1137
|
this.initializeProtocolState(attributes, {
|
|
1046
1138
|
members: [],
|
|
1047
1139
|
proposals: [],
|
|
1048
|
-
values:
|
|
1140
|
+
values: qValues,
|
|
1049
1141
|
});
|
|
1050
|
-
|
|
1051
|
-
snapshotTree);
|
|
1142
|
+
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1143
|
+
await this.instantiateRuntime(codeDetails, snapshotTree);
|
|
1052
1144
|
this.setLoaded();
|
|
1053
1145
|
}
|
|
1054
1146
|
async getDocumentAttributes(storage, tree) {
|
|
@@ -1085,7 +1177,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1085
1177
|
}
|
|
1086
1178
|
initializeProtocolState(attributes, quorumSnapshot) {
|
|
1087
1179
|
const protocol = this.protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(protocol_definitions_1.MessageType.Propose, JSON.stringify({ key, value })));
|
|
1088
|
-
const protocolLogger =
|
|
1180
|
+
const protocolLogger = (0, telemetry_utils_1.createChildLogger)({
|
|
1181
|
+
logger: this.subLogger,
|
|
1182
|
+
namespace: "ProtocolHandler",
|
|
1183
|
+
});
|
|
1089
1184
|
protocol.quorum.on("error", (error) => {
|
|
1090
1185
|
protocolLogger.sendErrorEvent(error);
|
|
1091
1186
|
});
|
|
@@ -1147,8 +1242,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1147
1242
|
return pkg;
|
|
1148
1243
|
}
|
|
1149
1244
|
get client() {
|
|
1150
|
-
|
|
1151
|
-
const client = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.client) !== undefined
|
|
1245
|
+
const client = this.options?.client !== undefined
|
|
1152
1246
|
? this.options.client
|
|
1153
1247
|
: {
|
|
1154
1248
|
details: {
|
|
@@ -1179,14 +1273,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1179
1273
|
}
|
|
1180
1274
|
createDeltaManager() {
|
|
1181
1275
|
const serviceProvider = () => this.service;
|
|
1182
|
-
const deltaManager = new deltaManager_1.DeltaManager(serviceProvider, telemetry_utils_1.
|
|
1276
|
+
const deltaManager = new deltaManager_1.DeltaManager(serviceProvider, (0, telemetry_utils_1.createChildLogger)({ logger: this.subLogger, namespace: "DeltaManager" }), () => this.activeConnection(), (props) => new connectionManager_1.ConnectionManager(serviceProvider, () => this.isDirty, this.client, this._canReconnect, (0, telemetry_utils_1.createChildLogger)({ logger: this.subLogger, namespace: "ConnectionManager" }), props));
|
|
1183
1277
|
// Disable inbound queues as Container is not ready to accept any ops until we are fully loaded!
|
|
1184
1278
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1185
1279
|
deltaManager.inbound.pause();
|
|
1186
1280
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1187
1281
|
deltaManager.inboundSignal.pause();
|
|
1188
1282
|
deltaManager.on("connect", (details, _opsBehind) => {
|
|
1189
|
-
(0,
|
|
1283
|
+
(0, core_utils_1.assert)(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
|
|
1190
1284
|
this.connectionStateHandler.receivedConnectEvent(details);
|
|
1191
1285
|
});
|
|
1192
1286
|
deltaManager.on("establishingConnection", (reason) => {
|
|
@@ -1195,11 +1289,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1195
1289
|
deltaManager.on("cancelEstablishingConnection", (reason) => {
|
|
1196
1290
|
this.connectionStateHandler.cancelEstablishingConnection(reason);
|
|
1197
1291
|
});
|
|
1198
|
-
deltaManager.on("disconnect", (reason
|
|
1199
|
-
|
|
1200
|
-
(_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
|
|
1292
|
+
deltaManager.on("disconnect", (reason) => {
|
|
1293
|
+
this.noopHeuristic?.notifyDisconnect();
|
|
1201
1294
|
if (!this.closed) {
|
|
1202
|
-
this.connectionStateHandler.receivedDisconnectEvent(reason
|
|
1295
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
1203
1296
|
}
|
|
1204
1297
|
});
|
|
1205
1298
|
deltaManager.on("throttled", (warning) => {
|
|
@@ -1231,10 +1324,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1231
1324
|
},
|
|
1232
1325
|
}, prefetchType);
|
|
1233
1326
|
}
|
|
1234
|
-
logConnectionStateChangeTelemetry(value, oldState, reason
|
|
1235
|
-
var _a;
|
|
1327
|
+
logConnectionStateChangeTelemetry(value, oldState, reason) {
|
|
1236
1328
|
// Log actual event
|
|
1237
|
-
const time =
|
|
1329
|
+
const time = client_utils_1.performance.now();
|
|
1238
1330
|
this.connectionTransitionTimes[value] = time;
|
|
1239
1331
|
const duration = time - this.connectionTransitionTimes[oldState];
|
|
1240
1332
|
let durationFromDisconnected;
|
|
@@ -1249,30 +1341,44 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1249
1341
|
if (value === connectionState_1.ConnectionState.Connected) {
|
|
1250
1342
|
durationFromDisconnected =
|
|
1251
1343
|
time - this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected];
|
|
1252
|
-
durationFromDisconnected = telemetry_utils_1.
|
|
1344
|
+
durationFromDisconnected = (0, telemetry_utils_1.formatTick)(durationFromDisconnected);
|
|
1253
1345
|
}
|
|
1254
1346
|
else if (value === connectionState_1.ConnectionState.CatchingUp) {
|
|
1255
1347
|
// This info is of most interesting while Catching Up.
|
|
1256
1348
|
checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
|
|
1257
|
-
|
|
1349
|
+
// Need to check that we have already loaded and fetched the snapshot.
|
|
1350
|
+
if (this.deltaManager.hasCheckpointSequenceNumber &&
|
|
1351
|
+
this._lifecycleState === "loaded") {
|
|
1258
1352
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
1259
1353
|
}
|
|
1260
1354
|
}
|
|
1261
1355
|
connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
|
|
1262
1356
|
}
|
|
1263
|
-
this.mc.logger.sendPerformanceEvent(
|
|
1357
|
+
this.mc.logger.sendPerformanceEvent({
|
|
1358
|
+
eventName: `ConnectionStateChange_${connectionState_1.ConnectionState[value]}`,
|
|
1359
|
+
from: connectionState_1.ConnectionState[oldState],
|
|
1360
|
+
duration,
|
|
1264
1361
|
durationFromDisconnected,
|
|
1265
|
-
reason,
|
|
1266
|
-
connectionInitiationReason,
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1362
|
+
reason: reason?.text,
|
|
1363
|
+
connectionInitiationReason,
|
|
1364
|
+
pendingClientId: this.connectionStateHandler.pendingClientId,
|
|
1365
|
+
clientId: this.clientId,
|
|
1366
|
+
autoReconnect,
|
|
1367
|
+
opsBehind,
|
|
1368
|
+
online: driver_utils_1.OnlineStatus[(0, driver_utils_1.isOnline)()],
|
|
1369
|
+
lastVisible: this.lastVisible !== undefined
|
|
1370
|
+
? client_utils_1.performance.now() - this.lastVisible
|
|
1371
|
+
: undefined,
|
|
1372
|
+
checkpointSequenceNumber,
|
|
1373
|
+
quorumSize: this._protocolHandler?.quorum.getMembers().size,
|
|
1374
|
+
isDirty: this.isDirty,
|
|
1375
|
+
...this._deltaManager.connectionProps,
|
|
1376
|
+
}, reason?.error);
|
|
1270
1377
|
if (value === connectionState_1.ConnectionState.Connected) {
|
|
1271
1378
|
this.firstConnection = false;
|
|
1272
1379
|
}
|
|
1273
1380
|
}
|
|
1274
1381
|
propagateConnectionState(initialTransition, disconnectedReason) {
|
|
1275
|
-
var _a;
|
|
1276
1382
|
// When container loaded, we want to propagate initial connection state.
|
|
1277
1383
|
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
1278
1384
|
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
@@ -1282,22 +1388,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1282
1388
|
return;
|
|
1283
1389
|
}
|
|
1284
1390
|
const state = this.connectionState === connectionState_1.ConnectionState.Connected;
|
|
1285
|
-
const logOpsOnReconnect = this.connectionState === connectionState_1.ConnectionState.Connected &&
|
|
1286
|
-
!this.firstConnection &&
|
|
1287
|
-
this.connectionMode === "write";
|
|
1288
|
-
if (logOpsOnReconnect) {
|
|
1289
|
-
this.messageCountAfterDisconnection = 0;
|
|
1290
|
-
}
|
|
1291
1391
|
// Both protocol and context should not be undefined if we got so far.
|
|
1292
|
-
this.setContextConnectedState(state,
|
|
1392
|
+
this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
|
|
1293
1393
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1294
|
-
(0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId, disconnectedReason);
|
|
1295
|
-
if (logOpsOnReconnect) {
|
|
1296
|
-
this.mc.logger.sendTelemetryEvent({
|
|
1297
|
-
eventName: "OpsSentOnReconnect",
|
|
1298
|
-
count: this.messageCountAfterDisconnection,
|
|
1299
|
-
});
|
|
1300
|
-
}
|
|
1394
|
+
(0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
|
|
1301
1395
|
}
|
|
1302
1396
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1303
1397
|
submitContainerMessage(type, contents, batch, metadata) {
|
|
@@ -1307,7 +1401,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1307
1401
|
case protocol_definitions_1.MessageType.Summarize:
|
|
1308
1402
|
return this.submitSummaryMessage(contents);
|
|
1309
1403
|
default: {
|
|
1310
|
-
const newError = new
|
|
1404
|
+
const newError = new telemetry_utils_1.GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type });
|
|
1311
1405
|
this.close(newError);
|
|
1312
1406
|
return -1;
|
|
1313
1407
|
}
|
|
@@ -1335,13 +1429,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1335
1429
|
return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
|
|
1336
1430
|
}
|
|
1337
1431
|
submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
|
|
1338
|
-
var _a;
|
|
1339
1432
|
if (this.connectionState !== connectionState_1.ConnectionState.Connected) {
|
|
1340
1433
|
this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
|
|
1341
1434
|
return -1;
|
|
1342
1435
|
}
|
|
1343
|
-
this.
|
|
1344
|
-
(_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
|
|
1436
|
+
this.noopHeuristic?.notifyMessageSent();
|
|
1345
1437
|
return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
|
|
1346
1438
|
}
|
|
1347
1439
|
processRemoteMessage(message) {
|
|
@@ -1352,21 +1444,31 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1352
1444
|
// Allow the protocol handler to process the message
|
|
1353
1445
|
const result = this.protocolHandler.processMessage(message, local);
|
|
1354
1446
|
// Forward messages to the loaded runtime for processing
|
|
1355
|
-
this.
|
|
1447
|
+
this.runtime.process(message, local);
|
|
1356
1448
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
1357
1449
|
if (this.activeConnection()) {
|
|
1358
|
-
if (this.
|
|
1450
|
+
if (this.noopHeuristic === undefined) {
|
|
1451
|
+
const serviceConfiguration = this.deltaManager.serviceConfiguration;
|
|
1359
1452
|
// Note that config from first connection will be used for this container's lifetime.
|
|
1360
1453
|
// That means that if relay service changes settings, such changes will impact only newly booted
|
|
1361
1454
|
// clients.
|
|
1362
1455
|
// All existing will continue to use settings they got earlier.
|
|
1363
|
-
(0,
|
|
1364
|
-
this.
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1456
|
+
(0, core_utils_1.assert)(serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
|
|
1457
|
+
this.noopHeuristic = new noopHeuristic_1.NoopHeuristic(serviceConfiguration.noopTimeFrequency, serviceConfiguration.noopCountFrequency);
|
|
1458
|
+
this.noopHeuristic.on("wantsNoop", () => {
|
|
1459
|
+
// On disconnect we notify the heuristic which should prevent it from wanting a noop.
|
|
1460
|
+
// Hitting this assert would imply we lost activeConnection between notifying the heuristic of a processed message and
|
|
1461
|
+
// running the microtask that the heuristic queued in response.
|
|
1462
|
+
(0, core_utils_1.assert)(this.activeConnection(), 0x241 /* "Trying to send noop without active connection" */);
|
|
1463
|
+
this.submitMessage(protocol_definitions_1.MessageType.NoOp);
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
this.noopHeuristic.notifyMessageProcessed(message);
|
|
1467
|
+
// The contract with the protocolHandler is that returning "immediateNoOp" is equivalent to "please immediately accept the proposal I just processed".
|
|
1468
|
+
if (result.immediateNoOp === true) {
|
|
1469
|
+
// ADO:1385: Remove cast and use MessageType once definition changes propagate
|
|
1470
|
+
this.submitMessage(driver_utils_1.MessageType2.Accept);
|
|
1368
1471
|
}
|
|
1369
|
-
this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
|
|
1370
1472
|
}
|
|
1371
1473
|
this.emit("op", message);
|
|
1372
1474
|
}
|
|
@@ -1375,12 +1477,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1375
1477
|
}
|
|
1376
1478
|
processSignal(message) {
|
|
1377
1479
|
// No clientId indicates a system signal message.
|
|
1378
|
-
if (
|
|
1480
|
+
if ((0, protocol_1.protocolHandlerShouldProcessSignal)(message)) {
|
|
1379
1481
|
this.protocolHandler.processSignal(message);
|
|
1380
1482
|
}
|
|
1381
1483
|
else {
|
|
1382
1484
|
const local = this.clientId === message.clientId;
|
|
1383
|
-
this.
|
|
1485
|
+
this.runtime.processSignal(message, local);
|
|
1384
1486
|
}
|
|
1385
1487
|
}
|
|
1386
1488
|
/**
|
|
@@ -1389,8 +1491,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1389
1491
|
* @returns The snapshot requested, or the latest snapshot if no version was specified, plus version ID
|
|
1390
1492
|
*/
|
|
1391
1493
|
async fetchSnapshotTree(specifiedVersion) {
|
|
1392
|
-
|
|
1393
|
-
const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
|
|
1494
|
+
const version = await this.getVersion(specifiedVersion ?? null);
|
|
1394
1495
|
if (version === undefined && specifiedVersion !== undefined) {
|
|
1395
1496
|
// We should have a defined version to load from if specified version requested
|
|
1396
1497
|
this.mc.logger.sendErrorEvent({
|
|
@@ -1399,35 +1500,38 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1399
1500
|
});
|
|
1400
1501
|
}
|
|
1401
1502
|
this._loadedFromVersion = version;
|
|
1402
|
-
const snapshot = (
|
|
1503
|
+
const snapshot = (await this.storageAdapter.getSnapshotTree(version)) ?? undefined;
|
|
1403
1504
|
if (snapshot === undefined && version !== undefined) {
|
|
1404
1505
|
this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
|
|
1405
1506
|
}
|
|
1406
|
-
return { snapshot, versionId: version
|
|
1407
|
-
}
|
|
1408
|
-
async instantiateContextDetached(existing, snapshot) {
|
|
1409
|
-
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1410
|
-
if (codeDetails === undefined) {
|
|
1411
|
-
throw new Error("pkg should be provided in create flow!!");
|
|
1412
|
-
}
|
|
1413
|
-
await this.instantiateContext(existing, codeDetails, snapshot);
|
|
1507
|
+
return { snapshot, versionId: version?.id };
|
|
1414
1508
|
}
|
|
1415
|
-
async
|
|
1416
|
-
|
|
1417
|
-
(0, common_utils_1.assert)(((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing context not disposed" */);
|
|
1509
|
+
async instantiateRuntime(codeDetails, snapshot, pendingLocalState) {
|
|
1510
|
+
(0, core_utils_1.assert)(this._runtime?.disposed !== false, 0x0dd /* "Existing runtime not disposed" */);
|
|
1418
1511
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1419
1512
|
// are set. Global requests will still go directly to the loader
|
|
1420
1513
|
const maybeLoader = this.scope;
|
|
1421
1514
|
const loader = new loader_1.RelativeLoader(this, maybeLoader.ILoader);
|
|
1422
|
-
|
|
1423
|
-
this.
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1515
|
+
const loadCodeResult = await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "CodeLoad" }, async () => this.codeLoader.load(codeDetails));
|
|
1516
|
+
this._loadedModule = {
|
|
1517
|
+
module: loadCodeResult.module,
|
|
1518
|
+
// An older interface ICodeLoader could return an IFluidModule which didn't have details.
|
|
1519
|
+
// If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
|
|
1520
|
+
// TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
|
|
1521
|
+
details: loadCodeResult.details ?? codeDetails,
|
|
1522
|
+
};
|
|
1523
|
+
const fluidExport = this._loadedModule.module.fluidExport;
|
|
1524
|
+
const runtimeFactory = fluidExport?.IRuntimeFactory;
|
|
1525
|
+
if (runtimeFactory === undefined) {
|
|
1526
|
+
throw new Error(packageNotFactoryError);
|
|
1428
1527
|
}
|
|
1429
|
-
this.
|
|
1430
|
-
|
|
1528
|
+
const getSpecifiedCodeDetails = () => (this.protocolHandler.quorum.get("code") ??
|
|
1529
|
+
this.protocolHandler.quorum.get("code2"));
|
|
1530
|
+
const existing = snapshot !== undefined;
|
|
1531
|
+
const context = new containerContext_1.ContainerContext(this.options, this.scope, snapshot, this._loadedFromVersion, this._deltaManager, this.storageAdapter, this.protocolHandler.quorum, 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, () => this.resolvedUrl?.id, () => this.clientId, () => this.attachState, () => this.connected, getSpecifiedCodeDetails, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState);
|
|
1532
|
+
this._runtime = await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
|
|
1533
|
+
this._lifecycleEvents.emit("runtimeInstantiated");
|
|
1534
|
+
this._loadedCodeDetails = codeDetails;
|
|
1431
1535
|
}
|
|
1432
1536
|
/**
|
|
1433
1537
|
* Set the connected state of the ContainerContext
|
|
@@ -1436,18 +1540,36 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1436
1540
|
* @param readonly - Is the container in readonly mode?
|
|
1437
1541
|
*/
|
|
1438
1542
|
setContextConnectedState(state, readonly) {
|
|
1439
|
-
|
|
1440
|
-
if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
|
|
1543
|
+
if (this._runtime?.disposed === false) {
|
|
1441
1544
|
/**
|
|
1442
1545
|
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
1443
1546
|
* ops getting through to the DeltaManager.
|
|
1444
1547
|
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
1445
1548
|
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
1446
1549
|
*/
|
|
1447
|
-
this.
|
|
1550
|
+
this.runtime.setConnectionState(state && !readonly, this.clientId);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
handleDeltaConnectionArg(connectionArgs, deltaConnectionArg, canConnect = true) {
|
|
1554
|
+
switch (deltaConnectionArg) {
|
|
1555
|
+
case undefined:
|
|
1556
|
+
if (canConnect) {
|
|
1557
|
+
// connect to delta stream now since we did not before
|
|
1558
|
+
this.connectToDeltaStream(connectionArgs);
|
|
1559
|
+
}
|
|
1560
|
+
// intentional fallthrough
|
|
1561
|
+
case "delayed":
|
|
1562
|
+
(0, core_utils_1.assert)(this.inboundQueuePausedFromInit, 0x346 /* inboundQueuePausedFromInit should be true */);
|
|
1563
|
+
this.inboundQueuePausedFromInit = false;
|
|
1564
|
+
this._deltaManager.inbound.resume();
|
|
1565
|
+
this._deltaManager.inboundSignal.resume();
|
|
1566
|
+
break;
|
|
1567
|
+
case "none":
|
|
1568
|
+
break;
|
|
1569
|
+
default:
|
|
1570
|
+
(0, core_utils_1.unreachableCase)(deltaConnectionArg);
|
|
1448
1571
|
}
|
|
1449
1572
|
}
|
|
1450
1573
|
}
|
|
1451
1574
|
exports.Container = Container;
|
|
1452
|
-
Container.version = "^0.1.0";
|
|
1453
1575
|
//# sourceMappingURL=container.js.map
|