@fluidframework/container-loader 2.0.0-dev.2.3.0.115467 → 2.0.0-dev.4.1.0.148229
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +18 -21
- package/.mocharc.js +2 -2
- package/README.md +65 -44
- package/api-extractor.json +2 -2
- package/closeAndGetPendingLocalState.md +51 -0
- package/dist/audience.d.ts +0 -1
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/collabWindowTracker.d.ts.map +1 -1
- package/dist/collabWindowTracker.js.map +1 -1
- package/dist/connectionManager.d.ts +5 -5
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +107 -44
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionState.d.ts.map +1 -1
- package/dist/connectionState.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +7 -7
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +50 -21
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +64 -5
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +329 -137
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +19 -8
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +58 -14
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +41 -2
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +88 -14
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +3 -3
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +21 -8
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +112 -37
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts +10 -22
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaManagerProxy.js +14 -50
- package/dist/deltaManagerProxy.js.map +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +4 -2
- package/dist/deltaQueue.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +13 -4
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +38 -24
- package/dist/loader.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +2 -1
- package/dist/protocol.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts +6 -2
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js +7 -4
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +6 -2
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +8 -5
- package/dist/utils.js.map +1 -1
- package/lib/audience.d.ts +0 -1
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/collabWindowTracker.d.ts.map +1 -1
- package/lib/collabWindowTracker.js.map +1 -1
- package/lib/connectionManager.d.ts +5 -5
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +110 -47
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionState.d.ts.map +1 -1
- package/lib/connectionState.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +7 -7
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +50 -21
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +64 -5
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +336 -144
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +19 -8
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +59 -15
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +41 -2
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +86 -14
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +3 -3
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +21 -8
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +114 -39
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts +10 -22
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaManagerProxy.js +14 -50
- package/lib/deltaManagerProxy.js.map +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +4 -2
- package/lib/deltaQueue.js.map +1 -1
- package/lib/index.d.ts +4 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +13 -4
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +37 -24
- package/lib/loader.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +2 -1
- package/lib/protocol.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts +6 -2
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js +7 -4
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +6 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +8 -5
- package/lib/utils.js.map +1 -1
- package/package.json +67 -56
- package/prettier.config.cjs +1 -1
- package/src/audience.ts +51 -46
- package/src/catchUpMonitor.ts +39 -37
- package/src/collabWindowTracker.ts +75 -70
- package/src/connectionManager.ts +1040 -941
- package/src/connectionState.ts +19 -19
- package/src/connectionStateHandler.ts +557 -463
- package/src/container.ts +2147 -1784
- package/src/containerContext.ts +417 -345
- package/src/containerStorageAdapter.ts +268 -154
- package/src/contracts.ts +155 -153
- package/src/deltaManager.ts +1074 -945
- package/src/deltaManagerProxy.ts +88 -137
- package/src/deltaQueue.ts +155 -151
- package/src/index.ts +13 -17
- package/src/loader.ts +434 -427
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +93 -87
- package/src/protocolTreeDocumentStorageService.ts +34 -34
- package/src/quorum.ts +34 -34
- package/src/retriableDocumentStorageService.ts +118 -102
- package/src/utils.ts +93 -83
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +8 -12
package/dist/container.js
CHANGED
|
@@ -71,8 +71,8 @@ async function waitContainerToCatchUp(container) {
|
|
|
71
71
|
// Waiting for "connected" state in either case gets us at least to our own Join op
|
|
72
72
|
// which is a reasonable approximation of "caught up"
|
|
73
73
|
const waitForOps = () => {
|
|
74
|
-
(0, common_utils_1.assert)(container.connectionState === connectionState_1.ConnectionState.CatchingUp
|
|
75
|
-
|
|
74
|
+
(0, common_utils_1.assert)(container.connectionState === connectionState_1.ConnectionState.CatchingUp ||
|
|
75
|
+
container.connectionState === connectionState_1.ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
|
|
76
76
|
const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
|
|
77
77
|
const connectionOpSeqNumber = deltaManager.lastKnownSeqNumber;
|
|
78
78
|
(0, common_utils_1.assert)(deltaManager.lastSequenceNumber <= connectionOpSeqNumber, 0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
|
|
@@ -113,7 +113,7 @@ const getCodeProposal =
|
|
|
113
113
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
114
114
|
(quorum) => { var _a; return (_a = quorum.get("code")) !== null && _a !== void 0 ? _a : quorum.get("code2"); };
|
|
115
115
|
/**
|
|
116
|
-
* Helper function to report to telemetry cases where operation takes longer than expected (
|
|
116
|
+
* Helper function to report to telemetry cases where operation takes longer than expected (200ms)
|
|
117
117
|
* @param logger - logger to use
|
|
118
118
|
* @param eventName - event name
|
|
119
119
|
* @param action - functor to call and measure
|
|
@@ -127,9 +127,15 @@ async function ReportIfTooLong(logger, eventName, action) {
|
|
|
127
127
|
}
|
|
128
128
|
exports.ReportIfTooLong = ReportIfTooLong;
|
|
129
129
|
const summarizerClientType = "summarizer";
|
|
130
|
+
/**
|
|
131
|
+
* @deprecated - In the next release Container will no longer be exported, IContainer should be used in its place.
|
|
132
|
+
*/
|
|
130
133
|
class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
134
|
+
/**
|
|
135
|
+
* @internal
|
|
136
|
+
*/
|
|
131
137
|
constructor(loader, config, protocolHandlerBuilder) {
|
|
132
|
-
var _a, _b;
|
|
138
|
+
var _a, _b, _c;
|
|
133
139
|
super((name, error) => {
|
|
134
140
|
this.mc.logger.sendErrorEvent({
|
|
135
141
|
eventName: "ContainerEventHandlerException",
|
|
@@ -141,6 +147,21 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
141
147
|
// Tells if container can reconnect on losing fist connection
|
|
142
148
|
// If false, container gets closed on loss of connection.
|
|
143
149
|
this._canReconnect = true;
|
|
150
|
+
/**
|
|
151
|
+
* Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
|
|
152
|
+
*
|
|
153
|
+
* States are allowed to progress to further states:
|
|
154
|
+
* "loading" - "loaded" - "closing" - "disposing" - "closed" - "disposed"
|
|
155
|
+
*
|
|
156
|
+
* For example, moving from "closed" to "disposing" is not allowed since it is an earlier state.
|
|
157
|
+
*
|
|
158
|
+
* loading: Container has been created, but is not yet in normal/loaded state
|
|
159
|
+
* loaded: Container is in normal/loaded state
|
|
160
|
+
* closing: Container has started closing process (for re-entrancy prevention)
|
|
161
|
+
* disposing: Container has started disposing process (for re-entrancy prevention)
|
|
162
|
+
* closed: Container has closed
|
|
163
|
+
* disposed: Container has been disposed
|
|
164
|
+
*/
|
|
144
165
|
this._lifecycleState = "loading";
|
|
145
166
|
this._attachState = container_definitions_1.AttachState.Detached;
|
|
146
167
|
/** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
|
|
@@ -150,7 +171,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
150
171
|
this.messageCountAfterDisconnection = 0;
|
|
151
172
|
this.attachStarted = false;
|
|
152
173
|
this._dirtyContainer = false;
|
|
174
|
+
this.savedOps = [];
|
|
153
175
|
this.setAutoReconnectTime = common_utils_1.performance.now();
|
|
176
|
+
this._disposed = false;
|
|
154
177
|
this.clientDetailsOverride = config.clientDetailsOverride;
|
|
155
178
|
this._resolvedUrl = config.resolvedUrl;
|
|
156
179
|
if (config.canReconnect !== undefined) {
|
|
@@ -187,15 +210,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
187
210
|
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; },
|
|
188
211
|
dmLastMsqSeqTimestamp: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.timestamp; },
|
|
189
212
|
dmLastMsqSeqClientId: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId; },
|
|
213
|
+
dmLastMsgClientSeq: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientSequenceNumber; },
|
|
190
214
|
connectionStateDuration: () => common_utils_1.performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
191
215
|
},
|
|
192
216
|
});
|
|
193
217
|
// Prefix all events in this file with container-loader
|
|
194
218
|
this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(this.subLogger, "Container"));
|
|
195
|
-
|
|
196
|
-
this.options = Object.assign(Object.assign({}, this.loader.services.options), { summarizeProtocolTree });
|
|
219
|
+
this.options = Object.assign({}, this.loader.services.options);
|
|
197
220
|
this._deltaManager = this.createDeltaManager();
|
|
198
|
-
this._clientId = (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId;
|
|
199
221
|
this.connectionStateHandler = (0, connectionStateHandler_1.createConnectionStateHandler)({
|
|
200
222
|
logger: this.mc.logger,
|
|
201
223
|
connectionStateChanged: (value, oldState, reason) => {
|
|
@@ -204,17 +226,23 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
204
226
|
}
|
|
205
227
|
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
206
228
|
if (this._lifecycleState === "loaded") {
|
|
207
|
-
this.propagateConnectionState(false /* initial transition */, value === connectionState_1.ConnectionState.Disconnected
|
|
229
|
+
this.propagateConnectionState(false /* initial transition */, value === connectionState_1.ConnectionState.Disconnected
|
|
230
|
+
? reason
|
|
231
|
+
: undefined /* disconnectedReason */);
|
|
208
232
|
}
|
|
209
233
|
},
|
|
210
234
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
211
235
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
212
|
-
logConnectionIssue: (eventName, details) => {
|
|
236
|
+
logConnectionIssue: (eventName, category, details) => {
|
|
213
237
|
const mode = this.connectionMode;
|
|
214
238
|
// We get here when socket does not receive any ops on "write" connection, including
|
|
215
|
-
// its own join op.
|
|
239
|
+
// its own join op.
|
|
240
|
+
// Report issues only if we already loaded container - op processing is paused while container is loading,
|
|
241
|
+
// so we always time-out processing of join op in cases where fetching snapshot takes a minute.
|
|
242
|
+
// It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
|
|
216
243
|
this._deltaManager.logConnectionIssue(Object.assign({ eventName,
|
|
217
|
-
mode,
|
|
244
|
+
mode, category: this._lifecycleState === "loading" ? "generic" : category, duration: common_utils_1.performance.now() -
|
|
245
|
+
this.connectionTransitionTimes[connectionState_1.ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
|
|
218
246
|
// If this is "write" connection, it took too long to receive join op. But in most cases that's due
|
|
219
247
|
// to very slow op fetches and we will eventually get there.
|
|
220
248
|
// For "read" connections, we get here due to self join signal not arriving on time. We will need to
|
|
@@ -228,13 +256,20 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
228
256
|
this.connect();
|
|
229
257
|
}
|
|
230
258
|
},
|
|
231
|
-
}, this.deltaManager,
|
|
259
|
+
}, this.deltaManager, (_a = config.serializedContainerState) === null || _a === void 0 ? void 0 : _a.clientId);
|
|
232
260
|
this.on(savedContainerEvent, () => {
|
|
233
261
|
this.connectionStateHandler.containerSaved();
|
|
234
262
|
});
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
263
|
+
// We expose our storage publicly, so it's possible others may call uploadSummaryWithContext() with a
|
|
264
|
+
// non-combined summary tree (in particular, ContainerRuntime.submitSummary). We'll intercept those calls
|
|
265
|
+
// using this callback and fix them up.
|
|
266
|
+
const addProtocolSummaryIfMissing = (summaryTree) => (0, driver_utils_1.isCombinedAppAndProtocolSummary)(summaryTree) === true
|
|
267
|
+
? summaryTree
|
|
268
|
+
: (0, driver_utils_1.combineAppAndProtocolSummary)(summaryTree, this.captureProtocolSummary());
|
|
269
|
+
// Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
|
|
270
|
+
// Even if not forced on via this flag, combined summaries may still be enabled by service policy.
|
|
271
|
+
const forceEnableSummarizeProtocolTree = (_b = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2")) !== null && _b !== void 0 ? _b : this.loader.services.options.summarizeProtocolTree;
|
|
272
|
+
this.storageAdapter = new containerStorageAdapter_1.ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, (_c = config.serializedContainerState) === null || _c === void 0 ? void 0 : _c.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
|
|
238
273
|
const isDomAvailable = typeof document === "object" &&
|
|
239
274
|
document !== null &&
|
|
240
275
|
typeof document.addEventListener === "function" &&
|
|
@@ -248,48 +283,17 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
248
283
|
}
|
|
249
284
|
else {
|
|
250
285
|
// settimeout so this will hopefully fire after disconnect event if being hidden caused it
|
|
251
|
-
setTimeout(() => {
|
|
286
|
+
setTimeout(() => {
|
|
287
|
+
this.lastVisible = undefined;
|
|
288
|
+
}, 0);
|
|
252
289
|
}
|
|
253
290
|
};
|
|
254
291
|
document.addEventListener("visibilitychange", this.visibilityEventHandler);
|
|
255
292
|
}
|
|
256
|
-
// We observed that most users of platform do not check Container.connected event on load, causing bugs.
|
|
257
|
-
// As such, we are raising events when new listener pops up.
|
|
258
|
-
// Note that we can raise both "disconnected" & "connect" events at the same time,
|
|
259
|
-
// if we are in connecting stage.
|
|
260
|
-
this.on("newListener", (event, listener) => {
|
|
261
|
-
// Fire events on the end of JS turn, giving a chance for caller to be in consistent state.
|
|
262
|
-
Promise.resolve().then(() => {
|
|
263
|
-
switch (event) {
|
|
264
|
-
case dirtyContainerEvent:
|
|
265
|
-
if (this._dirtyContainer) {
|
|
266
|
-
listener();
|
|
267
|
-
}
|
|
268
|
-
break;
|
|
269
|
-
case savedContainerEvent:
|
|
270
|
-
if (!this._dirtyContainer) {
|
|
271
|
-
listener();
|
|
272
|
-
}
|
|
273
|
-
break;
|
|
274
|
-
case telemetry_utils_1.connectedEventName:
|
|
275
|
-
if (this.connected) {
|
|
276
|
-
listener(this.clientId);
|
|
277
|
-
}
|
|
278
|
-
break;
|
|
279
|
-
case telemetry_utils_1.disconnectedEventName:
|
|
280
|
-
if (!this.connected) {
|
|
281
|
-
listener();
|
|
282
|
-
}
|
|
283
|
-
break;
|
|
284
|
-
default:
|
|
285
|
-
}
|
|
286
|
-
}).catch((error) => {
|
|
287
|
-
this.mc.logger.sendErrorEvent({ eventName: "RaiseConnectedEventError" }, error);
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
293
|
}
|
|
291
294
|
/**
|
|
292
295
|
* Load an existing container.
|
|
296
|
+
* @internal
|
|
293
297
|
*/
|
|
294
298
|
static async load(loader, loadOptions, pendingLocalState, protocolHandlerBuilder) {
|
|
295
299
|
const container = new Container(loader, {
|
|
@@ -311,7 +315,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
311
315
|
reject(err !== null && err !== void 0 ? err : new container_utils_1.GenericError("Container closed without error during load"));
|
|
312
316
|
};
|
|
313
317
|
container.on("closed", onClosed);
|
|
314
|
-
container
|
|
318
|
+
container
|
|
319
|
+
.load(version, mode, pendingLocalState)
|
|
315
320
|
.finally(() => {
|
|
316
321
|
container.removeListener("closed", onClosed);
|
|
317
322
|
})
|
|
@@ -360,10 +365,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
360
365
|
}
|
|
361
366
|
}
|
|
362
367
|
get closed() {
|
|
363
|
-
return (this._lifecycleState === "closing" ||
|
|
368
|
+
return (this._lifecycleState === "closing" ||
|
|
369
|
+
this._lifecycleState === "closed" ||
|
|
370
|
+
this._lifecycleState === "disposing" ||
|
|
371
|
+
this._lifecycleState === "disposed");
|
|
364
372
|
}
|
|
365
373
|
get storage() {
|
|
366
|
-
return this.
|
|
374
|
+
return this.storageAdapter;
|
|
367
375
|
}
|
|
368
376
|
get context() {
|
|
369
377
|
if (this._context === undefined) {
|
|
@@ -377,8 +385,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
377
385
|
}
|
|
378
386
|
return this._protocolHandler;
|
|
379
387
|
}
|
|
380
|
-
get connectionMode() {
|
|
381
|
-
|
|
388
|
+
get connectionMode() {
|
|
389
|
+
return this._deltaManager.connectionManager.connectionMode;
|
|
390
|
+
}
|
|
391
|
+
get IFluidRouter() {
|
|
392
|
+
return this;
|
|
393
|
+
}
|
|
382
394
|
get resolvedUrl() {
|
|
383
395
|
return this._resolvedUrl;
|
|
384
396
|
}
|
|
@@ -430,6 +442,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
430
442
|
get clientDetails() {
|
|
431
443
|
return this._deltaManager.clientDetails;
|
|
432
444
|
}
|
|
445
|
+
get offlineLoadEnabled() {
|
|
446
|
+
var _a;
|
|
447
|
+
// summarizer will not have any pending state we want to save
|
|
448
|
+
return (((_a = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad")) !== null && _a !== void 0 ? _a : false) &&
|
|
449
|
+
this.clientDetails.capabilities.interactive);
|
|
450
|
+
}
|
|
433
451
|
/**
|
|
434
452
|
* Get the code details that are currently specified for the container.
|
|
435
453
|
* @returns The current code details if any are specified, undefined if none are specified.
|
|
@@ -460,24 +478,71 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
460
478
|
get isDirty() {
|
|
461
479
|
return this._dirtyContainer;
|
|
462
480
|
}
|
|
463
|
-
get serviceFactory() {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
get
|
|
481
|
+
get serviceFactory() {
|
|
482
|
+
return this.loader.services.documentServiceFactory;
|
|
483
|
+
}
|
|
484
|
+
get urlResolver() {
|
|
485
|
+
return this.loader.services.urlResolver;
|
|
486
|
+
}
|
|
487
|
+
get scope() {
|
|
488
|
+
return this.loader.services.scope;
|
|
489
|
+
}
|
|
490
|
+
get codeLoader() {
|
|
491
|
+
return this.loader.services.codeLoader;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
|
|
495
|
+
*/
|
|
496
|
+
async getEntryPoint() {
|
|
497
|
+
var _a, _b;
|
|
498
|
+
// Only the disposing/disposed lifecycle states should prevent access to the entryPoint; closing/closed should still
|
|
499
|
+
// allow it since they mean a kind of read-only state for the Container.
|
|
500
|
+
// Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
|
|
501
|
+
if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
|
|
502
|
+
throw new container_utils_1.UsageError("The container is disposing or disposed");
|
|
503
|
+
}
|
|
504
|
+
while (this._context === undefined) {
|
|
505
|
+
await new Promise((resolve, reject) => {
|
|
506
|
+
const contextChangedHandler = () => {
|
|
507
|
+
resolve();
|
|
508
|
+
this.off("disposed", disposedHandler);
|
|
509
|
+
};
|
|
510
|
+
const disposedHandler = (error) => {
|
|
511
|
+
reject(error !== null && error !== void 0 ? error : "The Container is disposed");
|
|
512
|
+
this.off("contextChanged", contextChangedHandler);
|
|
513
|
+
};
|
|
514
|
+
this.once("contextChanged", contextChangedHandler);
|
|
515
|
+
this.once("disposed", disposedHandler);
|
|
516
|
+
});
|
|
517
|
+
// The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
|
|
518
|
+
// should have set this._context; making sure.
|
|
519
|
+
(0, common_utils_1.assert)(this._context !== undefined, 0x5a2 /* Context still not defined after contextChanged event */);
|
|
520
|
+
}
|
|
521
|
+
// Disable lint rule for the sake of more complete stack traces
|
|
522
|
+
// eslint-disable-next-line no-return-await
|
|
523
|
+
return await ((_b = (_a = this._context).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
524
|
+
}
|
|
467
525
|
/**
|
|
468
526
|
* Retrieves the quorum associated with the document
|
|
469
527
|
*/
|
|
470
528
|
getQuorum() {
|
|
471
529
|
return this.protocolHandler.quorum;
|
|
472
530
|
}
|
|
531
|
+
dispose(error) {
|
|
532
|
+
this._deltaManager.close(error, true /* doDispose */);
|
|
533
|
+
this.verifyClosed();
|
|
534
|
+
}
|
|
473
535
|
close(error) {
|
|
474
536
|
// 1. Ensure that close sequence is exactly the same no matter if it's initiated by host or by DeltaManager
|
|
475
537
|
// 2. We need to ensure that we deliver disconnect event to runtime properly. See connectionStateChanged
|
|
476
538
|
// handler. We only deliver events if container fully loaded. Transitioning from "loading" ->
|
|
477
539
|
// "closing" will lose that info (can also solve by tracking extra state).
|
|
478
540
|
this._deltaManager.close(error);
|
|
541
|
+
this.verifyClosed();
|
|
542
|
+
}
|
|
543
|
+
verifyClosed() {
|
|
479
544
|
(0, common_utils_1.assert)(this.connectionState === connectionState_1.ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
|
|
480
|
-
(0, common_utils_1.assert)(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
|
|
545
|
+
(0, common_utils_1.assert)(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
|
|
481
546
|
}
|
|
482
547
|
closeCore(error) {
|
|
483
548
|
var _a, _b, _c;
|
|
@@ -487,15 +552,19 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
487
552
|
try {
|
|
488
553
|
// Raise event first, to ensure we capture _lifecycleState before transition.
|
|
489
554
|
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
555
|
+
// Log generic events instead of error events if container is in loading state, as most errors are not really FF errors
|
|
556
|
+
// which can pollute telemetry for real bugs
|
|
490
557
|
this.mc.logger.sendTelemetryEvent({
|
|
491
558
|
eventName: "ContainerClose",
|
|
492
|
-
category:
|
|
559
|
+
category: this._lifecycleState !== "loading" && error !== undefined
|
|
560
|
+
? "error"
|
|
561
|
+
: "generic",
|
|
493
562
|
}, error);
|
|
494
563
|
this._lifecycleState = "closing";
|
|
495
564
|
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
496
565
|
this.connectionStateHandler.dispose();
|
|
497
566
|
(_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
498
|
-
this.
|
|
567
|
+
this.storageAdapter.dispose();
|
|
499
568
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
500
569
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
501
570
|
// Driver need to ensure all caches are cleared on critical errors
|
|
@@ -505,7 +574,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
505
574
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
|
|
506
575
|
}
|
|
507
576
|
this.emit("closed", error);
|
|
508
|
-
this.removeAllListeners();
|
|
509
577
|
if (this.visibilityEventHandler !== undefined) {
|
|
510
578
|
document.removeEventListener("visibilitychange", this.visibilityEventHandler);
|
|
511
579
|
}
|
|
@@ -514,22 +582,69 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
514
582
|
this._lifecycleState = "closed";
|
|
515
583
|
}
|
|
516
584
|
}
|
|
585
|
+
disposeCore(error) {
|
|
586
|
+
var _a, _b, _c;
|
|
587
|
+
(0, common_utils_1.assert)(!this._disposed, 0x54c /* Container already disposed */);
|
|
588
|
+
this._disposed = true;
|
|
589
|
+
try {
|
|
590
|
+
// Ensure that we raise all key events even if one of these throws
|
|
591
|
+
try {
|
|
592
|
+
// Raise event first, to ensure we capture _lifecycleState before transition.
|
|
593
|
+
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
594
|
+
this.mc.logger.sendTelemetryEvent({
|
|
595
|
+
eventName: "ContainerDispose",
|
|
596
|
+
category: "generic",
|
|
597
|
+
}, error);
|
|
598
|
+
// ! Progressing from "closed" to "disposing" is not allowed
|
|
599
|
+
if (this._lifecycleState !== "closed") {
|
|
600
|
+
this._lifecycleState = "disposing";
|
|
601
|
+
}
|
|
602
|
+
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
603
|
+
this.connectionStateHandler.dispose();
|
|
604
|
+
(_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
605
|
+
this.storageAdapter.dispose();
|
|
606
|
+
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
607
|
+
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
608
|
+
// Driver need to ensure all caches are cleared on critical errors
|
|
609
|
+
(_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
|
|
610
|
+
}
|
|
611
|
+
catch (exception) {
|
|
612
|
+
this.mc.logger.sendErrorEvent({ eventName: "ContainerDisposeException" }, exception);
|
|
613
|
+
}
|
|
614
|
+
this.emit("disposed", error);
|
|
615
|
+
this.removeAllListeners();
|
|
616
|
+
if (this.visibilityEventHandler !== undefined) {
|
|
617
|
+
document.removeEventListener("visibilitychange", this.visibilityEventHandler);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
finally {
|
|
621
|
+
this._lifecycleState = "disposed";
|
|
622
|
+
}
|
|
623
|
+
}
|
|
517
624
|
closeAndGetPendingLocalState() {
|
|
518
625
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
519
626
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
520
627
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
628
|
+
if (!this.offlineLoadEnabled) {
|
|
629
|
+
throw new container_utils_1.UsageError("Can't get pending local state unless offline load is enabled");
|
|
630
|
+
}
|
|
521
631
|
(0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
|
|
522
632
|
(0, common_utils_1.assert)(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
523
633
|
(0, common_utils_1.assert)(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
|
|
524
634
|
(0, common_utils_1.assert)(this._protocolHandler.attributes.term !== undefined, 0x37e /* Must have a valid protocol handler instance */);
|
|
635
|
+
(0, common_utils_1.assert)(!!this.baseSnapshot, "no base snapshot");
|
|
636
|
+
(0, common_utils_1.assert)(!!this.baseSnapshotBlobs, "no snapshot blobs");
|
|
525
637
|
const pendingState = {
|
|
526
638
|
pendingRuntimeState: this.context.getPendingLocalState(),
|
|
639
|
+
baseSnapshot: this.baseSnapshot,
|
|
640
|
+
snapshotBlobs: this.baseSnapshotBlobs,
|
|
641
|
+
savedOps: this.savedOps,
|
|
527
642
|
url: this.resolvedUrl.url,
|
|
528
|
-
protocol: this.protocolHandler.getProtocolState(),
|
|
529
643
|
term: this._protocolHandler.attributes.term,
|
|
530
644
|
clientId: this.clientId,
|
|
531
645
|
};
|
|
532
646
|
this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
|
|
647
|
+
// Only close here as method name suggests
|
|
533
648
|
this.close();
|
|
534
649
|
return JSON.stringify(pendingState);
|
|
535
650
|
}
|
|
@@ -541,13 +656,18 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
541
656
|
const appSummary = this.context.createSummary();
|
|
542
657
|
const protocolSummary = this.captureProtocolSummary();
|
|
543
658
|
const combinedSummary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
|
|
544
|
-
if (this.loader.services.detachedBlobStorage &&
|
|
545
|
-
|
|
659
|
+
if (this.loader.services.detachedBlobStorage &&
|
|
660
|
+
this.loader.services.detachedBlobStorage.size > 0) {
|
|
661
|
+
combinedSummary.tree[".hasAttachmentBlobs"] = {
|
|
662
|
+
type: protocol_definitions_1.SummaryType.Blob,
|
|
663
|
+
content: "true",
|
|
664
|
+
};
|
|
546
665
|
}
|
|
547
666
|
return JSON.stringify(combinedSummary);
|
|
548
667
|
}
|
|
549
668
|
async attach(request) {
|
|
550
669
|
await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
|
|
670
|
+
var _a;
|
|
551
671
|
if (this._lifecycleState !== "loaded") {
|
|
552
672
|
// pre-0.58 error message: containerNotValidForAttach
|
|
553
673
|
throw new container_utils_1.UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
|
|
@@ -556,8 +676,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
556
676
|
(0, common_utils_1.assert)(this._attachState === container_definitions_1.AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
|
|
557
677
|
this.attachStarted = true;
|
|
558
678
|
// If attachment blobs were uploaded in detached state we will go through a different attach flow
|
|
559
|
-
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
|
|
560
|
-
|
|
679
|
+
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined &&
|
|
680
|
+
this.loader.services.detachedBlobStorage.size > 0;
|
|
561
681
|
try {
|
|
562
682
|
(0, common_utils_1.assert)(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
|
|
563
683
|
let summary;
|
|
@@ -572,7 +692,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
572
692
|
// starting to attach the container to storage.
|
|
573
693
|
// Also, this should only be fired in detached container.
|
|
574
694
|
this._attachState = container_definitions_1.AttachState.Attaching;
|
|
575
|
-
this.
|
|
695
|
+
this.emit("attaching");
|
|
696
|
+
if (this.offlineLoadEnabled) {
|
|
697
|
+
const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
|
|
698
|
+
this.baseSnapshot = snapshot;
|
|
699
|
+
this.baseSnapshotBlobs =
|
|
700
|
+
(0, containerStorageAdapter_1.getBlobContentsFromTreeWithBlobContents)(snapshot);
|
|
701
|
+
}
|
|
576
702
|
}
|
|
577
703
|
// Actually go and create the resolved document
|
|
578
704
|
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
@@ -586,7 +712,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
586
712
|
const resolvedUrl = this.service.resolvedUrl;
|
|
587
713
|
(0, driver_utils_1.ensureFluidResolvedUrl)(resolvedUrl);
|
|
588
714
|
this._resolvedUrl = resolvedUrl;
|
|
589
|
-
await this.
|
|
715
|
+
await this.storageAdapter.connectToService(this.service);
|
|
590
716
|
if (hasAttachmentBlobs) {
|
|
591
717
|
// upload blobs to storage
|
|
592
718
|
(0, common_utils_1.assert)(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
|
|
@@ -595,10 +721,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
595
721
|
const redirectTable = new Map();
|
|
596
722
|
// if new blobs are added while uploading, upload them too
|
|
597
723
|
while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
|
|
598
|
-
const newIds = this.loader.services.detachedBlobStorage
|
|
724
|
+
const newIds = this.loader.services.detachedBlobStorage
|
|
725
|
+
.getBlobIds()
|
|
726
|
+
.filter((id) => !redirectTable.has(id));
|
|
599
727
|
for (const id of newIds) {
|
|
600
728
|
const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
|
|
601
|
-
const response = await this.
|
|
729
|
+
const response = await this.storageAdapter.createBlob(blob);
|
|
602
730
|
redirectTable.set(id, response.id);
|
|
603
731
|
}
|
|
604
732
|
}
|
|
@@ -607,8 +735,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
607
735
|
const protocolSummary = this.captureProtocolSummary();
|
|
608
736
|
summary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
|
|
609
737
|
this._attachState = container_definitions_1.AttachState.Attaching;
|
|
610
|
-
this.
|
|
611
|
-
|
|
738
|
+
this.emit("attaching");
|
|
739
|
+
if (this.offlineLoadEnabled) {
|
|
740
|
+
const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
|
|
741
|
+
this.baseSnapshot = snapshot;
|
|
742
|
+
this.baseSnapshotBlobs =
|
|
743
|
+
(0, containerStorageAdapter_1.getBlobContentsFromTreeWithBlobContents)(snapshot);
|
|
744
|
+
}
|
|
745
|
+
await this.storageAdapter.uploadSummaryWithContext(summary, {
|
|
612
746
|
referenceSequenceNumber: 0,
|
|
613
747
|
ackHandle: undefined,
|
|
614
748
|
proposalHandle: undefined,
|
|
@@ -617,7 +751,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
617
751
|
this._attachState = container_definitions_1.AttachState.Attached;
|
|
618
752
|
this.emit("attached");
|
|
619
753
|
if (!this.closed) {
|
|
620
|
-
this.resumeInternal({
|
|
754
|
+
this.resumeInternal({
|
|
755
|
+
fetchOpsFromStorage: false,
|
|
756
|
+
reason: "createDetached",
|
|
757
|
+
});
|
|
621
758
|
}
|
|
622
759
|
}
|
|
623
760
|
catch (error) {
|
|
@@ -628,6 +765,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
628
765
|
newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
|
|
629
766
|
}
|
|
630
767
|
this.close(newError);
|
|
768
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, newError);
|
|
631
769
|
throw newError;
|
|
632
770
|
}
|
|
633
771
|
}, { start: true, end: true, cancel: "generic" });
|
|
@@ -716,26 +854,30 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
716
854
|
throw new Error("Proposed code details should be greater than the current");
|
|
717
855
|
}
|
|
718
856
|
}
|
|
719
|
-
return this.protocolHandler.quorum
|
|
857
|
+
return this.protocolHandler.quorum
|
|
858
|
+
.propose("code", codeDetails)
|
|
720
859
|
.then(() => true)
|
|
721
860
|
.catch(() => false);
|
|
722
861
|
}
|
|
723
862
|
async processCodeProposal() {
|
|
863
|
+
var _a;
|
|
724
864
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
725
865
|
await Promise.all([
|
|
726
866
|
this.deltaManager.inbound.pause(),
|
|
727
|
-
this.deltaManager.inboundSignal.pause()
|
|
867
|
+
this.deltaManager.inboundSignal.pause(),
|
|
728
868
|
]);
|
|
729
|
-
if ((await this.context.satisfies(codeDetails) === true)
|
|
869
|
+
if ((await this.context.satisfies(codeDetails)) === true) {
|
|
730
870
|
this.deltaManager.inbound.resume();
|
|
731
871
|
this.deltaManager.inboundSignal.resume();
|
|
732
872
|
return;
|
|
733
873
|
}
|
|
734
874
|
// pre-0.58 error message: existingContextDoesNotSatisfyIncomingProposal
|
|
735
|
-
|
|
875
|
+
const error = new container_utils_1.GenericError("Existing context does not satisfy incoming proposal");
|
|
876
|
+
this.close(error);
|
|
877
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
736
878
|
}
|
|
737
879
|
async getVersion(version) {
|
|
738
|
-
const versions = await this.
|
|
880
|
+
const versions = await this.storageAdapter.getVersions(version, 1);
|
|
739
881
|
return versions[0];
|
|
740
882
|
}
|
|
741
883
|
recordConnectStartTime() {
|
|
@@ -757,6 +899,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
757
899
|
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
758
900
|
*/
|
|
759
901
|
async load(specifiedVersion, loadMode, pendingLocalState) {
|
|
902
|
+
var _a;
|
|
760
903
|
if (this._resolvedUrl === undefined) {
|
|
761
904
|
throw new Error("Attempting to load without a resolved url");
|
|
762
905
|
}
|
|
@@ -770,32 +913,48 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
770
913
|
// connections to same file) in two ways:
|
|
771
914
|
// A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
|
|
772
915
|
// B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
|
|
773
|
-
const connectionArgs = {
|
|
916
|
+
const connectionArgs = {
|
|
917
|
+
reason: "DocumentOpen",
|
|
918
|
+
mode: "write",
|
|
919
|
+
fetchOpsFromStorage: false,
|
|
920
|
+
};
|
|
774
921
|
// Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
|
|
775
922
|
// DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
|
|
776
|
-
if (loadMode.deltaConnection === undefined) {
|
|
923
|
+
if (loadMode.deltaConnection === undefined && !pendingLocalState) {
|
|
777
924
|
this.connectToDeltaStream(connectionArgs);
|
|
778
925
|
}
|
|
779
926
|
if (!pendingLocalState) {
|
|
780
|
-
await this.
|
|
927
|
+
await this.storageAdapter.connectToService(this.service);
|
|
781
928
|
}
|
|
782
929
|
else {
|
|
783
930
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
784
|
-
this.
|
|
931
|
+
this.storageAdapter.connectToService(this.service).catch((error) => {
|
|
932
|
+
var _a;
|
|
933
|
+
this.close(error);
|
|
934
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
935
|
+
});
|
|
785
936
|
}
|
|
786
937
|
this._attachState = container_definitions_1.AttachState.Attached;
|
|
787
938
|
// Fetch specified snapshot.
|
|
788
939
|
const { snapshot, versionId } = pendingLocalState === undefined
|
|
789
940
|
? await this.fetchSnapshotTree(specifiedVersion)
|
|
790
|
-
: { snapshot:
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
941
|
+
: { snapshot: pendingLocalState.baseSnapshot, versionId: undefined };
|
|
942
|
+
if (pendingLocalState) {
|
|
943
|
+
this.baseSnapshot = pendingLocalState.baseSnapshot;
|
|
944
|
+
this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
(0, common_utils_1.assert)(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
|
|
948
|
+
if (this.offlineLoadEnabled) {
|
|
949
|
+
this.baseSnapshot = snapshot;
|
|
950
|
+
// Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
|
|
951
|
+
this.baseSnapshotBlobs = await (0, containerStorageAdapter_1.getBlobContentsFromTree)(snapshot, this.storage);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
|
|
955
|
+
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
956
|
+
const sequenceNumber = (_a = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.savedOps[pendingLocalState.savedOps.length - 1]) === null || _a === void 0 ? void 0 : _a.sequenceNumber;
|
|
957
|
+
const dmAttributes = sequenceNumber !== undefined ? Object.assign(Object.assign({}, attributes), { sequenceNumber }) : attributes;
|
|
799
958
|
let opsBeforeReturnP;
|
|
800
959
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
801
960
|
// Kick off any ops fetching if required.
|
|
@@ -803,44 +962,52 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
803
962
|
case undefined:
|
|
804
963
|
// Start prefetch, but not set opsBeforeReturnP - boot is not blocked by it!
|
|
805
964
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
806
|
-
this.attachDeltaManagerOpHandler(
|
|
965
|
+
this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
|
|
807
966
|
break;
|
|
808
967
|
case "cached":
|
|
809
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
968
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
|
|
810
969
|
break;
|
|
811
970
|
case "all":
|
|
812
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
971
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "all");
|
|
813
972
|
break;
|
|
814
973
|
default:
|
|
815
974
|
(0, common_utils_1.unreachableCase)(loadMode.opsBeforeReturn);
|
|
816
975
|
}
|
|
817
976
|
// ...load in the existing quorum
|
|
818
977
|
// Initialize the protocol handler
|
|
819
|
-
|
|
820
|
-
await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot);
|
|
821
|
-
}
|
|
822
|
-
else {
|
|
823
|
-
this.initializeProtocolState(attributes, {
|
|
824
|
-
members: pendingLocalState.protocol.members,
|
|
825
|
-
proposals: pendingLocalState.protocol.proposals,
|
|
826
|
-
values: pendingLocalState.protocol.values,
|
|
827
|
-
});
|
|
828
|
-
}
|
|
978
|
+
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
|
|
829
979
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
830
980
|
await this.instantiateContext(true, // existing
|
|
831
981
|
codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
|
|
982
|
+
// replay saved ops
|
|
983
|
+
if (pendingLocalState) {
|
|
984
|
+
for (const message of pendingLocalState.savedOps) {
|
|
985
|
+
this.processRemoteMessage(message);
|
|
986
|
+
// allow runtime to apply stashed ops at this op's sequence number
|
|
987
|
+
await this.context.notifyOpReplay(message);
|
|
988
|
+
}
|
|
989
|
+
pendingLocalState.savedOps = [];
|
|
990
|
+
// now set clientId to stashed clientId so live ops are correctly processed as local
|
|
991
|
+
(0, common_utils_1.assert)(this.clientId === undefined, "Unexpected clientId when setting stashed clientId");
|
|
992
|
+
this._clientId = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId;
|
|
993
|
+
}
|
|
832
994
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
833
995
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
834
996
|
if (!this.closed) {
|
|
835
997
|
if (opsBeforeReturnP !== undefined) {
|
|
836
998
|
this._deltaManager.inbound.resume();
|
|
837
|
-
await
|
|
838
|
-
await
|
|
999
|
+
await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "WaitOps" }, async () => opsBeforeReturnP);
|
|
1000
|
+
await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "WaitOpProcessing" }, async () => this._deltaManager.inbound.waitTillProcessingDone());
|
|
839
1001
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
840
1002
|
this._deltaManager.inbound.pause();
|
|
841
1003
|
}
|
|
842
1004
|
switch (loadMode.deltaConnection) {
|
|
843
1005
|
case undefined:
|
|
1006
|
+
if (pendingLocalState) {
|
|
1007
|
+
// connect to delta stream now since we did not before
|
|
1008
|
+
this.connectToDeltaStream(connectionArgs);
|
|
1009
|
+
}
|
|
1010
|
+
// intentional fallthrough
|
|
844
1011
|
case "delayed":
|
|
845
1012
|
(0, common_utils_1.assert)(this.inboundQueuePausedFromInit, 0x346 /* inboundQueuePausedFromInit should be true */);
|
|
846
1013
|
this.inboundQueuePausedFromInit = false;
|
|
@@ -890,16 +1057,17 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
890
1057
|
}
|
|
891
1058
|
async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
|
|
892
1059
|
if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
|
|
893
|
-
(0, common_utils_1.assert)(!!this.loader.services.detachedBlobStorage &&
|
|
1060
|
+
(0, common_utils_1.assert)(!!this.loader.services.detachedBlobStorage &&
|
|
1061
|
+
this.loader.services.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
|
|
894
1062
|
delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
|
|
895
1063
|
}
|
|
896
1064
|
const snapshotTree = (0, utils_1.getSnapshotTreeFromSerializedContainer)(detachedContainerSnapshot);
|
|
897
|
-
this.
|
|
898
|
-
const attributes = await this.getDocumentAttributes(this.
|
|
1065
|
+
this.storageAdapter.loadSnapshotForRehydratingContainer(snapshotTree);
|
|
1066
|
+
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshotTree);
|
|
899
1067
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
900
1068
|
// Initialize the protocol handler
|
|
901
1069
|
const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshotTree);
|
|
902
|
-
const qValues = await (0, driver_utils_1.readAndParse)(this.
|
|
1070
|
+
const qValues = await (0, driver_utils_1.readAndParse)(this.storageAdapter, baseTree.blobs.quorumValues);
|
|
903
1071
|
const codeDetails = (0, quorum_1.getCodeDetailsFromQuorumValues)(qValues);
|
|
904
1072
|
this.initializeProtocolState(attributes, {
|
|
905
1073
|
members: [],
|
|
@@ -937,11 +1105,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
937
1105
|
};
|
|
938
1106
|
if (snapshot !== undefined) {
|
|
939
1107
|
const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshot);
|
|
940
|
-
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] =
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1108
|
+
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] =
|
|
1109
|
+
await Promise.all([
|
|
1110
|
+
(0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumMembers),
|
|
1111
|
+
(0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumProposals),
|
|
1112
|
+
(0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumValues),
|
|
1113
|
+
]);
|
|
945
1114
|
}
|
|
946
1115
|
this.initializeProtocolState(attributes, quorumSnapshot);
|
|
947
1116
|
}
|
|
@@ -968,7 +1137,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
968
1137
|
});
|
|
969
1138
|
}
|
|
970
1139
|
this.processCodeProposal().catch((error) => {
|
|
971
|
-
|
|
1140
|
+
var _a;
|
|
1141
|
+
const normalizedError = (0, telemetry_utils_1.normalizeError)(error);
|
|
1142
|
+
this.close(normalizedError);
|
|
1143
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, normalizedError);
|
|
972
1144
|
throw error;
|
|
973
1145
|
});
|
|
974
1146
|
}
|
|
@@ -1025,7 +1197,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1025
1197
|
if (this.clientDetailsOverride !== undefined) {
|
|
1026
1198
|
(0, merge_1.default)(client.details, this.clientDetailsOverride);
|
|
1027
1199
|
}
|
|
1028
|
-
client.details.environment = [
|
|
1200
|
+
client.details.environment = [
|
|
1201
|
+
client.details.environment,
|
|
1202
|
+
` loaderVersion:${packageVersion_1.pkgVersion}`,
|
|
1203
|
+
].join(";");
|
|
1029
1204
|
return client;
|
|
1030
1205
|
}
|
|
1031
1206
|
/**
|
|
@@ -1035,8 +1210,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1035
1210
|
* If it's not true, runtime is not in position to send ops.
|
|
1036
1211
|
*/
|
|
1037
1212
|
activeConnection() {
|
|
1038
|
-
return this.connectionState === connectionState_1.ConnectionState.Connected &&
|
|
1039
|
-
this.connectionMode === "write";
|
|
1213
|
+
return (this.connectionState === connectionState_1.ConnectionState.Connected && this.connectionMode === "write");
|
|
1040
1214
|
}
|
|
1041
1215
|
createDeltaManager() {
|
|
1042
1216
|
const serviceProvider = () => this.service;
|
|
@@ -1053,7 +1227,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1053
1227
|
deltaManager.on("disconnect", (reason) => {
|
|
1054
1228
|
var _a;
|
|
1055
1229
|
(_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
|
|
1056
|
-
this.
|
|
1230
|
+
if (!this.closed) {
|
|
1231
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
1232
|
+
}
|
|
1057
1233
|
});
|
|
1058
1234
|
deltaManager.on("throttled", (warning) => {
|
|
1059
1235
|
const warn = warning;
|
|
@@ -1071,6 +1247,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1071
1247
|
deltaManager.on("closed", (error) => {
|
|
1072
1248
|
this.closeCore(error);
|
|
1073
1249
|
});
|
|
1250
|
+
deltaManager.on("disposed", (error) => {
|
|
1251
|
+
this.disposeCore(error);
|
|
1252
|
+
});
|
|
1074
1253
|
return deltaManager;
|
|
1075
1254
|
}
|
|
1076
1255
|
async attachDeltaManagerOpHandler(attributes, prefetchType) {
|
|
@@ -1098,7 +1277,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1098
1277
|
}
|
|
1099
1278
|
else {
|
|
1100
1279
|
if (value === connectionState_1.ConnectionState.Connected) {
|
|
1101
|
-
durationFromDisconnected =
|
|
1280
|
+
durationFromDisconnected =
|
|
1281
|
+
time - this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected];
|
|
1102
1282
|
durationFromDisconnected = telemetry_utils_1.TelemetryLogger.formatTick(durationFromDisconnected);
|
|
1103
1283
|
}
|
|
1104
1284
|
else {
|
|
@@ -1141,32 +1321,39 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1141
1321
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1142
1322
|
(0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId, disconnectedReason);
|
|
1143
1323
|
if (logOpsOnReconnect) {
|
|
1144
|
-
this.mc.logger.sendTelemetryEvent({
|
|
1324
|
+
this.mc.logger.sendTelemetryEvent({
|
|
1325
|
+
eventName: "OpsSentOnReconnect",
|
|
1326
|
+
count: this.messageCountAfterDisconnection,
|
|
1327
|
+
});
|
|
1145
1328
|
}
|
|
1146
1329
|
}
|
|
1147
1330
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1148
1331
|
submitContainerMessage(type, contents, batch, metadata) {
|
|
1332
|
+
var _a;
|
|
1149
1333
|
switch (type) {
|
|
1150
1334
|
case protocol_definitions_1.MessageType.Operation:
|
|
1151
1335
|
return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
|
|
1152
1336
|
case protocol_definitions_1.MessageType.Summarize:
|
|
1153
1337
|
return this.submitSummaryMessage(contents);
|
|
1154
|
-
default:
|
|
1155
|
-
|
|
1338
|
+
default: {
|
|
1339
|
+
const newError = new container_utils_1.GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type });
|
|
1340
|
+
this.close(newError);
|
|
1341
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, newError);
|
|
1156
1342
|
return -1;
|
|
1343
|
+
}
|
|
1157
1344
|
}
|
|
1158
1345
|
}
|
|
1159
1346
|
/** @returns clientSequenceNumber of last message in a batch */
|
|
1160
|
-
submitBatch(batch) {
|
|
1347
|
+
submitBatch(batch, referenceSequenceNumber) {
|
|
1161
1348
|
let clientSequenceNumber = -1;
|
|
1162
1349
|
for (const message of batch) {
|
|
1163
1350
|
clientSequenceNumber = this.submitMessage(protocol_definitions_1.MessageType.Operation, message.contents, true, // batch
|
|
1164
|
-
message.metadata, message.compression);
|
|
1351
|
+
message.metadata, message.compression, referenceSequenceNumber);
|
|
1165
1352
|
}
|
|
1166
1353
|
this._deltaManager.flush();
|
|
1167
1354
|
return clientSequenceNumber;
|
|
1168
1355
|
}
|
|
1169
|
-
submitSummaryMessage(summary) {
|
|
1356
|
+
submitSummaryMessage(summary, referenceSequenceNumber) {
|
|
1170
1357
|
// github #6451: this is only needed for staging so the server
|
|
1171
1358
|
// know when the protocol tree is included
|
|
1172
1359
|
// this can be removed once all clients send
|
|
@@ -1174,11 +1361,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1174
1361
|
if (summary.details === undefined) {
|
|
1175
1362
|
summary.details = {};
|
|
1176
1363
|
}
|
|
1177
|
-
summary.details.includesProtocolTree =
|
|
1178
|
-
|
|
1179
|
-
return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */);
|
|
1364
|
+
summary.details.includesProtocolTree = this.storageAdapter.summarizeProtocolTree;
|
|
1365
|
+
return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
|
|
1180
1366
|
}
|
|
1181
|
-
submitMessage(type, contents, batch, metadata, compression) {
|
|
1367
|
+
submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
|
|
1182
1368
|
var _a;
|
|
1183
1369
|
if (this.connectionState !== connectionState_1.ConnectionState.Connected) {
|
|
1184
1370
|
this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
|
|
@@ -1186,9 +1372,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1186
1372
|
}
|
|
1187
1373
|
this.messageCountAfterDisconnection += 1;
|
|
1188
1374
|
(_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
|
|
1189
|
-
return this._deltaManager.submit(type, contents, batch, metadata, compression);
|
|
1375
|
+
return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
|
|
1190
1376
|
}
|
|
1191
1377
|
processRemoteMessage(message) {
|
|
1378
|
+
if (this.offlineLoadEnabled) {
|
|
1379
|
+
this.savedOps.push(message);
|
|
1380
|
+
}
|
|
1192
1381
|
const local = this.clientId === message.clientId;
|
|
1193
1382
|
// Allow the protocol handler to process the message
|
|
1194
1383
|
const result = this.protocolHandler.processMessage(message, local);
|
|
@@ -1234,10 +1423,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1234
1423
|
const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
|
|
1235
1424
|
if (version === undefined && specifiedVersion !== undefined) {
|
|
1236
1425
|
// We should have a defined version to load from if specified version requested
|
|
1237
|
-
this.mc.logger.sendErrorEvent({
|
|
1426
|
+
this.mc.logger.sendErrorEvent({
|
|
1427
|
+
eventName: "NoVersionFoundWhenSpecified",
|
|
1428
|
+
id: specifiedVersion,
|
|
1429
|
+
});
|
|
1238
1430
|
}
|
|
1239
1431
|
this._loadedFromVersion = version;
|
|
1240
|
-
const snapshot = (_a = await this.
|
|
1432
|
+
const snapshot = (_a = (await this.storageAdapter.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
|
|
1241
1433
|
if (snapshot === undefined && version !== undefined) {
|
|
1242
1434
|
this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
|
|
1243
1435
|
}
|
|
@@ -1256,7 +1448,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
|
|
|
1256
1448
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1257
1449
|
// are set. Global requests will still go directly to the loader
|
|
1258
1450
|
const loader = new loader_1.RelativeLoader(this, this.loader);
|
|
1259
|
-
this._context = await containerContext_1.ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new deltaManagerProxy_1.DeltaManagerProxy(this._deltaManager), new quorum_1.QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp) => this.submitSummaryMessage(summaryOp), (batch) => this.submitBatch(batch), (message) => this.submitSignal(message), (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
|
|
1451
|
+
this._context = await containerContext_1.ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new deltaManagerProxy_1.DeltaManagerProxy(this._deltaManager), new quorum_1.QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (message) => this.submitSignal(message), (error) => { var _a; return (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error); }, (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
|
|
1260
1452
|
this.emit("contextChanged", codeDetails);
|
|
1261
1453
|
}
|
|
1262
1454
|
updateDirtyContainerState(dirty) {
|