@fluidframework/container-loader 2.0.0-rc.3.0.2 → 2.0.0-rc.4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/api-report/container-loader.api.md +5 -1
- package/dist/attachment.d.ts +3 -2
- package/dist/attachment.d.ts.map +1 -1
- package/dist/attachment.js +5 -5
- package/dist/attachment.js.map +1 -1
- package/dist/audience.d.ts +6 -4
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +18 -3
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.d.ts +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/connectionManager.d.ts +7 -3
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +57 -38
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +31 -10
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +49 -36
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +22 -13
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +145 -117
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +3 -3
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +12 -3
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +42 -4
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +2 -2
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/debugLogger.d.ts +1 -2
- package/dist/debugLogger.d.ts.map +1 -1
- package/dist/debugLogger.js.map +1 -1
- package/dist/deltaManager.d.ts +5 -6
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +29 -24
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaQueue.d.ts +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js.map +1 -1
- package/dist/error.d.ts +1 -2
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +2 -2
- package/dist/loadPaused.d.ts +35 -0
- package/dist/loadPaused.d.ts.map +1 -0
- package/dist/loadPaused.js +115 -0
- package/dist/loadPaused.js.map +1 -0
- package/dist/loader.d.ts +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +1 -14
- package/dist/loader.js.map +1 -1
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -1
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.js +4 -4
- package/dist/location-redirection-utilities/resolveWithLocationRedirection.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 +3 -0
- package/dist/protocol.js.map +1 -1
- package/dist/public.d.ts +1 -1
- package/dist/retriableDocumentStorageService.d.ts +1 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/serializedStateManager.d.ts +89 -9
- package/dist/serializedStateManager.d.ts.map +1 -1
- package/dist/serializedStateManager.js +150 -34
- package/dist/serializedStateManager.js.map +1 -1
- package/dist/utils.d.ts +11 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +29 -14
- package/dist/utils.js.map +1 -1
- package/lib/attachment.d.ts +3 -2
- package/lib/attachment.d.ts.map +1 -1
- package/lib/attachment.js +5 -5
- package/lib/attachment.js.map +1 -1
- package/lib/audience.d.ts +6 -4
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js +19 -4
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/connectionManager.d.ts +7 -3
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +36 -17
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +31 -10
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +49 -36
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +22 -13
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +146 -118
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +3 -3
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +12 -3
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +42 -4
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +2 -2
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/debugLogger.d.ts +1 -2
- package/lib/debugLogger.d.ts.map +1 -1
- package/lib/debugLogger.js.map +1 -1
- package/lib/deltaManager.d.ts +5 -6
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +10 -5
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaQueue.d.ts +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js.map +1 -1
- package/lib/error.d.ts +1 -2
- package/lib/error.d.ts.map +1 -1
- package/lib/error.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +2 -2
- package/lib/loadPaused.d.ts +35 -0
- package/lib/loadPaused.d.ts.map +1 -0
- package/lib/loadPaused.js +111 -0
- package/lib/loadPaused.js.map +1 -0
- package/lib/loader.d.ts +1 -1
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +3 -16
- package/lib/loader.js.map +1 -1
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -1
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.js +1 -1
- package/lib/location-redirection-utilities/resolveWithLocationRedirection.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 +3 -0
- package/lib/protocol.js.map +1 -1
- package/lib/public.d.ts +1 -1
- package/lib/retriableDocumentStorageService.d.ts +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +1 -1
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/serializedStateManager.d.ts +89 -9
- package/lib/serializedStateManager.d.ts.map +1 -1
- package/lib/serializedStateManager.js +146 -30
- package/lib/serializedStateManager.js.map +1 -1
- package/lib/tsdoc-metadata.json +1 -1
- package/lib/utils.d.ts +11 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +15 -1
- package/lib/utils.js.map +1 -1
- package/package.json +24 -21
- package/src/attachment.ts +12 -13
- package/src/audience.ts +30 -9
- package/src/catchUpMonitor.ts +1 -1
- package/src/connectionManager.ts +45 -22
- package/src/connectionStateHandler.ts +78 -45
- package/src/container.ts +181 -160
- package/src/containerContext.ts +2 -2
- package/src/containerStorageAdapter.ts +61 -6
- package/src/contracts.ts +5 -4
- package/src/debugLogger.ts +1 -1
- package/src/deltaManager.ts +15 -8
- package/src/deltaQueue.ts +1 -1
- package/src/error.ts +1 -1
- package/src/index.ts +1 -0
- package/src/loadPaused.ts +140 -0
- package/src/loader.ts +6 -23
- package/src/location-redirection-utilities/resolveWithLocationRedirection.ts +1 -1
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +4 -0
- package/src/retriableDocumentStorageService.ts +5 -2
- package/src/serializedStateManager.ts +215 -48
- package/src/utils.ts +19 -1
package/lib/container.js
CHANGED
|
@@ -7,7 +7,7 @@ import { AttachState, } from "@fluidframework/container-definitions";
|
|
|
7
7
|
import { isFluidCodeDetails, } from "@fluidframework/container-definitions/internal";
|
|
8
8
|
import { LogLevel, } from "@fluidframework/core-interfaces";
|
|
9
9
|
import { assert, isPromiseLike, unreachableCase } from "@fluidframework/core-utils/internal";
|
|
10
|
-
import { MessageType2, OnlineStatus, isCombinedAppAndProtocolSummary, isInstanceOfISnapshot, isOnline, readAndParse, runWithRetry, } from "@fluidframework/driver-utils/internal";
|
|
10
|
+
import { getSnapshotTree, MessageType2, OnlineStatus, isCombinedAppAndProtocolSummary, isInstanceOfISnapshot, isOnline, readAndParse, runWithRetry, } from "@fluidframework/driver-utils/internal";
|
|
11
11
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
12
12
|
import { EventEmitterWithErrorHandling, GenericError, PerformanceEvent, UsageError, connectedEventName, createChildLogger, createChildMonitoringContext, formatTick, normalizeError, raiseConnectedEvent, wrapError, } from "@fluidframework/telemetry-utils/internal";
|
|
13
13
|
import structuredClone from "@ungap/structured-clone";
|
|
@@ -126,10 +126,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
126
126
|
* Load an existing container.
|
|
127
127
|
*/
|
|
128
128
|
static async load(loadProps, createProps) {
|
|
129
|
-
const { version, pendingLocalState, loadMode, resolvedUrl
|
|
129
|
+
const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
|
|
130
130
|
const container = new Container(createProps, loadProps);
|
|
131
|
-
|
|
132
|
-
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
|
|
131
|
+
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load", ...loadMode }, async (event) => new Promise((resolve, reject) => {
|
|
133
132
|
const defaultMode = { opsBeforeReturn: "cached" };
|
|
134
133
|
// if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
|
|
135
134
|
// to return container, so ignore this value and use undefined for opsBeforeReturn
|
|
@@ -142,12 +141,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
142
141
|
};
|
|
143
142
|
container.on("closed", onClosed);
|
|
144
143
|
container
|
|
145
|
-
.load(version, mode, resolvedUrl, pendingLocalState
|
|
144
|
+
.load(version, mode, resolvedUrl, pendingLocalState)
|
|
146
145
|
.finally(() => {
|
|
147
146
|
container.removeListener("closed", onClosed);
|
|
148
147
|
})
|
|
149
148
|
.then((props) => {
|
|
150
|
-
event.end({ ...props
|
|
149
|
+
event.end({ ...props });
|
|
151
150
|
resolve(container);
|
|
152
151
|
}, (error) => {
|
|
153
152
|
const err = normalizeError(error);
|
|
@@ -161,7 +160,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
161
160
|
container.dispose(err);
|
|
162
161
|
onClosed(err);
|
|
163
162
|
});
|
|
164
|
-
}), { start: true, end: true, cancel: "generic" }
|
|
163
|
+
}), { start: true, end: true, cancel: "generic" });
|
|
165
164
|
}
|
|
166
165
|
/**
|
|
167
166
|
* Create a new container in a detached state.
|
|
@@ -176,6 +175,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
176
175
|
/**
|
|
177
176
|
* Create a new container in a detached state that is initialized with a
|
|
178
177
|
* snapshot from a previous detached container.
|
|
178
|
+
* @param createProps - Config options for this new container instance
|
|
179
|
+
* @param snapshot - A stringified {@link IPendingDetachedContainerState}, e.g. generated via {@link serialize}
|
|
179
180
|
*/
|
|
180
181
|
static async rehydrateDetachedFromSnapshot(createProps, snapshot) {
|
|
181
182
|
const container = new Container(createProps);
|
|
@@ -189,14 +190,33 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
189
190
|
// It's conceivable the container could be closed when this is called
|
|
190
191
|
// Only transition states if currently loading
|
|
191
192
|
if (this._lifecycleState === "loading") {
|
|
192
|
-
// Propagate current connection state through the system.
|
|
193
|
-
this.propagateConnectionState(true /* initial transition */);
|
|
194
193
|
this._lifecycleState = "loaded";
|
|
194
|
+
// Connections transitions are delayed till we are loaded.
|
|
195
|
+
// This is done by holding ops and signals until the end of load sequence
|
|
196
|
+
// (calling this.handleDeltaConnectionArg() after setLoaded() call)
|
|
197
|
+
// If this assert fires, it means our logic managing connection flow is wrong, and the logic below is also wrong.
|
|
198
|
+
assert(this.connectionState !== ConnectionState.Connected, 0x969 /* not connected yet */);
|
|
199
|
+
// Propagate current connection state through the system.
|
|
200
|
+
const readonly = this.readOnlyInfo.readonly ?? false;
|
|
201
|
+
// This call does not look like needed any more, with delaying all connection-related events past loaded phase.
|
|
202
|
+
// Yet, there could be some customer code that would break if we do not deliver it.
|
|
203
|
+
// Will be removed in further PRs with proper changeset.
|
|
204
|
+
this.setContextConnectedState(false /* connected */, readonly);
|
|
205
|
+
// Deliver delayed calls to DeltaManager - we ignored "connect" events while loading.
|
|
206
|
+
const cm = this._deltaManager.connectionManager;
|
|
207
|
+
if (cm.connected) {
|
|
208
|
+
const details = cm.connectionDetails;
|
|
209
|
+
assert(details !== undefined, 0x96a /* should have details if connected */);
|
|
210
|
+
this.connectionStateHandler.receivedConnectEvent(details);
|
|
211
|
+
}
|
|
195
212
|
}
|
|
196
213
|
}
|
|
197
214
|
get closed() {
|
|
198
215
|
return (this._lifecycleState === "closing" || this._lifecycleState === "closed" || this.disposed);
|
|
199
216
|
}
|
|
217
|
+
get loaded() {
|
|
218
|
+
return this._lifecycleState === "loaded";
|
|
219
|
+
}
|
|
200
220
|
get disposed() {
|
|
201
221
|
return this._lifecycleState === "disposing" || this._lifecycleState === "disposed";
|
|
202
222
|
}
|
|
@@ -265,11 +285,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
265
285
|
return this.connectionStateHandler.connectionState === ConnectionState.Connected;
|
|
266
286
|
}
|
|
267
287
|
/**
|
|
268
|
-
*
|
|
269
|
-
*
|
|
288
|
+
* clientId of the latest connection. Changes only once client is connected, caught up and fully loaded.
|
|
289
|
+
* Changes to clientId are delayed through container loading sequence and delived once container is fully loaded.
|
|
290
|
+
* clientId does not reset on lost connection - old value persists until new connection is fully established.
|
|
270
291
|
*/
|
|
271
292
|
get clientId() {
|
|
272
|
-
return this.
|
|
293
|
+
return this.protocolHandler.audience.getSelf()?.clientId;
|
|
273
294
|
}
|
|
274
295
|
get isInteractiveClient() {
|
|
275
296
|
return this.deltaManager.clientDetails.capabilities.interactive;
|
|
@@ -333,6 +354,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
333
354
|
eventName: "ContainerEventHandlerException",
|
|
334
355
|
name: typeof name === "string" ? name : undefined,
|
|
335
356
|
}, error);
|
|
357
|
+
this.close(normalizeError(error));
|
|
336
358
|
});
|
|
337
359
|
/**
|
|
338
360
|
* Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
|
|
@@ -427,13 +449,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
427
449
|
throw normalizeErrorAndClose(error);
|
|
428
450
|
});
|
|
429
451
|
}
|
|
452
|
+
// If offline load is enabled, attachP will return the attach summary (in Snapshot format) so we can initialize SerializedStateManager
|
|
430
453
|
const snapshotWithBlobs = await attachP;
|
|
431
454
|
this.serializedStateManager.setInitialSnapshot(snapshotWithBlobs);
|
|
432
455
|
if (!this.closed) {
|
|
433
|
-
this.handleDeltaConnectionArg({
|
|
456
|
+
this.handleDeltaConnectionArg(attachProps?.deltaConnection, {
|
|
434
457
|
fetchOpsFromStorage: false,
|
|
435
458
|
reason: { text: "createDetached" },
|
|
436
|
-
}
|
|
459
|
+
});
|
|
437
460
|
}
|
|
438
461
|
}, { start: true, end: true, cancel: "generic" });
|
|
439
462
|
});
|
|
@@ -457,7 +480,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
457
480
|
const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
|
|
458
481
|
this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
|
|
459
482
|
const pendingLocalState = loadProps?.pendingLocalState;
|
|
460
|
-
this._clientId = pendingLocalState?.clientId;
|
|
461
483
|
this._canReconnect = canReconnect ?? true;
|
|
462
484
|
this.clientDetailsOverride = clientDetailsOverride;
|
|
463
485
|
this.urlResolver = urlResolver;
|
|
@@ -527,12 +549,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
527
549
|
this.connectionStateHandler = createConnectionStateHandler({
|
|
528
550
|
logger: this.mc.logger,
|
|
529
551
|
connectionStateChanged: (value, oldState, reason) => {
|
|
530
|
-
if (value === ConnectionState.Connected) {
|
|
531
|
-
this._clientId = this.connectionStateHandler.pendingClientId;
|
|
532
|
-
}
|
|
533
552
|
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
534
|
-
if (this.
|
|
535
|
-
this.propagateConnectionState(
|
|
553
|
+
if (this.loaded) {
|
|
554
|
+
this.propagateConnectionState(value === ConnectionState.Disconnected
|
|
536
555
|
? reason
|
|
537
556
|
: undefined /* disconnectedReason */);
|
|
538
557
|
}
|
|
@@ -554,15 +573,22 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
554
573
|
this.connectionTransitionTimes[ConnectionState.CatchingUp],
|
|
555
574
|
...(details === undefined ? {} : { details: JSON.stringify(details) }),
|
|
556
575
|
});
|
|
576
|
+
// This assert is important for many reasons:
|
|
577
|
+
// 1) Cosmetic / OCE burden: It's useless to raise NoJoinOp error events, if we are loading, as that's most
|
|
578
|
+
// likely to happen if snapshot loading takes too long. During this time we are not processing ops so there is no
|
|
579
|
+
// way to move to "connected" state, and thus "NoJoin" timer would fire (see
|
|
580
|
+
// IConnectionStateHandler.logConnectionIssue() callback and related code in ConnectStateHandler class implementation).
|
|
581
|
+
// But these events do not tell us anything about connectivity pipeline / op processing pipeline,
|
|
582
|
+
// only that boot is slow, and we have events for that.
|
|
583
|
+
// 2) Doing recovery below is useless in loading mode, for the reasons described above. At the same time we can't
|
|
584
|
+
// not do it, as maybe we lost JoinSignal for "self", and when loading is done, we never move to connected
|
|
585
|
+
// state. So we would have to do (in most cases) useless infinite reconnect loop while we are loading.
|
|
586
|
+
assert(this.loaded, 0x96b /* connection issues can be raised only after container is loaded */);
|
|
557
587
|
// If this is "write" connection, it took too long to receive join op. But in most cases that's due
|
|
558
588
|
// to very slow op fetches and we will eventually get there.
|
|
559
|
-
// For "read" connections, we get here due to
|
|
560
|
-
//
|
|
561
|
-
|
|
562
|
-
// current state of audience.
|
|
563
|
-
// Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
|
|
564
|
-
// to call this.applyForConnectedState("addMemberEvent") for "read" connections)
|
|
565
|
-
if (mode === "read") {
|
|
589
|
+
// For "read" connections, we get here due to join signal for "self" not arriving on time.
|
|
590
|
+
// Attempt to recover by reconnecting.
|
|
591
|
+
if (mode === "read" && category === "error") {
|
|
566
592
|
const reason = { text: "NoJoinSignal" };
|
|
567
593
|
this.disconnectInternal(reason);
|
|
568
594
|
this.connectInternal({ reason, fetchOpsFromStorage: false });
|
|
@@ -571,6 +597,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
571
597
|
clientShouldHaveLeft: (clientId) => {
|
|
572
598
|
this.clientsWhoShouldHaveLeft.add(clientId);
|
|
573
599
|
},
|
|
600
|
+
onCriticalError: (error) => {
|
|
601
|
+
this.close(normalizeError(error));
|
|
602
|
+
},
|
|
574
603
|
}, this.deltaManager, pendingLocalState?.clientId);
|
|
575
604
|
this.on(savedContainerEvent, () => {
|
|
576
605
|
this.connectionStateHandler.containerSaved();
|
|
@@ -585,11 +614,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
585
614
|
// Even if not forced on via this flag, combined summaries may still be enabled by service policy.
|
|
586
615
|
const forceEnableSummarizeProtocolTree = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
|
|
587
616
|
options.summarizeProtocolTree;
|
|
588
|
-
this.storageAdapter = new ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
|
|
617
|
+
this.storageAdapter = new ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, pendingLocalState?.loadedGroupIdSnapshots, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
|
|
589
618
|
const offlineLoadEnabled = (this.isInteractiveClient &&
|
|
590
619
|
this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad")) ??
|
|
591
620
|
options.enableOfflineLoad === true;
|
|
592
|
-
this.serializedStateManager = new SerializedStateManager(pendingLocalState, this.subLogger, this.storageAdapter, offlineLoadEnabled);
|
|
621
|
+
this.serializedStateManager = new SerializedStateManager(pendingLocalState, this.subLogger, this.storageAdapter, offlineLoadEnabled, this, () => this.isDirty);
|
|
593
622
|
const isDomAvailable = typeof document === "object" &&
|
|
594
623
|
document !== null &&
|
|
595
624
|
typeof document.addEventListener === "function" &&
|
|
@@ -724,6 +753,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
724
753
|
this.close();
|
|
725
754
|
return pendingState;
|
|
726
755
|
}
|
|
756
|
+
/**
|
|
757
|
+
* Serialize current container state required to rehydrate to the same position without dataloss.
|
|
758
|
+
* Note: The container must already be attached. For detached containers use {@link serialize}
|
|
759
|
+
* @returns stringified {@link IPendingContainerState} for the container
|
|
760
|
+
*/
|
|
727
761
|
async getPendingLocalState() {
|
|
728
762
|
return this.getPendingLocalStateCore({ notifyImminentClosure: false });
|
|
729
763
|
}
|
|
@@ -733,12 +767,18 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
733
767
|
}
|
|
734
768
|
assert(this.attachmentData.state === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
|
|
735
769
|
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
736
|
-
const pendingState = await this.serializedStateManager.
|
|
770
|
+
const pendingState = await this.serializedStateManager.getPendingLocalState(props, this.clientId, this.runtime, this.resolvedUrl);
|
|
737
771
|
return pendingState;
|
|
738
772
|
}
|
|
739
773
|
get attachState() {
|
|
740
774
|
return this.attachmentData.state;
|
|
741
775
|
}
|
|
776
|
+
/**
|
|
777
|
+
* Serialize current container state required to rehydrate to the same position without dataloss.
|
|
778
|
+
* Note: The container must be detached and not closed. For attached containers use
|
|
779
|
+
* {@link getPendingLocalState} or {@link closeAndGetPendingLocalState}
|
|
780
|
+
* @returns stringified {@link IPendingDetachedContainerState} for the container
|
|
781
|
+
*/
|
|
742
782
|
serialize() {
|
|
743
783
|
if (this.attachmentData.state === AttachState.Attached || this.closed) {
|
|
744
784
|
throw new UsageError("Container must not be attached or closed.");
|
|
@@ -794,11 +834,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
794
834
|
connectInternal(args) {
|
|
795
835
|
assert(!this.closed, 0x2c5 /* "Attempting to connect() a closed Container" */);
|
|
796
836
|
assert(this.attachState === AttachState.Attached, 0x2c6 /* "Attempting to connect() a container that is not attached" */);
|
|
797
|
-
// Resume processing ops and connect to delta stream
|
|
798
|
-
this.resumeInternal(args);
|
|
799
837
|
// Set Auto Reconnect Mode
|
|
800
838
|
const mode = ReconnectMode.Enabled;
|
|
801
839
|
this.setAutoReconnectInternal(mode, args.reason);
|
|
840
|
+
// Resume processing ops and connect to delta stream
|
|
841
|
+
this.resumeInternal(args);
|
|
802
842
|
}
|
|
803
843
|
disconnect() {
|
|
804
844
|
if (this.closed) {
|
|
@@ -818,6 +858,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
818
858
|
assert(!this.closed, 0x0d9 /* "Attempting to connect() a closed DeltaManager" */);
|
|
819
859
|
// Resume processing ops
|
|
820
860
|
if (this.inboundQueuePausedFromInit) {
|
|
861
|
+
// This assert guards against possibility of ops/signals showing up too soon, while
|
|
862
|
+
// container is not ready yet to receive them. We can hit it only if some internal code call into here,
|
|
863
|
+
// as public API like Container.connect() can be only called when user got back container object, i.e.
|
|
864
|
+
// it is already fully loaded.
|
|
865
|
+
assert(this.loaded, 0x96c /* connect() can be called only in fully loaded state */);
|
|
821
866
|
this.inboundQueuePausedFromInit = false;
|
|
822
867
|
this._deltaManager.inbound.resume();
|
|
823
868
|
this._deltaManager.inboundSignal.resume();
|
|
@@ -906,7 +951,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
906
951
|
*
|
|
907
952
|
* @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
|
|
908
953
|
*/
|
|
909
|
-
async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState
|
|
954
|
+
async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
|
|
910
955
|
const timings = { phase1: performance.now() };
|
|
911
956
|
this.service = await this.createDocumentService(async () => this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType));
|
|
912
957
|
// Except in cases where it has stashed ops or requested by feature gate, the container will connect in "read" mode
|
|
@@ -921,7 +966,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
921
966
|
};
|
|
922
967
|
// Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
|
|
923
968
|
// DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
|
|
924
|
-
if (loadMode.deltaConnection === undefined
|
|
969
|
+
if (loadMode.deltaConnection === undefined) {
|
|
925
970
|
this.connectToDeltaStream(connectionArgs);
|
|
926
971
|
}
|
|
927
972
|
this.storageAdapter.connectToService(this.service);
|
|
@@ -933,54 +978,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
933
978
|
true && this.service?.policies?.supportGetSnapshotApi === true;
|
|
934
979
|
// Fetch specified snapshot.
|
|
935
980
|
const { baseSnapshot, version } = await this.serializedStateManager.fetchSnapshot(specifiedVersion, supportGetSnapshotApi);
|
|
981
|
+
const baseSnapshotTree = getSnapshotTree(baseSnapshot);
|
|
936
982
|
this._loadedFromVersion = version;
|
|
937
|
-
const attributes = await getDocumentAttributes(this.storageAdapter,
|
|
983
|
+
const attributes = await getDocumentAttributes(this.storageAdapter, baseSnapshotTree);
|
|
938
984
|
// If we saved ops, we will replay them and don't need DeltaManager to fetch them
|
|
939
985
|
const lastProcessedSequenceNumber = pendingLocalState?.savedOps[pendingLocalState.savedOps.length - 1]?.sequenceNumber ??
|
|
940
986
|
attributes.sequenceNumber;
|
|
941
987
|
let opsBeforeReturnP;
|
|
942
|
-
if (loadMode.pauseAfterLoad === true) {
|
|
943
|
-
// If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
|
|
944
|
-
if (loadMode.opsBeforeReturn === "sequenceNumber") {
|
|
945
|
-
assert(loadToSequenceNumber !== undefined, 0x727 /* sequenceNumber should be defined */);
|
|
946
|
-
// Note: It is possible that we think the latest snapshot is newer than the specified sequence number
|
|
947
|
-
// due to saved ops that may be replayed after the snapshot.
|
|
948
|
-
// https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
|
|
949
|
-
if (lastProcessedSequenceNumber > loadToSequenceNumber) {
|
|
950
|
-
throw new Error("Cannot satisfy request to pause the container at the specified sequence number. Most recent snapshot is newer than the specified sequence number.");
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
// Force readonly mode - this will ensure we don't receive an error for the lack of join op
|
|
954
|
-
this.forceReadonly(true);
|
|
955
|
-
// We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
|
|
956
|
-
const opHandler = () => {
|
|
957
|
-
if (loadToSequenceNumber === undefined) {
|
|
958
|
-
// If there is no specified sequence number, pause after the inbound queue is empty.
|
|
959
|
-
if (this.deltaManager.inbound.length !== 0) {
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
else {
|
|
964
|
-
// If there is a specified sequence number, keep processing until we reach it.
|
|
965
|
-
if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
// Pause op processing once we have processed the desired number of ops.
|
|
970
|
-
void this.deltaManager.inbound.pause();
|
|
971
|
-
void this.deltaManager.outbound.pause();
|
|
972
|
-
this.off("op", opHandler);
|
|
973
|
-
};
|
|
974
|
-
if ((loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
|
|
975
|
-
this.deltaManager.lastSequenceNumber === loadToSequenceNumber) {
|
|
976
|
-
// If we have already reached the desired sequence number, call opHandler() to pause immediately.
|
|
977
|
-
opHandler();
|
|
978
|
-
}
|
|
979
|
-
else {
|
|
980
|
-
// If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
|
|
981
|
-
this.on("op", opHandler);
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
988
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
985
989
|
// Kick off any ops fetching if required.
|
|
986
990
|
switch (loadMode.opsBeforeReturn) {
|
|
@@ -989,7 +993,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
989
993
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
990
994
|
this.attachDeltaManagerOpHandler(attributes, loadMode.deltaConnection !== "none" ? "all" : "none", lastProcessedSequenceNumber);
|
|
991
995
|
break;
|
|
992
|
-
case "sequenceNumber":
|
|
993
996
|
case "cached":
|
|
994
997
|
case "all":
|
|
995
998
|
opsBeforeReturnP = this.attachDeltaManagerOpHandler(attributes, loadMode.opsBeforeReturn, lastProcessedSequenceNumber);
|
|
@@ -999,10 +1002,16 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
999
1002
|
}
|
|
1000
1003
|
// ...load in the existing quorum
|
|
1001
1004
|
// Initialize the protocol handler
|
|
1002
|
-
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter,
|
|
1005
|
+
await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, baseSnapshotTree);
|
|
1006
|
+
// If we are loading from pending state, we start with old clientId.
|
|
1007
|
+
// We switch to latest connection clientId only after setLoaded().
|
|
1008
|
+
assert(this.clientId === undefined, 0x96d /* there should be no clientId yet */);
|
|
1009
|
+
if (pendingLocalState?.clientId !== undefined) {
|
|
1010
|
+
this.protocolHandler.audience.setCurrentClientId(pendingLocalState?.clientId);
|
|
1011
|
+
}
|
|
1003
1012
|
timings.phase3 = performance.now();
|
|
1004
1013
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1005
|
-
await this.instantiateRuntime(codeDetails,
|
|
1014
|
+
await this.instantiateRuntime(codeDetails, baseSnapshotTree,
|
|
1006
1015
|
// give runtime a dummy value so it knows we're loading from a stash blob
|
|
1007
1016
|
pendingLocalState ? pendingLocalState?.pendingRuntimeState ?? {} : undefined, isInstanceOfISnapshot(baseSnapshot) ? baseSnapshot : undefined);
|
|
1008
1017
|
// replay saved ops
|
|
@@ -1016,6 +1025,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1016
1025
|
await this.runtime.notifyOpReplay?.(message);
|
|
1017
1026
|
}
|
|
1018
1027
|
pendingLocalState.savedOps = [];
|
|
1028
|
+
this.storageAdapter.clearPendingState();
|
|
1019
1029
|
}
|
|
1020
1030
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
1021
1031
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
@@ -1027,20 +1037,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1027
1037
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1028
1038
|
this._deltaManager.inbound.pause();
|
|
1029
1039
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
this.
|
|
1035
|
-
await new Promise((resolve, reject) => {
|
|
1036
|
-
const opHandler = (message) => {
|
|
1037
|
-
if (message.sequenceNumber >= loadToSequenceNumber) {
|
|
1038
|
-
resolve();
|
|
1039
|
-
this.off("op", opHandler);
|
|
1040
|
-
}
|
|
1041
|
-
};
|
|
1042
|
-
this.on("op", opHandler);
|
|
1043
|
-
});
|
|
1040
|
+
// Internal context is fully loaded at this point
|
|
1041
|
+
// Move to loaded before calling this.handleDeltaConnectionArg() - latter allows ops & signals in, which
|
|
1042
|
+
// may result in container moving to "connected" state. Such transitions are allowed only in loaded state.
|
|
1043
|
+
this.setLoaded();
|
|
1044
|
+
this.handleDeltaConnectionArg(loadMode.deltaConnection);
|
|
1044
1045
|
}
|
|
1045
1046
|
// Safety net: static version of Container.load() should have learned about it through "closed" handler.
|
|
1046
1047
|
// But if that did not happen for some reason, fail load for sure.
|
|
@@ -1050,8 +1051,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1050
1051
|
if (this.closed) {
|
|
1051
1052
|
throw new Error("Container was closed while load()");
|
|
1052
1053
|
}
|
|
1053
|
-
// Internal context is fully loaded at this point
|
|
1054
|
-
this.setLoaded();
|
|
1055
1054
|
timings.end = performance.now();
|
|
1056
1055
|
this.subLogger.sendTelemetryEvent({
|
|
1057
1056
|
eventName: "LoadStagesTimings",
|
|
@@ -1231,7 +1230,21 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1231
1230
|
deltaManager.inboundSignal.pause();
|
|
1232
1231
|
deltaManager.on("connect", (details, _opsBehind) => {
|
|
1233
1232
|
assert(this.connectionMode === details.mode, 0x4b7 /* mismatch */);
|
|
1234
|
-
|
|
1233
|
+
// Delay raising events until setLoaded()
|
|
1234
|
+
// Here are some of the reasons why this design is chosen:
|
|
1235
|
+
// 1. Various processes track speed of connection. But we are not processing ops or signal while container is loading,
|
|
1236
|
+
// and thus we can't move forward across connection modes. This results in telemetry errors (like NoJoinOp) that
|
|
1237
|
+
// have nothing to do with connection flow itself
|
|
1238
|
+
// 2. This also makes it hard to reason about recovery (like reconnection) in case we might have lost JoinSignal. Reconnecting
|
|
1239
|
+
// in loading phase is useless (get back to same state), but at the same time not doing it may result in broken connection
|
|
1240
|
+
// without recovery (after we loaded).
|
|
1241
|
+
// 3. We expose non-consistent view. ContainerRuntime may start loading in non-connected state, but end in connected, with
|
|
1242
|
+
// no events telling about it (until we loaded). Most of the code relies on a fact that state changes when events fire.
|
|
1243
|
+
// This will not delay any processes (as observed by the user). I.e. once container moves to loaded phase,
|
|
1244
|
+
// we immediately would transition across all phases, if we have proper signals / ops ready.
|
|
1245
|
+
if (this.loaded) {
|
|
1246
|
+
this.connectionStateHandler.receivedConnectEvent(details);
|
|
1247
|
+
}
|
|
1235
1248
|
});
|
|
1236
1249
|
deltaManager.on("establishingConnection", (reason) => {
|
|
1237
1250
|
this.connectionStateHandler.establishingConnection(reason);
|
|
@@ -1241,8 +1254,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1241
1254
|
});
|
|
1242
1255
|
deltaManager.on("disconnect", (text, error) => {
|
|
1243
1256
|
this.noopHeuristic?.notifyDisconnect();
|
|
1244
|
-
|
|
1245
|
-
|
|
1257
|
+
const reason = { text, error };
|
|
1258
|
+
// Symmetry with "connect" events
|
|
1259
|
+
if (this.loaded) {
|
|
1260
|
+
this.connectionStateHandler.receivedDisconnectEvent(reason);
|
|
1261
|
+
}
|
|
1262
|
+
else if (!this.closed) {
|
|
1263
|
+
// Raise cancellation to get state machine back to initial state
|
|
1264
|
+
this.connectionStateHandler.cancelEstablishingConnection(reason);
|
|
1246
1265
|
}
|
|
1247
1266
|
});
|
|
1248
1267
|
deltaManager.on("throttled", (warning) => {
|
|
@@ -1255,7 +1274,9 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1255
1274
|
this.emit("warning", warn);
|
|
1256
1275
|
});
|
|
1257
1276
|
deltaManager.on("readonly", (readonly) => {
|
|
1258
|
-
|
|
1277
|
+
if (this.loaded) {
|
|
1278
|
+
this.setContextConnectedState(this.connectionState === ConnectionState.Connected, readonly);
|
|
1279
|
+
}
|
|
1259
1280
|
this.emit("readonly", readonly);
|
|
1260
1281
|
});
|
|
1261
1282
|
deltaManager.on("closed", (error) => {
|
|
@@ -1297,8 +1318,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1297
1318
|
// This info is of most interesting while Catching Up.
|
|
1298
1319
|
checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
|
|
1299
1320
|
// Need to check that we have already loaded and fetched the snapshot.
|
|
1300
|
-
if (this.deltaManager.hasCheckpointSequenceNumber &&
|
|
1301
|
-
this._lifecycleState === "loaded") {
|
|
1321
|
+
if (this.deltaManager.hasCheckpointSequenceNumber && this.loaded) {
|
|
1302
1322
|
opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
|
|
1303
1323
|
}
|
|
1304
1324
|
}
|
|
@@ -1312,7 +1332,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1312
1332
|
reason: reason?.text,
|
|
1313
1333
|
connectionInitiationReason,
|
|
1314
1334
|
pendingClientId: this.connectionStateHandler.pendingClientId,
|
|
1315
|
-
clientId: this.clientId,
|
|
1335
|
+
clientId: this.connectionStateHandler.clientId,
|
|
1316
1336
|
autoReconnect,
|
|
1317
1337
|
opsBehind,
|
|
1318
1338
|
online: OnlineStatus[isOnline()],
|
|
@@ -1328,20 +1348,23 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1328
1348
|
this.firstConnection = false;
|
|
1329
1349
|
}
|
|
1330
1350
|
}
|
|
1331
|
-
propagateConnectionState(
|
|
1332
|
-
|
|
1333
|
-
|
|
1351
|
+
propagateConnectionState(disconnectedReason) {
|
|
1352
|
+
const connected = this.connectionState === ConnectionState.Connected;
|
|
1353
|
+
if (connected) {
|
|
1354
|
+
const clientId = this.connectionStateHandler.clientId;
|
|
1355
|
+
assert(clientId !== undefined, 0x96e /* there has to be clientId */);
|
|
1356
|
+
this.protocolHandler.audience.setCurrentClientId(clientId);
|
|
1357
|
+
}
|
|
1358
|
+
// We communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
1334
1359
|
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
1335
|
-
if (
|
|
1336
|
-
this.connectionState !== ConnectionState.Connected &&
|
|
1360
|
+
if (this.connectionState !== ConnectionState.Connected &&
|
|
1337
1361
|
this.connectionState !== ConnectionState.Disconnected) {
|
|
1338
1362
|
return;
|
|
1339
1363
|
}
|
|
1340
|
-
const state = this.connectionState === ConnectionState.Connected;
|
|
1341
1364
|
// Both protocol and context should not be undefined if we got so far.
|
|
1342
|
-
this.setContextConnectedState(
|
|
1343
|
-
this.protocolHandler.setConnectionState(
|
|
1344
|
-
raiseConnectedEvent(this.mc.logger, this,
|
|
1365
|
+
this.setContextConnectedState(connected, this.readOnlyInfo.readonly ?? false);
|
|
1366
|
+
this.protocolHandler.setConnectionState(connected, this.clientId);
|
|
1367
|
+
raiseConnectedEvent(this.mc.logger, this, connected, this.clientId, disconnectedReason?.text);
|
|
1345
1368
|
}
|
|
1346
1369
|
// back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
|
|
1347
1370
|
submitContainerMessage(type, contents, batch, metadata) {
|
|
@@ -1462,24 +1485,29 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1462
1485
|
/**
|
|
1463
1486
|
* Set the connected state of the ContainerContext
|
|
1464
1487
|
* This controls the "connected" state of the ContainerRuntime as well
|
|
1465
|
-
* @param
|
|
1488
|
+
* @param connected - Is the container currently connected?
|
|
1466
1489
|
* @param readonly - Is the container in readonly mode?
|
|
1467
1490
|
*/
|
|
1468
|
-
setContextConnectedState(
|
|
1469
|
-
if (this._runtime?.disposed === false) {
|
|
1491
|
+
setContextConnectedState(connected, readonly) {
|
|
1492
|
+
if (this._runtime?.disposed === false && this.loaded) {
|
|
1470
1493
|
/**
|
|
1471
1494
|
* We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
|
|
1472
1495
|
* ops getting through to the DeltaManager.
|
|
1473
1496
|
* The ContainerRuntime's "connected" state simply means it is ok to send ops
|
|
1474
1497
|
* See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
|
|
1475
1498
|
*/
|
|
1476
|
-
this.runtime.setConnectionState(
|
|
1499
|
+
this.runtime.setConnectionState(connected && !readonly, this.clientId);
|
|
1477
1500
|
}
|
|
1478
1501
|
}
|
|
1479
|
-
handleDeltaConnectionArg(
|
|
1502
|
+
handleDeltaConnectionArg(deltaConnectionArg, connectionArgs) {
|
|
1503
|
+
// This ensures that we allow transitions to "connected" state only after container has been fully loaded
|
|
1504
|
+
// and we propagate such events to container runtime. All events prior to being loaded are ignored.
|
|
1505
|
+
// This means if we get here in non-loaded state, we might not deliver proper events to container runtime,
|
|
1506
|
+
// and runtime implementation may miss such events.
|
|
1507
|
+
assert(this.loaded, 0x96f /* has to be called after container transitions to loaded state */);
|
|
1480
1508
|
switch (deltaConnectionArg) {
|
|
1481
1509
|
case undefined:
|
|
1482
|
-
if (
|
|
1510
|
+
if (connectionArgs) {
|
|
1483
1511
|
// connect to delta stream now since we did not before
|
|
1484
1512
|
this.connectToDeltaStream(connectionArgs);
|
|
1485
1513
|
}
|