@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/lib/container.js
CHANGED
|
@@ -7,10 +7,10 @@ import merge from "lodash/merge";
|
|
|
7
7
|
import { v4 as uuid } from "uuid";
|
|
8
8
|
import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
|
|
9
9
|
import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
|
|
10
|
-
import { GenericError, UsageError
|
|
11
|
-
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, } from "@fluidframework/driver-utils";
|
|
10
|
+
import { GenericError, UsageError } from "@fluidframework/container-utils";
|
|
11
|
+
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, isCombinedAppAndProtocolSummary, } from "@fluidframework/driver-utils";
|
|
12
12
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
13
|
-
import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName,
|
|
13
|
+
import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
|
|
14
14
|
import { Audience } from "./audience";
|
|
15
15
|
import { ContainerContext } from "./containerContext";
|
|
16
16
|
import { ReconnectMode, getPackageName } from "./contracts";
|
|
@@ -18,14 +18,14 @@ import { DeltaManager } from "./deltaManager";
|
|
|
18
18
|
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
19
19
|
import { RelativeLoader } from "./loader";
|
|
20
20
|
import { pkgVersion } from "./packageVersion";
|
|
21
|
-
import { ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
22
|
-
import { createConnectionStateHandler
|
|
21
|
+
import { ContainerStorageAdapter, getBlobContentsFromTree, getBlobContentsFromTreeWithBlobContents, } from "./containerStorageAdapter";
|
|
22
|
+
import { createConnectionStateHandler } from "./connectionStateHandler";
|
|
23
23
|
import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
|
|
24
|
-
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy } from "./quorum";
|
|
24
|
+
import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy, } from "./quorum";
|
|
25
25
|
import { CollabWindowTracker } from "./collabWindowTracker";
|
|
26
26
|
import { ConnectionManager } from "./connectionManager";
|
|
27
27
|
import { ConnectionState } from "./connectionState";
|
|
28
|
-
import { ProtocolHandler
|
|
28
|
+
import { ProtocolHandler } from "./protocol";
|
|
29
29
|
const detachedContainerRefSeqNumber = 0;
|
|
30
30
|
const dirtyContainerEvent = "dirty";
|
|
31
31
|
const savedContainerEvent = "saved";
|
|
@@ -65,8 +65,8 @@ export async function waitContainerToCatchUp(container) {
|
|
|
65
65
|
// Waiting for "connected" state in either case gets us at least to our own Join op
|
|
66
66
|
// which is a reasonable approximation of "caught up"
|
|
67
67
|
const waitForOps = () => {
|
|
68
|
-
assert(container.connectionState === ConnectionState.CatchingUp
|
|
69
|
-
|
|
68
|
+
assert(container.connectionState === ConnectionState.CatchingUp ||
|
|
69
|
+
container.connectionState === ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
|
|
70
70
|
const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
|
|
71
71
|
const connectionOpSeqNumber = deltaManager.lastKnownSeqNumber;
|
|
72
72
|
assert(deltaManager.lastSequenceNumber <= connectionOpSeqNumber, 0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
|
|
@@ -106,7 +106,7 @@ const getCodeProposal =
|
|
|
106
106
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
107
107
|
(quorum) => { var _a; return (_a = quorum.get("code")) !== null && _a !== void 0 ? _a : quorum.get("code2"); };
|
|
108
108
|
/**
|
|
109
|
-
* Helper function to report to telemetry cases where operation takes longer than expected (
|
|
109
|
+
* Helper function to report to telemetry cases where operation takes longer than expected (200ms)
|
|
110
110
|
* @param logger - logger to use
|
|
111
111
|
* @param eventName - event name
|
|
112
112
|
* @param action - functor to call and measure
|
|
@@ -119,9 +119,15 @@ export async function ReportIfTooLong(logger, eventName, action) {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
const summarizerClientType = "summarizer";
|
|
122
|
+
/**
|
|
123
|
+
* @deprecated - In the next release Container will no longer be exported, IContainer should be used in its place.
|
|
124
|
+
*/
|
|
122
125
|
export class Container extends EventEmitterWithErrorHandling {
|
|
126
|
+
/**
|
|
127
|
+
* @internal
|
|
128
|
+
*/
|
|
123
129
|
constructor(loader, config, protocolHandlerBuilder) {
|
|
124
|
-
var _a, _b;
|
|
130
|
+
var _a, _b, _c;
|
|
125
131
|
super((name, error) => {
|
|
126
132
|
this.mc.logger.sendErrorEvent({
|
|
127
133
|
eventName: "ContainerEventHandlerException",
|
|
@@ -133,6 +139,21 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
133
139
|
// Tells if container can reconnect on losing fist connection
|
|
134
140
|
// If false, container gets closed on loss of connection.
|
|
135
141
|
this._canReconnect = true;
|
|
142
|
+
/**
|
|
143
|
+
* Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
|
|
144
|
+
*
|
|
145
|
+
* States are allowed to progress to further states:
|
|
146
|
+
* "loading" - "loaded" - "closing" - "disposing" - "closed" - "disposed"
|
|
147
|
+
*
|
|
148
|
+
* For example, moving from "closed" to "disposing" is not allowed since it is an earlier state.
|
|
149
|
+
*
|
|
150
|
+
* loading: Container has been created, but is not yet in normal/loaded state
|
|
151
|
+
* loaded: Container is in normal/loaded state
|
|
152
|
+
* closing: Container has started closing process (for re-entrancy prevention)
|
|
153
|
+
* disposing: Container has started disposing process (for re-entrancy prevention)
|
|
154
|
+
* closed: Container has closed
|
|
155
|
+
* disposed: Container has been disposed
|
|
156
|
+
*/
|
|
136
157
|
this._lifecycleState = "loading";
|
|
137
158
|
this._attachState = AttachState.Detached;
|
|
138
159
|
/** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
|
|
@@ -142,7 +163,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
142
163
|
this.messageCountAfterDisconnection = 0;
|
|
143
164
|
this.attachStarted = false;
|
|
144
165
|
this._dirtyContainer = false;
|
|
166
|
+
this.savedOps = [];
|
|
145
167
|
this.setAutoReconnectTime = performance.now();
|
|
168
|
+
this._disposed = false;
|
|
146
169
|
this.clientDetailsOverride = config.clientDetailsOverride;
|
|
147
170
|
this._resolvedUrl = config.resolvedUrl;
|
|
148
171
|
if (config.canReconnect !== undefined) {
|
|
@@ -179,15 +202,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
179
202
|
dmLastMsqSeqNumber: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.sequenceNumber; },
|
|
180
203
|
dmLastMsqSeqTimestamp: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.timestamp; },
|
|
181
204
|
dmLastMsqSeqClientId: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId; },
|
|
205
|
+
dmLastMsgClientSeq: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientSequenceNumber; },
|
|
182
206
|
connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
|
|
183
207
|
},
|
|
184
208
|
});
|
|
185
209
|
// Prefix all events in this file with container-loader
|
|
186
210
|
this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
|
|
187
|
-
|
|
188
|
-
this.options = Object.assign(Object.assign({}, this.loader.services.options), { summarizeProtocolTree });
|
|
211
|
+
this.options = Object.assign({}, this.loader.services.options);
|
|
189
212
|
this._deltaManager = this.createDeltaManager();
|
|
190
|
-
this._clientId = (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId;
|
|
191
213
|
this.connectionStateHandler = createConnectionStateHandler({
|
|
192
214
|
logger: this.mc.logger,
|
|
193
215
|
connectionStateChanged: (value, oldState, reason) => {
|
|
@@ -196,17 +218,23 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
196
218
|
}
|
|
197
219
|
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
198
220
|
if (this._lifecycleState === "loaded") {
|
|
199
|
-
this.propagateConnectionState(false /* initial transition */, value === ConnectionState.Disconnected
|
|
221
|
+
this.propagateConnectionState(false /* initial transition */, value === ConnectionState.Disconnected
|
|
222
|
+
? reason
|
|
223
|
+
: undefined /* disconnectedReason */);
|
|
200
224
|
}
|
|
201
225
|
},
|
|
202
226
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
203
227
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
204
|
-
logConnectionIssue: (eventName, details) => {
|
|
228
|
+
logConnectionIssue: (eventName, category, details) => {
|
|
205
229
|
const mode = this.connectionMode;
|
|
206
230
|
// We get here when socket does not receive any ops on "write" connection, including
|
|
207
|
-
// its own join op.
|
|
231
|
+
// its own join op.
|
|
232
|
+
// Report issues only if we already loaded container - op processing is paused while container is loading,
|
|
233
|
+
// so we always time-out processing of join op in cases where fetching snapshot takes a minute.
|
|
234
|
+
// It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
|
|
208
235
|
this._deltaManager.logConnectionIssue(Object.assign({ eventName,
|
|
209
|
-
mode,
|
|
236
|
+
mode, category: this._lifecycleState === "loading" ? "generic" : category, duration: performance.now() -
|
|
237
|
+
this.connectionTransitionTimes[ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
|
|
210
238
|
// If this is "write" connection, it took too long to receive join op. But in most cases that's due
|
|
211
239
|
// to very slow op fetches and we will eventually get there.
|
|
212
240
|
// For "read" connections, we get here due to self join signal not arriving on time. We will need to
|
|
@@ -220,13 +248,20 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
220
248
|
this.connect();
|
|
221
249
|
}
|
|
222
250
|
},
|
|
223
|
-
}, this.deltaManager,
|
|
251
|
+
}, this.deltaManager, (_a = config.serializedContainerState) === null || _a === void 0 ? void 0 : _a.clientId);
|
|
224
252
|
this.on(savedContainerEvent, () => {
|
|
225
253
|
this.connectionStateHandler.containerSaved();
|
|
226
254
|
});
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
255
|
+
// We expose our storage publicly, so it's possible others may call uploadSummaryWithContext() with a
|
|
256
|
+
// non-combined summary tree (in particular, ContainerRuntime.submitSummary). We'll intercept those calls
|
|
257
|
+
// using this callback and fix them up.
|
|
258
|
+
const addProtocolSummaryIfMissing = (summaryTree) => isCombinedAppAndProtocolSummary(summaryTree) === true
|
|
259
|
+
? summaryTree
|
|
260
|
+
: combineAppAndProtocolSummary(summaryTree, this.captureProtocolSummary());
|
|
261
|
+
// Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
|
|
262
|
+
// Even if not forced on via this flag, combined summaries may still be enabled by service policy.
|
|
263
|
+
const forceEnableSummarizeProtocolTree = (_b = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2")) !== null && _b !== void 0 ? _b : this.loader.services.options.summarizeProtocolTree;
|
|
264
|
+
this.storageAdapter = new ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, (_c = config.serializedContainerState) === null || _c === void 0 ? void 0 : _c.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
|
|
230
265
|
const isDomAvailable = typeof document === "object" &&
|
|
231
266
|
document !== null &&
|
|
232
267
|
typeof document.addEventListener === "function" &&
|
|
@@ -240,48 +275,17 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
240
275
|
}
|
|
241
276
|
else {
|
|
242
277
|
// settimeout so this will hopefully fire after disconnect event if being hidden caused it
|
|
243
|
-
setTimeout(() => {
|
|
278
|
+
setTimeout(() => {
|
|
279
|
+
this.lastVisible = undefined;
|
|
280
|
+
}, 0);
|
|
244
281
|
}
|
|
245
282
|
};
|
|
246
283
|
document.addEventListener("visibilitychange", this.visibilityEventHandler);
|
|
247
284
|
}
|
|
248
|
-
// We observed that most users of platform do not check Container.connected event on load, causing bugs.
|
|
249
|
-
// As such, we are raising events when new listener pops up.
|
|
250
|
-
// Note that we can raise both "disconnected" & "connect" events at the same time,
|
|
251
|
-
// if we are in connecting stage.
|
|
252
|
-
this.on("newListener", (event, listener) => {
|
|
253
|
-
// Fire events on the end of JS turn, giving a chance for caller to be in consistent state.
|
|
254
|
-
Promise.resolve().then(() => {
|
|
255
|
-
switch (event) {
|
|
256
|
-
case dirtyContainerEvent:
|
|
257
|
-
if (this._dirtyContainer) {
|
|
258
|
-
listener();
|
|
259
|
-
}
|
|
260
|
-
break;
|
|
261
|
-
case savedContainerEvent:
|
|
262
|
-
if (!this._dirtyContainer) {
|
|
263
|
-
listener();
|
|
264
|
-
}
|
|
265
|
-
break;
|
|
266
|
-
case connectedEventName:
|
|
267
|
-
if (this.connected) {
|
|
268
|
-
listener(this.clientId);
|
|
269
|
-
}
|
|
270
|
-
break;
|
|
271
|
-
case disconnectedEventName:
|
|
272
|
-
if (!this.connected) {
|
|
273
|
-
listener();
|
|
274
|
-
}
|
|
275
|
-
break;
|
|
276
|
-
default:
|
|
277
|
-
}
|
|
278
|
-
}).catch((error) => {
|
|
279
|
-
this.mc.logger.sendErrorEvent({ eventName: "RaiseConnectedEventError" }, error);
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
285
|
}
|
|
283
286
|
/**
|
|
284
287
|
* Load an existing container.
|
|
288
|
+
* @internal
|
|
285
289
|
*/
|
|
286
290
|
static async load(loader, loadOptions, pendingLocalState, protocolHandlerBuilder) {
|
|
287
291
|
const container = new Container(loader, {
|
|
@@ -303,7 +307,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
303
307
|
reject(err !== null && err !== void 0 ? err : new GenericError("Container closed without error during load"));
|
|
304
308
|
};
|
|
305
309
|
container.on("closed", onClosed);
|
|
306
|
-
container
|
|
310
|
+
container
|
|
311
|
+
.load(version, mode, pendingLocalState)
|
|
307
312
|
.finally(() => {
|
|
308
313
|
container.removeListener("closed", onClosed);
|
|
309
314
|
})
|
|
@@ -352,10 +357,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
352
357
|
}
|
|
353
358
|
}
|
|
354
359
|
get closed() {
|
|
355
|
-
return (this._lifecycleState === "closing" ||
|
|
360
|
+
return (this._lifecycleState === "closing" ||
|
|
361
|
+
this._lifecycleState === "closed" ||
|
|
362
|
+
this._lifecycleState === "disposing" ||
|
|
363
|
+
this._lifecycleState === "disposed");
|
|
356
364
|
}
|
|
357
365
|
get storage() {
|
|
358
|
-
return this.
|
|
366
|
+
return this.storageAdapter;
|
|
359
367
|
}
|
|
360
368
|
get context() {
|
|
361
369
|
if (this._context === undefined) {
|
|
@@ -369,8 +377,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
369
377
|
}
|
|
370
378
|
return this._protocolHandler;
|
|
371
379
|
}
|
|
372
|
-
get connectionMode() {
|
|
373
|
-
|
|
380
|
+
get connectionMode() {
|
|
381
|
+
return this._deltaManager.connectionManager.connectionMode;
|
|
382
|
+
}
|
|
383
|
+
get IFluidRouter() {
|
|
384
|
+
return this;
|
|
385
|
+
}
|
|
374
386
|
get resolvedUrl() {
|
|
375
387
|
return this._resolvedUrl;
|
|
376
388
|
}
|
|
@@ -422,6 +434,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
422
434
|
get clientDetails() {
|
|
423
435
|
return this._deltaManager.clientDetails;
|
|
424
436
|
}
|
|
437
|
+
get offlineLoadEnabled() {
|
|
438
|
+
var _a;
|
|
439
|
+
// summarizer will not have any pending state we want to save
|
|
440
|
+
return (((_a = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad")) !== null && _a !== void 0 ? _a : false) &&
|
|
441
|
+
this.clientDetails.capabilities.interactive);
|
|
442
|
+
}
|
|
425
443
|
/**
|
|
426
444
|
* Get the code details that are currently specified for the container.
|
|
427
445
|
* @returns The current code details if any are specified, undefined if none are specified.
|
|
@@ -452,24 +470,71 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
452
470
|
get isDirty() {
|
|
453
471
|
return this._dirtyContainer;
|
|
454
472
|
}
|
|
455
|
-
get serviceFactory() {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
get
|
|
473
|
+
get serviceFactory() {
|
|
474
|
+
return this.loader.services.documentServiceFactory;
|
|
475
|
+
}
|
|
476
|
+
get urlResolver() {
|
|
477
|
+
return this.loader.services.urlResolver;
|
|
478
|
+
}
|
|
479
|
+
get scope() {
|
|
480
|
+
return this.loader.services.scope;
|
|
481
|
+
}
|
|
482
|
+
get codeLoader() {
|
|
483
|
+
return this.loader.services.codeLoader;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint}
|
|
487
|
+
*/
|
|
488
|
+
async getEntryPoint() {
|
|
489
|
+
var _a, _b;
|
|
490
|
+
// Only the disposing/disposed lifecycle states should prevent access to the entryPoint; closing/closed should still
|
|
491
|
+
// allow it since they mean a kind of read-only state for the Container.
|
|
492
|
+
// Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
|
|
493
|
+
if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
|
|
494
|
+
throw new UsageError("The container is disposing or disposed");
|
|
495
|
+
}
|
|
496
|
+
while (this._context === undefined) {
|
|
497
|
+
await new Promise((resolve, reject) => {
|
|
498
|
+
const contextChangedHandler = () => {
|
|
499
|
+
resolve();
|
|
500
|
+
this.off("disposed", disposedHandler);
|
|
501
|
+
};
|
|
502
|
+
const disposedHandler = (error) => {
|
|
503
|
+
reject(error !== null && error !== void 0 ? error : "The Container is disposed");
|
|
504
|
+
this.off("contextChanged", contextChangedHandler);
|
|
505
|
+
};
|
|
506
|
+
this.once("contextChanged", contextChangedHandler);
|
|
507
|
+
this.once("disposed", disposedHandler);
|
|
508
|
+
});
|
|
509
|
+
// The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
|
|
510
|
+
// should have set this._context; making sure.
|
|
511
|
+
assert(this._context !== undefined, 0x5a2 /* Context still not defined after contextChanged event */);
|
|
512
|
+
}
|
|
513
|
+
// Disable lint rule for the sake of more complete stack traces
|
|
514
|
+
// eslint-disable-next-line no-return-await
|
|
515
|
+
return await ((_b = (_a = this._context).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
516
|
+
}
|
|
459
517
|
/**
|
|
460
518
|
* Retrieves the quorum associated with the document
|
|
461
519
|
*/
|
|
462
520
|
getQuorum() {
|
|
463
521
|
return this.protocolHandler.quorum;
|
|
464
522
|
}
|
|
523
|
+
dispose(error) {
|
|
524
|
+
this._deltaManager.close(error, true /* doDispose */);
|
|
525
|
+
this.verifyClosed();
|
|
526
|
+
}
|
|
465
527
|
close(error) {
|
|
466
528
|
// 1. Ensure that close sequence is exactly the same no matter if it's initiated by host or by DeltaManager
|
|
467
529
|
// 2. We need to ensure that we deliver disconnect event to runtime properly. See connectionStateChanged
|
|
468
530
|
// handler. We only deliver events if container fully loaded. Transitioning from "loading" ->
|
|
469
531
|
// "closing" will lose that info (can also solve by tracking extra state).
|
|
470
532
|
this._deltaManager.close(error);
|
|
533
|
+
this.verifyClosed();
|
|
534
|
+
}
|
|
535
|
+
verifyClosed() {
|
|
471
536
|
assert(this.connectionState === ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
|
|
472
|
-
assert(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
|
|
537
|
+
assert(this._lifecycleState === "closed" || this._lifecycleState === "disposed", 0x314 /* Container properly closed */);
|
|
473
538
|
}
|
|
474
539
|
closeCore(error) {
|
|
475
540
|
var _a, _b, _c;
|
|
@@ -479,15 +544,19 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
479
544
|
try {
|
|
480
545
|
// Raise event first, to ensure we capture _lifecycleState before transition.
|
|
481
546
|
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
547
|
+
// Log generic events instead of error events if container is in loading state, as most errors are not really FF errors
|
|
548
|
+
// which can pollute telemetry for real bugs
|
|
482
549
|
this.mc.logger.sendTelemetryEvent({
|
|
483
550
|
eventName: "ContainerClose",
|
|
484
|
-
category:
|
|
551
|
+
category: this._lifecycleState !== "loading" && error !== undefined
|
|
552
|
+
? "error"
|
|
553
|
+
: "generic",
|
|
485
554
|
}, error);
|
|
486
555
|
this._lifecycleState = "closing";
|
|
487
556
|
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
488
557
|
this.connectionStateHandler.dispose();
|
|
489
558
|
(_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
490
|
-
this.
|
|
559
|
+
this.storageAdapter.dispose();
|
|
491
560
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
492
561
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
493
562
|
// Driver need to ensure all caches are cleared on critical errors
|
|
@@ -497,7 +566,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
497
566
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
|
|
498
567
|
}
|
|
499
568
|
this.emit("closed", error);
|
|
500
|
-
this.removeAllListeners();
|
|
501
569
|
if (this.visibilityEventHandler !== undefined) {
|
|
502
570
|
document.removeEventListener("visibilitychange", this.visibilityEventHandler);
|
|
503
571
|
}
|
|
@@ -506,22 +574,69 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
506
574
|
this._lifecycleState = "closed";
|
|
507
575
|
}
|
|
508
576
|
}
|
|
577
|
+
disposeCore(error) {
|
|
578
|
+
var _a, _b, _c;
|
|
579
|
+
assert(!this._disposed, 0x54c /* Container already disposed */);
|
|
580
|
+
this._disposed = true;
|
|
581
|
+
try {
|
|
582
|
+
// Ensure that we raise all key events even if one of these throws
|
|
583
|
+
try {
|
|
584
|
+
// Raise event first, to ensure we capture _lifecycleState before transition.
|
|
585
|
+
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
586
|
+
this.mc.logger.sendTelemetryEvent({
|
|
587
|
+
eventName: "ContainerDispose",
|
|
588
|
+
category: "generic",
|
|
589
|
+
}, error);
|
|
590
|
+
// ! Progressing from "closed" to "disposing" is not allowed
|
|
591
|
+
if (this._lifecycleState !== "closed") {
|
|
592
|
+
this._lifecycleState = "disposing";
|
|
593
|
+
}
|
|
594
|
+
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
595
|
+
this.connectionStateHandler.dispose();
|
|
596
|
+
(_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
597
|
+
this.storageAdapter.dispose();
|
|
598
|
+
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
599
|
+
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
600
|
+
// Driver need to ensure all caches are cleared on critical errors
|
|
601
|
+
(_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
|
|
602
|
+
}
|
|
603
|
+
catch (exception) {
|
|
604
|
+
this.mc.logger.sendErrorEvent({ eventName: "ContainerDisposeException" }, exception);
|
|
605
|
+
}
|
|
606
|
+
this.emit("disposed", error);
|
|
607
|
+
this.removeAllListeners();
|
|
608
|
+
if (this.visibilityEventHandler !== undefined) {
|
|
609
|
+
document.removeEventListener("visibilitychange", this.visibilityEventHandler);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
finally {
|
|
613
|
+
this._lifecycleState = "disposed";
|
|
614
|
+
}
|
|
615
|
+
}
|
|
509
616
|
closeAndGetPendingLocalState() {
|
|
510
617
|
// runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
|
|
511
618
|
// container at the same time we get pending state, otherwise this container could reconnect and resubmit with
|
|
512
619
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
620
|
+
if (!this.offlineLoadEnabled) {
|
|
621
|
+
throw new UsageError("Can't get pending local state unless offline load is enabled");
|
|
622
|
+
}
|
|
513
623
|
assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
|
|
514
624
|
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
515
625
|
assert(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
|
|
516
626
|
assert(this._protocolHandler.attributes.term !== undefined, 0x37e /* Must have a valid protocol handler instance */);
|
|
627
|
+
assert(!!this.baseSnapshot, "no base snapshot");
|
|
628
|
+
assert(!!this.baseSnapshotBlobs, "no snapshot blobs");
|
|
517
629
|
const pendingState = {
|
|
518
630
|
pendingRuntimeState: this.context.getPendingLocalState(),
|
|
631
|
+
baseSnapshot: this.baseSnapshot,
|
|
632
|
+
snapshotBlobs: this.baseSnapshotBlobs,
|
|
633
|
+
savedOps: this.savedOps,
|
|
519
634
|
url: this.resolvedUrl.url,
|
|
520
|
-
protocol: this.protocolHandler.getProtocolState(),
|
|
521
635
|
term: this._protocolHandler.attributes.term,
|
|
522
636
|
clientId: this.clientId,
|
|
523
637
|
};
|
|
524
638
|
this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
|
|
639
|
+
// Only close here as method name suggests
|
|
525
640
|
this.close();
|
|
526
641
|
return JSON.stringify(pendingState);
|
|
527
642
|
}
|
|
@@ -533,13 +648,18 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
533
648
|
const appSummary = this.context.createSummary();
|
|
534
649
|
const protocolSummary = this.captureProtocolSummary();
|
|
535
650
|
const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
536
|
-
if (this.loader.services.detachedBlobStorage &&
|
|
537
|
-
|
|
651
|
+
if (this.loader.services.detachedBlobStorage &&
|
|
652
|
+
this.loader.services.detachedBlobStorage.size > 0) {
|
|
653
|
+
combinedSummary.tree[".hasAttachmentBlobs"] = {
|
|
654
|
+
type: SummaryType.Blob,
|
|
655
|
+
content: "true",
|
|
656
|
+
};
|
|
538
657
|
}
|
|
539
658
|
return JSON.stringify(combinedSummary);
|
|
540
659
|
}
|
|
541
660
|
async attach(request) {
|
|
542
661
|
await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Attach" }, async () => {
|
|
662
|
+
var _a;
|
|
543
663
|
if (this._lifecycleState !== "loaded") {
|
|
544
664
|
// pre-0.58 error message: containerNotValidForAttach
|
|
545
665
|
throw new UsageError(`The Container is not in a valid state for attach [${this._lifecycleState}]`);
|
|
@@ -548,8 +668,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
548
668
|
assert(this._attachState === AttachState.Detached && !this.attachStarted, 0x205 /* "attach() called more than once" */);
|
|
549
669
|
this.attachStarted = true;
|
|
550
670
|
// If attachment blobs were uploaded in detached state we will go through a different attach flow
|
|
551
|
-
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined
|
|
552
|
-
|
|
671
|
+
const hasAttachmentBlobs = this.loader.services.detachedBlobStorage !== undefined &&
|
|
672
|
+
this.loader.services.detachedBlobStorage.size > 0;
|
|
553
673
|
try {
|
|
554
674
|
assert(this.deltaManager.inbound.length === 0, 0x0d6 /* "Inbound queue should be empty when attaching" */);
|
|
555
675
|
let summary;
|
|
@@ -564,7 +684,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
564
684
|
// starting to attach the container to storage.
|
|
565
685
|
// Also, this should only be fired in detached container.
|
|
566
686
|
this._attachState = AttachState.Attaching;
|
|
567
|
-
this.
|
|
687
|
+
this.emit("attaching");
|
|
688
|
+
if (this.offlineLoadEnabled) {
|
|
689
|
+
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
690
|
+
this.baseSnapshot = snapshot;
|
|
691
|
+
this.baseSnapshotBlobs =
|
|
692
|
+
getBlobContentsFromTreeWithBlobContents(snapshot);
|
|
693
|
+
}
|
|
568
694
|
}
|
|
569
695
|
// Actually go and create the resolved document
|
|
570
696
|
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
@@ -578,7 +704,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
578
704
|
const resolvedUrl = this.service.resolvedUrl;
|
|
579
705
|
ensureFluidResolvedUrl(resolvedUrl);
|
|
580
706
|
this._resolvedUrl = resolvedUrl;
|
|
581
|
-
await this.
|
|
707
|
+
await this.storageAdapter.connectToService(this.service);
|
|
582
708
|
if (hasAttachmentBlobs) {
|
|
583
709
|
// upload blobs to storage
|
|
584
710
|
assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
|
|
@@ -587,10 +713,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
587
713
|
const redirectTable = new Map();
|
|
588
714
|
// if new blobs are added while uploading, upload them too
|
|
589
715
|
while (redirectTable.size < this.loader.services.detachedBlobStorage.size) {
|
|
590
|
-
const newIds = this.loader.services.detachedBlobStorage
|
|
716
|
+
const newIds = this.loader.services.detachedBlobStorage
|
|
717
|
+
.getBlobIds()
|
|
718
|
+
.filter((id) => !redirectTable.has(id));
|
|
591
719
|
for (const id of newIds) {
|
|
592
720
|
const blob = await this.loader.services.detachedBlobStorage.readBlob(id);
|
|
593
|
-
const response = await this.
|
|
721
|
+
const response = await this.storageAdapter.createBlob(blob);
|
|
594
722
|
redirectTable.set(id, response.id);
|
|
595
723
|
}
|
|
596
724
|
}
|
|
@@ -599,8 +727,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
599
727
|
const protocolSummary = this.captureProtocolSummary();
|
|
600
728
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
601
729
|
this._attachState = AttachState.Attaching;
|
|
602
|
-
this.
|
|
603
|
-
|
|
730
|
+
this.emit("attaching");
|
|
731
|
+
if (this.offlineLoadEnabled) {
|
|
732
|
+
const snapshot = getSnapshotTreeFromSerializedContainer(summary);
|
|
733
|
+
this.baseSnapshot = snapshot;
|
|
734
|
+
this.baseSnapshotBlobs =
|
|
735
|
+
getBlobContentsFromTreeWithBlobContents(snapshot);
|
|
736
|
+
}
|
|
737
|
+
await this.storageAdapter.uploadSummaryWithContext(summary, {
|
|
604
738
|
referenceSequenceNumber: 0,
|
|
605
739
|
ackHandle: undefined,
|
|
606
740
|
proposalHandle: undefined,
|
|
@@ -609,7 +743,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
609
743
|
this._attachState = AttachState.Attached;
|
|
610
744
|
this.emit("attached");
|
|
611
745
|
if (!this.closed) {
|
|
612
|
-
this.resumeInternal({
|
|
746
|
+
this.resumeInternal({
|
|
747
|
+
fetchOpsFromStorage: false,
|
|
748
|
+
reason: "createDetached",
|
|
749
|
+
});
|
|
613
750
|
}
|
|
614
751
|
}
|
|
615
752
|
catch (error) {
|
|
@@ -620,6 +757,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
620
757
|
newError.addTelemetryProperties({ resolvedUrl: resolvedUrl.url });
|
|
621
758
|
}
|
|
622
759
|
this.close(newError);
|
|
760
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, newError);
|
|
623
761
|
throw newError;
|
|
624
762
|
}
|
|
625
763
|
}, { start: true, end: true, cancel: "generic" });
|
|
@@ -708,26 +846,30 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
708
846
|
throw new Error("Proposed code details should be greater than the current");
|
|
709
847
|
}
|
|
710
848
|
}
|
|
711
|
-
return this.protocolHandler.quorum
|
|
849
|
+
return this.protocolHandler.quorum
|
|
850
|
+
.propose("code", codeDetails)
|
|
712
851
|
.then(() => true)
|
|
713
852
|
.catch(() => false);
|
|
714
853
|
}
|
|
715
854
|
async processCodeProposal() {
|
|
855
|
+
var _a;
|
|
716
856
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
717
857
|
await Promise.all([
|
|
718
858
|
this.deltaManager.inbound.pause(),
|
|
719
|
-
this.deltaManager.inboundSignal.pause()
|
|
859
|
+
this.deltaManager.inboundSignal.pause(),
|
|
720
860
|
]);
|
|
721
|
-
if ((await this.context.satisfies(codeDetails) === true)
|
|
861
|
+
if ((await this.context.satisfies(codeDetails)) === true) {
|
|
722
862
|
this.deltaManager.inbound.resume();
|
|
723
863
|
this.deltaManager.inboundSignal.resume();
|
|
724
864
|
return;
|
|
725
865
|
}
|
|
726
866
|
// pre-0.58 error message: existingContextDoesNotSatisfyIncomingProposal
|
|
727
|
-
|
|
867
|
+
const error = new GenericError("Existing context does not satisfy incoming proposal");
|
|
868
|
+
this.close(error);
|
|
869
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
728
870
|
}
|
|
729
871
|
async getVersion(version) {
|
|
730
|
-
const versions = await this.
|
|
872
|
+
const versions = await this.storageAdapter.getVersions(version, 1);
|
|
731
873
|
return versions[0];
|
|
732
874
|
}
|
|
733
875
|
recordConnectStartTime() {
|
|
@@ -749,6 +891,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
749
891
|
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
750
892
|
*/
|
|
751
893
|
async load(specifiedVersion, loadMode, pendingLocalState) {
|
|
894
|
+
var _a;
|
|
752
895
|
if (this._resolvedUrl === undefined) {
|
|
753
896
|
throw new Error("Attempting to load without a resolved url");
|
|
754
897
|
}
|
|
@@ -762,32 +905,48 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
762
905
|
// connections to same file) in two ways:
|
|
763
906
|
// A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
|
|
764
907
|
// B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
|
|
765
|
-
const connectionArgs = {
|
|
908
|
+
const connectionArgs = {
|
|
909
|
+
reason: "DocumentOpen",
|
|
910
|
+
mode: "write",
|
|
911
|
+
fetchOpsFromStorage: false,
|
|
912
|
+
};
|
|
766
913
|
// Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
|
|
767
914
|
// DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
|
|
768
|
-
if (loadMode.deltaConnection === undefined) {
|
|
915
|
+
if (loadMode.deltaConnection === undefined && !pendingLocalState) {
|
|
769
916
|
this.connectToDeltaStream(connectionArgs);
|
|
770
917
|
}
|
|
771
918
|
if (!pendingLocalState) {
|
|
772
|
-
await this.
|
|
919
|
+
await this.storageAdapter.connectToService(this.service);
|
|
773
920
|
}
|
|
774
921
|
else {
|
|
775
922
|
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
776
|
-
this.
|
|
923
|
+
this.storageAdapter.connectToService(this.service).catch((error) => {
|
|
924
|
+
var _a;
|
|
925
|
+
this.close(error);
|
|
926
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
927
|
+
});
|
|
777
928
|
}
|
|
778
929
|
this._attachState = AttachState.Attached;
|
|
779
930
|
// Fetch specified snapshot.
|
|
780
931
|
const { snapshot, versionId } = pendingLocalState === undefined
|
|
781
932
|
? await this.fetchSnapshotTree(specifiedVersion)
|
|
782
|
-
: { snapshot:
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
933
|
+
: { snapshot: pendingLocalState.baseSnapshot, versionId: undefined };
|
|
934
|
+
if (pendingLocalState) {
|
|
935
|
+
this.baseSnapshot = pendingLocalState.baseSnapshot;
|
|
936
|
+
this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
assert(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
|
|
940
|
+
if (this.offlineLoadEnabled) {
|
|
941
|
+
this.baseSnapshot = snapshot;
|
|
942
|
+
// Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
|
|
943
|
+
this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storage);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
|
|
947
|
+
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
948
|
+
const sequenceNumber = (_a = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.savedOps[pendingLocalState.savedOps.length - 1]) === null || _a === void 0 ? void 0 : _a.sequenceNumber;
|
|
949
|
+
const dmAttributes = sequenceNumber !== undefined ? Object.assign(Object.assign({}, attributes), { sequenceNumber }) : attributes;
|
|
791
950
|
let opsBeforeReturnP;
|
|
792
951
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
793
952
|
// Kick off any ops fetching if required.
|
|
@@ -795,44 +954,52 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
795
954
|
case undefined:
|
|
796
955
|
// Start prefetch, but not set opsBeforeReturnP - boot is not blocked by it!
|
|
797
956
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
798
|
-
this.attachDeltaManagerOpHandler(
|
|
957
|
+
this.attachDeltaManagerOpHandler(dmAttributes, loadMode.deltaConnection !== "none" ? "all" : "none");
|
|
799
958
|
break;
|
|
800
959
|
case "cached":
|
|
801
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
960
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
|
|
802
961
|
break;
|
|
803
962
|
case "all":
|
|
804
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
963
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "all");
|
|
805
964
|
break;
|
|
806
965
|
default:
|
|
807
966
|
unreachableCase(loadMode.opsBeforeReturn);
|
|
808
967
|
}
|
|
809
968
|
// ...load in the existing quorum
|
|
810
969
|
// Initialize the protocol handler
|
|
811
|
-
|
|
812
|
-
await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot);
|
|
813
|
-
}
|
|
814
|
-
else {
|
|
815
|
-
this.initializeProtocolState(attributes, {
|
|
816
|
-
members: pendingLocalState.protocol.members,
|
|
817
|
-
proposals: pendingLocalState.protocol.proposals,
|
|
818
|
-
values: pendingLocalState.protocol.values,
|
|
819
|
-
});
|
|
820
|
-
}
|
|
970
|
+
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
|
|
821
971
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
822
972
|
await this.instantiateContext(true, // existing
|
|
823
973
|
codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
|
|
974
|
+
// replay saved ops
|
|
975
|
+
if (pendingLocalState) {
|
|
976
|
+
for (const message of pendingLocalState.savedOps) {
|
|
977
|
+
this.processRemoteMessage(message);
|
|
978
|
+
// allow runtime to apply stashed ops at this op's sequence number
|
|
979
|
+
await this.context.notifyOpReplay(message);
|
|
980
|
+
}
|
|
981
|
+
pendingLocalState.savedOps = [];
|
|
982
|
+
// now set clientId to stashed clientId so live ops are correctly processed as local
|
|
983
|
+
assert(this.clientId === undefined, "Unexpected clientId when setting stashed clientId");
|
|
984
|
+
this._clientId = pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId;
|
|
985
|
+
}
|
|
824
986
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
825
987
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
826
988
|
if (!this.closed) {
|
|
827
989
|
if (opsBeforeReturnP !== undefined) {
|
|
828
990
|
this._deltaManager.inbound.resume();
|
|
829
|
-
await
|
|
830
|
-
await
|
|
991
|
+
await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "WaitOps" }, async () => opsBeforeReturnP);
|
|
992
|
+
await PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "WaitOpProcessing" }, async () => this._deltaManager.inbound.waitTillProcessingDone());
|
|
831
993
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
832
994
|
this._deltaManager.inbound.pause();
|
|
833
995
|
}
|
|
834
996
|
switch (loadMode.deltaConnection) {
|
|
835
997
|
case undefined:
|
|
998
|
+
if (pendingLocalState) {
|
|
999
|
+
// connect to delta stream now since we did not before
|
|
1000
|
+
this.connectToDeltaStream(connectionArgs);
|
|
1001
|
+
}
|
|
1002
|
+
// intentional fallthrough
|
|
836
1003
|
case "delayed":
|
|
837
1004
|
assert(this.inboundQueuePausedFromInit, 0x346 /* inboundQueuePausedFromInit should be true */);
|
|
838
1005
|
this.inboundQueuePausedFromInit = false;
|
|
@@ -882,16 +1049,17 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
882
1049
|
}
|
|
883
1050
|
async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
|
|
884
1051
|
if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
|
|
885
|
-
assert(!!this.loader.services.detachedBlobStorage &&
|
|
1052
|
+
assert(!!this.loader.services.detachedBlobStorage &&
|
|
1053
|
+
this.loader.services.detachedBlobStorage.size > 0, 0x250 /* "serialized container with attachment blobs must be rehydrated with detached blob storage" */);
|
|
886
1054
|
delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
|
|
887
1055
|
}
|
|
888
1056
|
const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
|
|
889
|
-
this.
|
|
890
|
-
const attributes = await this.getDocumentAttributes(this.
|
|
1057
|
+
this.storageAdapter.loadSnapshotForRehydratingContainer(snapshotTree);
|
|
1058
|
+
const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshotTree);
|
|
891
1059
|
await this.attachDeltaManagerOpHandler(attributes);
|
|
892
1060
|
// Initialize the protocol handler
|
|
893
1061
|
const baseTree = getProtocolSnapshotTree(snapshotTree);
|
|
894
|
-
const qValues = await readAndParse(this.
|
|
1062
|
+
const qValues = await readAndParse(this.storageAdapter, baseTree.blobs.quorumValues);
|
|
895
1063
|
const codeDetails = getCodeDetailsFromQuorumValues(qValues);
|
|
896
1064
|
this.initializeProtocolState(attributes, {
|
|
897
1065
|
members: [],
|
|
@@ -929,11 +1097,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
929
1097
|
};
|
|
930
1098
|
if (snapshot !== undefined) {
|
|
931
1099
|
const baseTree = getProtocolSnapshotTree(snapshot);
|
|
932
|
-
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] =
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1100
|
+
[quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] =
|
|
1101
|
+
await Promise.all([
|
|
1102
|
+
readAndParse(storage, baseTree.blobs.quorumMembers),
|
|
1103
|
+
readAndParse(storage, baseTree.blobs.quorumProposals),
|
|
1104
|
+
readAndParse(storage, baseTree.blobs.quorumValues),
|
|
1105
|
+
]);
|
|
937
1106
|
}
|
|
938
1107
|
this.initializeProtocolState(attributes, quorumSnapshot);
|
|
939
1108
|
}
|
|
@@ -960,7 +1129,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
960
1129
|
});
|
|
961
1130
|
}
|
|
962
1131
|
this.processCodeProposal().catch((error) => {
|
|
963
|
-
|
|
1132
|
+
var _a;
|
|
1133
|
+
const normalizedError = normalizeError(error);
|
|
1134
|
+
this.close(normalizedError);
|
|
1135
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, normalizedError);
|
|
964
1136
|
throw error;
|
|
965
1137
|
});
|
|
966
1138
|
}
|
|
@@ -1017,7 +1189,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1017
1189
|
if (this.clientDetailsOverride !== undefined) {
|
|
1018
1190
|
merge(client.details, this.clientDetailsOverride);
|
|
1019
1191
|
}
|
|
1020
|
-
client.details.environment = [
|
|
1192
|
+
client.details.environment = [
|
|
1193
|
+
client.details.environment,
|
|
1194
|
+
` loaderVersion:${pkgVersion}`,
|
|
1195
|
+
].join(";");
|
|
1021
1196
|
return client;
|
|
1022
1197
|
}
|
|
1023
1198
|
/**
|
|
@@ -1027,8 +1202,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1027
1202
|
* If it's not true, runtime is not in position to send ops.
|
|
1028
1203
|
*/
|
|
1029
1204
|
activeConnection() {
|
|
1030
|
-
return this.connectionState === ConnectionState.Connected &&
|
|
1031
|
-
this.connectionMode === "write";
|
|
1205
|
+
return (this.connectionState === ConnectionState.Connected && this.connectionMode === "write");
|
|
1032
1206
|
}
|
|
1033
1207
|
createDeltaManager() {
|
|
1034
1208
|
const serviceProvider = () => this.service;
|
|
@@ -1045,7 +1219,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1045
1219
|
deltaManager.on("disconnect", (reason) => {
|
|
1046
1220
|
var _a;
|
|
1047
1221
|
(_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
|
|
1048
|
-
this.
|
|
1222
|
+
if (!this.closed) {
|
|
1223
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
1224
|
+
}
|
|
1049
1225
|
});
|
|
1050
1226
|
deltaManager.on("throttled", (warning) => {
|
|
1051
1227
|
const warn = warning;
|
|
@@ -1063,6 +1239,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1063
1239
|
deltaManager.on("closed", (error) => {
|
|
1064
1240
|
this.closeCore(error);
|
|
1065
1241
|
});
|
|
1242
|
+
deltaManager.on("disposed", (error) => {
|
|
1243
|
+
this.disposeCore(error);
|
|
1244
|
+
});
|
|
1066
1245
|
return deltaManager;
|
|
1067
1246
|
}
|
|
1068
1247
|
async attachDeltaManagerOpHandler(attributes, prefetchType) {
|
|
@@ -1090,7 +1269,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1090
1269
|
}
|
|
1091
1270
|
else {
|
|
1092
1271
|
if (value === ConnectionState.Connected) {
|
|
1093
|
-
durationFromDisconnected =
|
|
1272
|
+
durationFromDisconnected =
|
|
1273
|
+
time - this.connectionTransitionTimes[ConnectionState.Disconnected];
|
|
1094
1274
|
durationFromDisconnected = TelemetryLogger.formatTick(durationFromDisconnected);
|
|
1095
1275
|
}
|
|
1096
1276
|
else {
|
|
@@ -1133,32 +1313,39 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1133
1313
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1134
1314
|
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
|
|
1135
1315
|
if (logOpsOnReconnect) {
|
|
1136
|
-
this.mc.logger.sendTelemetryEvent({
|
|
1316
|
+
this.mc.logger.sendTelemetryEvent({
|
|
1317
|
+
eventName: "OpsSentOnReconnect",
|
|
1318
|
+
count: this.messageCountAfterDisconnection,
|
|
1319
|
+
});
|
|
1137
1320
|
}
|
|
1138
1321
|
}
|
|
1139
1322
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1140
1323
|
submitContainerMessage(type, contents, batch, metadata) {
|
|
1324
|
+
var _a;
|
|
1141
1325
|
switch (type) {
|
|
1142
1326
|
case MessageType.Operation:
|
|
1143
1327
|
return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
|
|
1144
1328
|
case MessageType.Summarize:
|
|
1145
1329
|
return this.submitSummaryMessage(contents);
|
|
1146
|
-
default:
|
|
1147
|
-
|
|
1330
|
+
default: {
|
|
1331
|
+
const newError = new GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type });
|
|
1332
|
+
this.close(newError);
|
|
1333
|
+
(_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, newError);
|
|
1148
1334
|
return -1;
|
|
1335
|
+
}
|
|
1149
1336
|
}
|
|
1150
1337
|
}
|
|
1151
1338
|
/** @returns clientSequenceNumber of last message in a batch */
|
|
1152
|
-
submitBatch(batch) {
|
|
1339
|
+
submitBatch(batch, referenceSequenceNumber) {
|
|
1153
1340
|
let clientSequenceNumber = -1;
|
|
1154
1341
|
for (const message of batch) {
|
|
1155
1342
|
clientSequenceNumber = this.submitMessage(MessageType.Operation, message.contents, true, // batch
|
|
1156
|
-
message.metadata, message.compression);
|
|
1343
|
+
message.metadata, message.compression, referenceSequenceNumber);
|
|
1157
1344
|
}
|
|
1158
1345
|
this._deltaManager.flush();
|
|
1159
1346
|
return clientSequenceNumber;
|
|
1160
1347
|
}
|
|
1161
|
-
submitSummaryMessage(summary) {
|
|
1348
|
+
submitSummaryMessage(summary, referenceSequenceNumber) {
|
|
1162
1349
|
// github #6451: this is only needed for staging so the server
|
|
1163
1350
|
// know when the protocol tree is included
|
|
1164
1351
|
// this can be removed once all clients send
|
|
@@ -1166,11 +1353,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1166
1353
|
if (summary.details === undefined) {
|
|
1167
1354
|
summary.details = {};
|
|
1168
1355
|
}
|
|
1169
|
-
summary.details.includesProtocolTree =
|
|
1170
|
-
|
|
1171
|
-
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */);
|
|
1356
|
+
summary.details.includesProtocolTree = this.storageAdapter.summarizeProtocolTree;
|
|
1357
|
+
return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */, undefined /* metadata */, undefined /* compression */, referenceSequenceNumber);
|
|
1172
1358
|
}
|
|
1173
|
-
submitMessage(type, contents, batch, metadata, compression) {
|
|
1359
|
+
submitMessage(type, contents, batch, metadata, compression, referenceSequenceNumber) {
|
|
1174
1360
|
var _a;
|
|
1175
1361
|
if (this.connectionState !== ConnectionState.Connected) {
|
|
1176
1362
|
this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
|
|
@@ -1178,9 +1364,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1178
1364
|
}
|
|
1179
1365
|
this.messageCountAfterDisconnection += 1;
|
|
1180
1366
|
(_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
|
|
1181
|
-
return this._deltaManager.submit(type, contents, batch, metadata, compression);
|
|
1367
|
+
return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
|
|
1182
1368
|
}
|
|
1183
1369
|
processRemoteMessage(message) {
|
|
1370
|
+
if (this.offlineLoadEnabled) {
|
|
1371
|
+
this.savedOps.push(message);
|
|
1372
|
+
}
|
|
1184
1373
|
const local = this.clientId === message.clientId;
|
|
1185
1374
|
// Allow the protocol handler to process the message
|
|
1186
1375
|
const result = this.protocolHandler.processMessage(message, local);
|
|
@@ -1226,10 +1415,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1226
1415
|
const version = await this.getVersion(specifiedVersion !== null && specifiedVersion !== void 0 ? specifiedVersion : null);
|
|
1227
1416
|
if (version === undefined && specifiedVersion !== undefined) {
|
|
1228
1417
|
// We should have a defined version to load from if specified version requested
|
|
1229
|
-
this.mc.logger.sendErrorEvent({
|
|
1418
|
+
this.mc.logger.sendErrorEvent({
|
|
1419
|
+
eventName: "NoVersionFoundWhenSpecified",
|
|
1420
|
+
id: specifiedVersion,
|
|
1421
|
+
});
|
|
1230
1422
|
}
|
|
1231
1423
|
this._loadedFromVersion = version;
|
|
1232
|
-
const snapshot = (_a = await this.
|
|
1424
|
+
const snapshot = (_a = (await this.storageAdapter.getSnapshotTree(version))) !== null && _a !== void 0 ? _a : undefined;
|
|
1233
1425
|
if (snapshot === undefined && version !== undefined) {
|
|
1234
1426
|
this.mc.logger.sendErrorEvent({ eventName: "getSnapshotTreeFailed", id: version.id });
|
|
1235
1427
|
}
|
|
@@ -1248,7 +1440,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1248
1440
|
// The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
|
|
1249
1441
|
// are set. Global requests will still go directly to the loader
|
|
1250
1442
|
const loader = new RelativeLoader(this, this.loader);
|
|
1251
|
-
this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp) => this.submitSummaryMessage(summaryOp), (batch) => this.submitBatch(batch), (message) => this.submitSignal(message), (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
|
|
1443
|
+
this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (message) => this.submitSignal(message), (error) => { var _a; return (_a = this.dispose) === null || _a === void 0 ? void 0 : _a.call(this, error); }, (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
|
|
1252
1444
|
this.emit("contextChanged", codeDetails);
|
|
1253
1445
|
}
|
|
1254
1446
|
updateDirtyContainerState(dirty) {
|