@fluidframework/container-loader 0.59.4001 → 1.1.0-75972
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 +1 -1
- package/README.md +1 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +3 -1
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionState.d.ts +15 -3
- package/dist/connectionState.d.ts.map +1 -1
- package/dist/connectionState.js +15 -3
- package/dist/connectionState.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +47 -11
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +108 -38
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +20 -28
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +97 -153
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +6 -4
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +8 -7
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +4 -5
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +4 -7
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +1 -1
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +9 -1
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts +0 -1
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaManagerProxy.js +0 -3
- package/dist/deltaManagerProxy.js.map +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +8 -3
- package/dist/deltaQueue.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +1 -13
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +2 -3
- package/dist/loader.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts +2 -3
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js +0 -1
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +3 -4
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +4 -7
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +3 -2
- package/dist/utils.js.map +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +4 -2
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionState.d.ts +15 -3
- package/lib/connectionState.d.ts.map +1 -1
- package/lib/connectionState.js +15 -3
- package/lib/connectionState.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +47 -11
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +108 -38
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +20 -28
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +98 -154
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +6 -4
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +8 -7
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +4 -5
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +4 -7
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +1 -1
- package/lib/contracts.js +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +9 -1
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts +0 -1
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaManagerProxy.js +0 -3
- package/lib/deltaManagerProxy.js.map +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +8 -3
- package/lib/deltaQueue.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +1 -13
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +2 -3
- package/lib/loader.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts +2 -3
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js +0 -1
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +3 -4
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +4 -7
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +3 -2
- package/lib/utils.js.map +1 -1
- package/package.json +14 -27
- package/src/connectionManager.ts +4 -6
- package/src/connectionState.ts +20 -6
- package/src/connectionStateHandler.ts +136 -56
- package/src/container.ts +139 -185
- package/src/containerContext.ts +10 -10
- package/src/containerStorageAdapter.ts +5 -10
- package/src/contracts.ts +1 -1
- package/src/deltaManager.ts +8 -2
- package/src/deltaManagerProxy.ts +0 -4
- package/src/deltaQueue.ts +7 -3
- package/src/index.ts +1 -0
- package/src/loader.ts +4 -21
- package/src/packageVersion.ts +1 -1
- package/src/protocolTreeDocumentStorageService.ts +0 -1
- package/src/retriableDocumentStorageService.ts +4 -12
- package/src/utils.ts +3 -2
package/lib/container.js
CHANGED
|
@@ -9,7 +9,7 @@ import { assert, performance, unreachableCase } from "@fluidframework/common-uti
|
|
|
9
9
|
import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
|
|
10
10
|
import { DataCorruptionError, extractSafePropertiesFromMessage, GenericError, UsageError, } from "@fluidframework/container-utils";
|
|
11
11
|
import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, } from "@fluidframework/driver-utils";
|
|
12
|
-
import { isSystemMessage,
|
|
12
|
+
import { isSystemMessage, ProtocolOpHandlerWithClientValidation, } from "@fluidframework/protocol-base";
|
|
13
13
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
14
14
|
import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, disconnectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
|
|
15
15
|
import { Audience } from "./audience";
|
|
@@ -36,7 +36,7 @@ const savedContainerEvent = "saved";
|
|
|
36
36
|
* Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
|
|
37
37
|
* up to date. Host may chose to wait in such case and retry resolving URI.
|
|
38
38
|
* Warning: Will wait infinitely for connection to establish if there is no connection.
|
|
39
|
-
* May result in deadlock if Container.
|
|
39
|
+
* May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
|
|
40
40
|
* @returns true: container is up to date, it processed all the ops that were know at the time of first connection
|
|
41
41
|
* false: storage does not provide indication of how far the client is. Container processed
|
|
42
42
|
* all the ops known to it, but it maybe still behind.
|
|
@@ -58,7 +58,8 @@ export async function waitContainerToCatchUp(container) {
|
|
|
58
58
|
};
|
|
59
59
|
container.on("closed", closedCallback);
|
|
60
60
|
const waitForOps = () => {
|
|
61
|
-
assert(container.connectionState
|
|
61
|
+
assert(container.connectionState === ConnectionState.CatchingUp
|
|
62
|
+
|| container.connectionState === ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
|
|
62
63
|
const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
|
|
63
64
|
const connectionOpSeqNumber = deltaManager.lastKnownSeqNumber;
|
|
64
65
|
assert(deltaManager.lastSequenceNumber <= connectionOpSeqNumber, 0x266 /* "lastKnownSeqNumber should never be below last processed sequence number" */);
|
|
@@ -89,9 +90,7 @@ export async function waitContainerToCatchUp(container) {
|
|
|
89
90
|
waitForOps();
|
|
90
91
|
};
|
|
91
92
|
container.on(connectedEventName, callback);
|
|
92
|
-
|
|
93
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
94
|
-
container.resume();
|
|
93
|
+
container.connect();
|
|
95
94
|
});
|
|
96
95
|
}
|
|
97
96
|
const getCodeProposal =
|
|
@@ -100,7 +99,7 @@ const getCodeProposal =
|
|
|
100
99
|
const summarizerClientType = "summarizer";
|
|
101
100
|
export class Container extends EventEmitterWithErrorHandling {
|
|
102
101
|
constructor(loader, config) {
|
|
103
|
-
var _a;
|
|
102
|
+
var _a, _b;
|
|
104
103
|
super((name, error) => {
|
|
105
104
|
this.mc.logger.sendErrorEvent({
|
|
106
105
|
eventName: "ContainerEventHandlerException",
|
|
@@ -111,7 +110,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
111
110
|
// Tells if container can reconnect on losing fist connection
|
|
112
111
|
// If false, container gets closed on loss of connection.
|
|
113
112
|
this._canReconnect = true;
|
|
114
|
-
this._lifecycleState = "
|
|
113
|
+
this._lifecycleState = "loading";
|
|
115
114
|
this._attachState = AttachState.Detached;
|
|
116
115
|
this.resumedOpProcessingAfterLoad = false;
|
|
117
116
|
this.firstConnection = true;
|
|
@@ -167,20 +166,18 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
167
166
|
logConnectionStateChangeTelemetry: (value, oldState, reason) => this.logConnectionStateChangeTelemetry(value, oldState, reason),
|
|
168
167
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
169
168
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
170
|
-
logConnectionIssue: (eventName) => {
|
|
169
|
+
logConnectionIssue: (eventName, details) => {
|
|
171
170
|
// We get here when socket does not receive any ops on "write" connection, including
|
|
172
171
|
// its own join op. Attempt recovery option.
|
|
173
|
-
this._deltaManager.logConnectionIssue({
|
|
174
|
-
eventName,
|
|
175
|
-
duration: performance.now() - this.connectionTransitionTimes[ConnectionState.Connecting],
|
|
176
|
-
});
|
|
172
|
+
this._deltaManager.logConnectionIssue(Object.assign({ eventName, duration: performance.now() - this.connectionTransitionTimes[ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
|
|
177
173
|
},
|
|
178
174
|
connectionStateChanged: () => {
|
|
179
|
-
if
|
|
175
|
+
// Fire events only if container is fully loaded and not closed
|
|
176
|
+
if (this._lifecycleState === "loaded") {
|
|
180
177
|
this.propagateConnectionState();
|
|
181
178
|
}
|
|
182
179
|
},
|
|
183
|
-
}, this.mc.logger);
|
|
180
|
+
}, this.mc.logger, (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId);
|
|
184
181
|
this.on(savedContainerEvent, () => {
|
|
185
182
|
this.connectionStateHandler.containerSaved();
|
|
186
183
|
});
|
|
@@ -258,16 +255,16 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
258
255
|
clientDetailsOverride: loadOptions.clientDetailsOverride,
|
|
259
256
|
resolvedUrl: loadOptions.resolvedUrl,
|
|
260
257
|
canReconnect: loadOptions.canReconnect,
|
|
258
|
+
serializedContainerState: pendingLocalState,
|
|
261
259
|
});
|
|
262
260
|
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
|
|
263
|
-
var _a;
|
|
264
|
-
container._lifecycleState = "loading";
|
|
261
|
+
var _a, _b;
|
|
265
262
|
const version = loadOptions.version;
|
|
266
|
-
// always load unpaused with pending ops!
|
|
267
|
-
// It is also default mode in general.
|
|
268
263
|
const defaultMode = { opsBeforeReturn: "cached" };
|
|
269
|
-
|
|
270
|
-
|
|
264
|
+
// if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
|
|
265
|
+
// to return container, so ignore this value and use undefined for opsBeforeReturn
|
|
266
|
+
const mode = pendingLocalState
|
|
267
|
+
? Object.assign(Object.assign({}, ((_a = loadOptions.loadMode) !== null && _a !== void 0 ? _a : defaultMode)), { opsBeforeReturn: undefined }) : (_b = loadOptions.loadMode) !== null && _b !== void 0 ? _b : defaultMode;
|
|
271
268
|
const onClosed = (err) => {
|
|
272
269
|
// pre-0.58 error message: containerClosedWithoutErrorDuringLoad
|
|
273
270
|
reject(err !== null && err !== void 0 ? err : new GenericError("Container closed without error during load"));
|
|
@@ -296,7 +293,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
296
293
|
static async createDetached(loader, codeDetails) {
|
|
297
294
|
const container = new Container(loader, {});
|
|
298
295
|
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "CreateDetached" }, async (_event) => {
|
|
299
|
-
container._lifecycleState = "loading";
|
|
300
296
|
await container.createDetached(codeDetails);
|
|
301
297
|
return container;
|
|
302
298
|
}, { start: true, end: true, cancel: "generic" });
|
|
@@ -309,20 +305,16 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
309
305
|
const container = new Container(loader, {});
|
|
310
306
|
return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "RehydrateDetachedFromSnapshot" }, async (_event) => {
|
|
311
307
|
const deserializedSummary = JSON.parse(snapshot);
|
|
312
|
-
container._lifecycleState = "loading";
|
|
313
308
|
await container.rehydrateDetachedFromSnapshot(deserializedSummary);
|
|
314
309
|
return container;
|
|
315
310
|
}, { start: true, end: true, cancel: "generic" });
|
|
316
311
|
}
|
|
317
|
-
|
|
318
|
-
return (this._lifecycleState !== "created" && this._lifecycleState !== "loading");
|
|
319
|
-
}
|
|
320
|
-
set loaded(t) {
|
|
321
|
-
assert(t, 0x27d /* "Setting loaded state to false is not supported" */);
|
|
322
|
-
assert(this._lifecycleState !== "created", 0x27e /* "Must go through loading state before loaded" */);
|
|
312
|
+
setLoaded() {
|
|
323
313
|
// It's conceivable the container could be closed when this is called
|
|
324
314
|
// Only transition states if currently loading
|
|
325
315
|
if (this._lifecycleState === "loading") {
|
|
316
|
+
// Propagate current connection state through the system.
|
|
317
|
+
this.propagateConnectionState();
|
|
326
318
|
this._lifecycleState = "loaded";
|
|
327
319
|
}
|
|
328
320
|
}
|
|
@@ -444,19 +436,30 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
444
436
|
return this.protocolHandler.quorum;
|
|
445
437
|
}
|
|
446
438
|
close(error) {
|
|
439
|
+
// 1. Ensure that close sequence is exactly the same no matter if it's initiated by host or by DeltaManager
|
|
440
|
+
// 2. We need to ensure that we deliver disconnect event to runtime properly. See connectionStateChanged
|
|
441
|
+
// handler. We only deliver events if container fully loaded. Transitioning from "loading" ->
|
|
442
|
+
// "closing" will lose that info (can also solve by tracking extra state).
|
|
443
|
+
this._deltaManager.close(error);
|
|
444
|
+
assert(this.connectionState === ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
|
|
445
|
+
assert(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
|
|
446
|
+
}
|
|
447
|
+
closeCore(error) {
|
|
447
448
|
var _a, _b, _c, _d;
|
|
448
|
-
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
449
|
+
assert(!this.closed, 0x315 /* re-entrancy */);
|
|
451
450
|
try {
|
|
452
|
-
this._lifecycleState = "closing";
|
|
453
451
|
// Ensure that we raise all key events even if one of these throws
|
|
454
452
|
try {
|
|
455
|
-
|
|
453
|
+
// Raise event first, to ensure we capture _lifecycleState before transition.
|
|
454
|
+
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
455
|
+
this.mc.logger.sendTelemetryEvent({
|
|
456
|
+
eventName: "ContainerClose",
|
|
457
|
+
category: error === undefined ? "generic" : "error",
|
|
458
|
+
}, error);
|
|
459
|
+
this._lifecycleState = "closing";
|
|
456
460
|
(_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
|
|
457
461
|
this.connectionStateHandler.dispose();
|
|
458
462
|
(_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
459
|
-
assert(this.connectionState === ConnectionState.Disconnected, 0x0cf /* "disconnect event was not raised!" */);
|
|
460
463
|
(_c = this._storageService) === null || _c === void 0 ? void 0 : _c.dispose();
|
|
461
464
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
462
465
|
// about file, like file being overwritten in storage, but client having stale local cache.
|
|
@@ -466,10 +469,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
466
469
|
catch (exception) {
|
|
467
470
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
|
|
468
471
|
}
|
|
469
|
-
this.mc.logger.sendTelemetryEvent({
|
|
470
|
-
eventName: "ContainerClose",
|
|
471
|
-
category: error === undefined ? "generic" : "error",
|
|
472
|
-
}, error);
|
|
473
472
|
this.emit("closed", error);
|
|
474
473
|
this.removeAllListeners();
|
|
475
474
|
if (this.visibilityEventHandler !== undefined) {
|
|
@@ -486,9 +485,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
486
485
|
// a new clientId and a future container using stale pending state without the new clientId would resubmit them
|
|
487
486
|
assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
|
|
488
487
|
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
488
|
+
assert(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
|
|
489
|
+
assert(this._protocolHandler.attributes.term !== undefined, 0x30b /* Must have a valid protocol handler instance */);
|
|
489
490
|
const pendingState = {
|
|
490
491
|
pendingRuntimeState: this.context.getPendingLocalState(),
|
|
491
492
|
url: this.resolvedUrl.url,
|
|
493
|
+
protocol: this.protocolHandler.getProtocolState(),
|
|
494
|
+
term: this._protocolHandler.attributes.term,
|
|
495
|
+
clientId: this.clientId,
|
|
492
496
|
};
|
|
493
497
|
this.close();
|
|
494
498
|
return JSON.stringify(pendingState);
|
|
@@ -532,7 +536,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
532
536
|
// starting to attach the container to storage.
|
|
533
537
|
// Also, this should only be fired in detached container.
|
|
534
538
|
this._attachState = AttachState.Attaching;
|
|
535
|
-
this.context.notifyAttaching();
|
|
539
|
+
this.context.notifyAttaching(getSnapshotTreeFromSerializedContainer(summary));
|
|
536
540
|
}
|
|
537
541
|
// Actually go and create the resolved document
|
|
538
542
|
const createNewResolvedUrl = await this.urlResolver.resolve(request);
|
|
@@ -567,7 +571,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
567
571
|
const protocolSummary = this.captureProtocolSummary();
|
|
568
572
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
569
573
|
this._attachState = AttachState.Attaching;
|
|
570
|
-
this.context.notifyAttaching();
|
|
574
|
+
this.context.notifyAttaching(getSnapshotTreeFromSerializedContainer(summary));
|
|
571
575
|
await this.storageService.uploadSummaryWithContext(summary, {
|
|
572
576
|
referenceSequenceNumber: 0,
|
|
573
577
|
ackHandle: undefined,
|
|
@@ -597,27 +601,6 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
597
601
|
async request(path) {
|
|
598
602
|
return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.context.request(path), { end: true, cancel: "error" });
|
|
599
603
|
}
|
|
600
|
-
/**
|
|
601
|
-
* Dictates whether or not the current container will automatically attempt to reconnect to the delta stream
|
|
602
|
-
* after receiving a disconnect event
|
|
603
|
-
* @param reconnect - Boolean indicating if reconnect should automatically occur
|
|
604
|
-
* @deprecated - 0.58, This API will be removed in 1.0
|
|
605
|
-
* Use `connect()` and `disconnect()` instead of `setAutoReconnect(true)` and `setAutoReconnect(false)` respectively
|
|
606
|
-
* See https://github.com/microsoft/FluidFramework/issues/9167 for context
|
|
607
|
-
*/
|
|
608
|
-
setAutoReconnect(reconnect) {
|
|
609
|
-
if (this.closed) {
|
|
610
|
-
throw new Error("Attempting to setAutoReconnect() a closed Container");
|
|
611
|
-
}
|
|
612
|
-
const mode = reconnect ? ReconnectMode.Enabled : ReconnectMode.Disabled;
|
|
613
|
-
this.setAutoReconnectInternal(mode);
|
|
614
|
-
// If container state is not attached and resumed, then don't connect to delta stream. Also don't set the
|
|
615
|
-
// manual reconnection flag to true as we haven't made the initial connection yet.
|
|
616
|
-
if (reconnect && this._attachState === AttachState.Attached && this.resumedOpProcessingAfterLoad) {
|
|
617
|
-
// Ensure connection to web socket
|
|
618
|
-
this.connectToDeltaStream({ reason: "autoReconnect" });
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
604
|
setAutoReconnectInternal(mode) {
|
|
622
605
|
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
623
606
|
if (currentMode === mode) {
|
|
@@ -671,22 +654,8 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
671
654
|
const mode = ReconnectMode.Disabled;
|
|
672
655
|
this.setAutoReconnectInternal(mode);
|
|
673
656
|
}
|
|
674
|
-
/**
|
|
675
|
-
* Have the container attempt to resume processing ops
|
|
676
|
-
* @deprecated - 0.58, This API will be removed in 1.0
|
|
677
|
-
* Use `connect()` instead
|
|
678
|
-
* See https://github.com/microsoft/FluidFramework/issues/9167 for context
|
|
679
|
-
*/
|
|
680
|
-
resume() {
|
|
681
|
-
if (!this.closed) {
|
|
682
|
-
// Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
|
|
683
|
-
// If there is gap, we will learn about it once connected, but the gap should be small (if any),
|
|
684
|
-
// assuming that resume() is called quickly after initial container boot.
|
|
685
|
-
this.resumeInternal({ reason: "DocumentOpenResume", fetchOpsFromStorage: false });
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
657
|
resumeInternal(args) {
|
|
689
|
-
assert(!this.closed, 0x0d9 /* "Attempting to
|
|
658
|
+
assert(!this.closed, 0x0d9 /* "Attempting to connect() a closed DeltaManager" */);
|
|
690
659
|
// Resume processing ops
|
|
691
660
|
if (!this.resumedOpProcessingAfterLoad) {
|
|
692
661
|
this.resumedOpProcessingAfterLoad = true;
|
|
@@ -775,12 +744,26 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
775
744
|
if (loadMode.deltaConnection === undefined) {
|
|
776
745
|
this.connectToDeltaStream(connectionArgs);
|
|
777
746
|
}
|
|
778
|
-
|
|
747
|
+
if (!pendingLocalState) {
|
|
748
|
+
await this.connectStorageService();
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
752
|
+
this.connectStorageService().catch((error) => this.close(error));
|
|
753
|
+
}
|
|
779
754
|
this._attachState = AttachState.Attached;
|
|
780
755
|
// Fetch specified snapshot.
|
|
781
|
-
const { snapshot, versionId } =
|
|
782
|
-
|
|
783
|
-
|
|
756
|
+
const { snapshot, versionId } = pendingLocalState === undefined
|
|
757
|
+
? await this.fetchSnapshotTree(specifiedVersion)
|
|
758
|
+
: { snapshot: undefined, versionId: undefined };
|
|
759
|
+
assert(snapshot !== undefined || pendingLocalState !== undefined, 0x237 /* "Snapshot should exist" */);
|
|
760
|
+
const attributes = pendingLocalState === undefined
|
|
761
|
+
? await this.getDocumentAttributes(this.storageService, snapshot)
|
|
762
|
+
: {
|
|
763
|
+
sequenceNumber: pendingLocalState.protocol.sequenceNumber,
|
|
764
|
+
minimumSequenceNumber: pendingLocalState.protocol.minimumSequenceNumber,
|
|
765
|
+
term: pendingLocalState.term,
|
|
766
|
+
};
|
|
784
767
|
let opsBeforeReturnP;
|
|
785
768
|
// Attach op handlers to finish initialization and be able to start processing ops
|
|
786
769
|
// Kick off any ops fetching if required.
|
|
@@ -801,15 +784,14 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
801
784
|
}
|
|
802
785
|
// ...load in the existing quorum
|
|
803
786
|
// Initialize the protocol handler
|
|
804
|
-
this._protocolHandler =
|
|
805
|
-
await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot)
|
|
787
|
+
this._protocolHandler = pendingLocalState === undefined
|
|
788
|
+
? await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot)
|
|
789
|
+
: await this.initializeProtocolState(attributes, pendingLocalState.protocol.members, pendingLocalState.protocol.proposals, pendingLocalState.protocol.values);
|
|
806
790
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
807
791
|
await this.instantiateContext(true, // existing
|
|
808
|
-
codeDetails, snapshot, pendingLocalState);
|
|
809
|
-
// Propagate current connection state through the system.
|
|
810
|
-
this.propagateConnectionState();
|
|
792
|
+
codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
|
|
811
793
|
// Internal context is fully loaded at this point
|
|
812
|
-
this.
|
|
794
|
+
this.setLoaded();
|
|
813
795
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
814
796
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
815
797
|
if (!this.closed) {
|
|
@@ -822,7 +804,10 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
822
804
|
}
|
|
823
805
|
switch (loadMode.deltaConnection) {
|
|
824
806
|
case undefined:
|
|
825
|
-
|
|
807
|
+
// Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
|
|
808
|
+
// If there is gap, we will learn about it once connected, but the gap should be small (if any),
|
|
809
|
+
// assuming that resumeInternal() is called quickly after initial container boot.
|
|
810
|
+
this.resumeInternal({ reason: "DocumentLoad", fetchOpsFromStorage: false });
|
|
826
811
|
break;
|
|
827
812
|
case "delayed":
|
|
828
813
|
this.resumedOpProcessingAfterLoad = true;
|
|
@@ -862,8 +847,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
862
847
|
qValues);
|
|
863
848
|
// The load context - given we seeded the quorum - will be great
|
|
864
849
|
await this.instantiateContextDetached(false);
|
|
865
|
-
this.
|
|
866
|
-
this.loaded = true;
|
|
850
|
+
this.setLoaded();
|
|
867
851
|
}
|
|
868
852
|
async rehydrateDetachedFromSnapshot(detachedContainerSnapshot) {
|
|
869
853
|
if (detachedContainerSnapshot.tree[".hasAttachmentBlobs"] !== undefined) {
|
|
@@ -884,8 +868,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
884
868
|
codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : []);
|
|
885
869
|
await this.instantiateContextDetached(true, // existing
|
|
886
870
|
snapshotTree);
|
|
887
|
-
this.
|
|
888
|
-
this.propagateConnectionState();
|
|
871
|
+
this.setLoaded();
|
|
889
872
|
}
|
|
890
873
|
async connectStorageService() {
|
|
891
874
|
var _a, _b;
|
|
@@ -939,18 +922,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
939
922
|
return protocolHandler;
|
|
940
923
|
}
|
|
941
924
|
async initializeProtocolState(attributes, members, proposals, values) {
|
|
942
|
-
const protocol = new
|
|
925
|
+
const protocol = new ProtocolOpHandlerWithClientValidation(attributes.minimumSequenceNumber, attributes.sequenceNumber, attributes.term, members, proposals, values, (key, value) => this.submitMessage(MessageType.Propose, { key, value }));
|
|
943
926
|
const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
|
|
944
927
|
protocol.quorum.on("error", (error) => {
|
|
945
928
|
protocolLogger.sendErrorEvent(error);
|
|
946
929
|
});
|
|
947
930
|
// Track membership changes and update connection state accordingly
|
|
948
|
-
|
|
949
|
-
this.connectionStateHandler.receivedAddMemberEvent(clientId);
|
|
950
|
-
});
|
|
951
|
-
protocol.quorum.on("removeMember", (clientId) => {
|
|
952
|
-
this.connectionStateHandler.receivedRemoveMemberEvent(clientId);
|
|
953
|
-
});
|
|
931
|
+
this.connectionStateHandler.initProtocol(protocol);
|
|
954
932
|
protocol.quorum.on("addProposal", (proposal) => {
|
|
955
933
|
if (proposal.key === "code" || proposal.key === "code2") {
|
|
956
934
|
this.emit("codeDetailsProposed", proposal.value, proposal);
|
|
@@ -972,17 +950,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
972
950
|
return protocol;
|
|
973
951
|
}
|
|
974
952
|
captureProtocolSummary() {
|
|
975
|
-
const quorumSnapshot = this.protocolHandler.
|
|
976
|
-
// Save attributes for the document
|
|
977
|
-
const documentAttributes = {
|
|
978
|
-
minimumSequenceNumber: this.protocolHandler.minimumSequenceNumber,
|
|
979
|
-
sequenceNumber: this.protocolHandler.sequenceNumber,
|
|
980
|
-
term: this.protocolHandler.term,
|
|
981
|
-
};
|
|
953
|
+
const quorumSnapshot = this.protocolHandler.snapshot();
|
|
982
954
|
const summary = {
|
|
983
955
|
tree: {
|
|
984
956
|
attributes: {
|
|
985
|
-
content: JSON.stringify(
|
|
957
|
+
content: JSON.stringify(this.protocolHandler.attributes),
|
|
986
958
|
type: SummaryType.Blob,
|
|
987
959
|
},
|
|
988
960
|
quorumMembers: {
|
|
@@ -1071,7 +1043,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1071
1043
|
this.emit("readonly", readonly);
|
|
1072
1044
|
});
|
|
1073
1045
|
deltaManager.on("closed", (error) => {
|
|
1074
|
-
this.
|
|
1046
|
+
this.closeCore(error);
|
|
1075
1047
|
});
|
|
1076
1048
|
return deltaManager;
|
|
1077
1049
|
}
|
|
@@ -1085,6 +1057,7 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1085
1057
|
}, prefetchType);
|
|
1086
1058
|
}
|
|
1087
1059
|
logConnectionStateChangeTelemetry(value, oldState, reason) {
|
|
1060
|
+
var _a;
|
|
1088
1061
|
// Log actual event
|
|
1089
1062
|
const time = performance.now();
|
|
1090
1063
|
this.connectionTransitionTimes[value] = time;
|
|
@@ -1120,12 +1093,13 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1120
1093
|
durationFromDisconnected,
|
|
1121
1094
|
reason,
|
|
1122
1095
|
connectionInitiationReason, pendingClientId: this.connectionStateHandler.pendingClientId, clientId: this.clientId, autoReconnect,
|
|
1123
|
-
opsBehind, online: OnlineStatus[isOnline()], lastVisible: this.lastVisible !== undefined ? performance.now() - this.lastVisible : undefined, checkpointSequenceNumber }, this._deltaManager.connectionProps));
|
|
1096
|
+
opsBehind, online: OnlineStatus[isOnline()], lastVisible: this.lastVisible !== undefined ? performance.now() - this.lastVisible : undefined, checkpointSequenceNumber, quorumSize: (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.quorum.getMembers().size }, this._deltaManager.connectionProps));
|
|
1124
1097
|
if (value === ConnectionState.Connected) {
|
|
1125
1098
|
this.firstConnection = false;
|
|
1126
1099
|
}
|
|
1127
1100
|
}
|
|
1128
1101
|
propagateConnectionState() {
|
|
1102
|
+
var _a;
|
|
1129
1103
|
const logOpsOnReconnect = this.connectionState === ConnectionState.Connected &&
|
|
1130
1104
|
!this.firstConnection &&
|
|
1131
1105
|
this.connectionMode === "write";
|
|
@@ -1133,11 +1107,11 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1133
1107
|
this.messageCountAfterDisconnection = 0;
|
|
1134
1108
|
}
|
|
1135
1109
|
const state = this.connectionState === ConnectionState.Connected;
|
|
1136
|
-
if
|
|
1110
|
+
// Both protocol and context should not be undefined if we got so far.
|
|
1111
|
+
if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
|
|
1137
1112
|
this.context.setConnectionState(state, this.clientId);
|
|
1138
1113
|
}
|
|
1139
|
-
|
|
1140
|
-
this.protocolHandler.quorum.setConnectionState(state, this.clientId);
|
|
1114
|
+
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1141
1115
|
raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
|
|
1142
1116
|
if (logOpsOnReconnect) {
|
|
1143
1117
|
this.mc.logger.sendTelemetryEvent({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
|
|
@@ -1179,31 +1153,19 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1179
1153
|
return this._deltaManager.submit(type, contents, batch, metadata);
|
|
1180
1154
|
}
|
|
1181
1155
|
processRemoteMessage(message) {
|
|
1182
|
-
// Check and report if we're getting messages from a clientId that we previously
|
|
1183
|
-
// flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
|
|
1184
|
-
if (message.clientId != null) {
|
|
1185
|
-
let errorMsg;
|
|
1186
|
-
const client = this.getQuorum().getMember(message.clientId);
|
|
1187
|
-
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
1188
|
-
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
1189
|
-
errorMsg = "Remote message's clientId is missing from the quorum";
|
|
1190
|
-
}
|
|
1191
|
-
else if ((client === null || client === void 0 ? void 0 : client.shouldHaveLeft) === true && message.type !== MessageType.NoOp) {
|
|
1192
|
-
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
1193
|
-
errorMsg = "Remote message's clientId already should have left";
|
|
1194
|
-
}
|
|
1195
|
-
if (errorMsg !== undefined) {
|
|
1196
|
-
const error = new DataCorruptionError(errorMsg, extractSafePropertiesFromMessage(message));
|
|
1197
|
-
this.close(normalizeError(error));
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
1156
|
const local = this.clientId === message.clientId;
|
|
1157
|
+
// Allow the protocol handler to process the message
|
|
1158
|
+
let result = { immediateNoOp: false };
|
|
1159
|
+
try {
|
|
1160
|
+
result = this.protocolHandler.processMessage(message, local);
|
|
1161
|
+
}
|
|
1162
|
+
catch (error) {
|
|
1163
|
+
this.close(wrapError(error, (errorMessage) => new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
|
|
1164
|
+
}
|
|
1201
1165
|
// Forward non system messages to the loaded runtime for processing
|
|
1202
1166
|
if (!isSystemMessage(message)) {
|
|
1203
1167
|
this.context.process(message, local, undefined);
|
|
1204
1168
|
}
|
|
1205
|
-
// Allow the protocol handler to process the message
|
|
1206
|
-
const result = this.protocolHandler.processMessage(message, local);
|
|
1207
1169
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
1208
1170
|
if (this.activeConnection()) {
|
|
1209
1171
|
if (this.collabWindowTracker === undefined) {
|
|
@@ -1211,35 +1173,17 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1211
1173
|
// That means that if relay service changes settings, such changes will impact only newly booted
|
|
1212
1174
|
// clients.
|
|
1213
1175
|
// All existing will continue to use settings they got earlier.
|
|
1214
|
-
|
|
1176
|
+
assert(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
|
|
1215
1177
|
this.collabWindowTracker = new CollabWindowTracker((type, contents) => {
|
|
1216
1178
|
assert(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
|
|
1217
1179
|
this.submitMessage(type, contents);
|
|
1218
|
-
}, noopTimeFrequency, noopCountFrequency);
|
|
1180
|
+
}, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
|
|
1219
1181
|
}
|
|
1220
1182
|
this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
|
|
1221
1183
|
}
|
|
1222
1184
|
this.emit("op", message);
|
|
1223
1185
|
return result;
|
|
1224
1186
|
}
|
|
1225
|
-
/**
|
|
1226
|
-
* #260 (ADO)
|
|
1227
|
-
* back-compat: noopTimeFrequency & noopCountFrequency properties were added to
|
|
1228
|
-
* IClientConfiguration in 0.59.3000. During the integration, we must read the
|
|
1229
|
-
* available configuration from the loader options.
|
|
1230
|
-
*/
|
|
1231
|
-
getNoopConfig() {
|
|
1232
|
-
var _a, _b;
|
|
1233
|
-
assert(this.serviceConfiguration !== undefined, 0x2e2);
|
|
1234
|
-
if (this.serviceConfiguration.noopTimeFrequency !== undefined ||
|
|
1235
|
-
this.serviceConfiguration.noopCountFrequency !== undefined) {
|
|
1236
|
-
return [
|
|
1237
|
-
this.serviceConfiguration.noopTimeFrequency,
|
|
1238
|
-
this.serviceConfiguration.noopCountFrequency,
|
|
1239
|
-
];
|
|
1240
|
-
}
|
|
1241
|
-
return [(_a = this.loader.services.options) === null || _a === void 0 ? void 0 : _a.noopTimeFrequency, (_b = this.loader.services.options) === null || _b === void 0 ? void 0 : _b.noopCountFrequency];
|
|
1242
|
-
}
|
|
1243
1187
|
submitSignal(message) {
|
|
1244
1188
|
this._deltaManager.submitSignal(JSON.stringify(message));
|
|
1245
1189
|
}
|
|
@@ -1280,12 +1224,12 @@ export class Container extends EventEmitterWithErrorHandling {
|
|
|
1280
1224
|
}
|
|
1281
1225
|
return { snapshot, versionId: version === null || version === void 0 ? void 0 : version.id };
|
|
1282
1226
|
}
|
|
1283
|
-
async instantiateContextDetached(existing, snapshot
|
|
1227
|
+
async instantiateContextDetached(existing, snapshot) {
|
|
1284
1228
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1285
1229
|
if (codeDetails === undefined) {
|
|
1286
1230
|
throw new Error("pkg should be provided in create flow!!");
|
|
1287
1231
|
}
|
|
1288
|
-
await this.instantiateContext(existing, codeDetails, snapshot
|
|
1232
|
+
await this.instantiateContext(existing, codeDetails, snapshot);
|
|
1289
1233
|
}
|
|
1290
1234
|
async instantiateContext(existing, codeDetails, snapshot, pendingLocalState) {
|
|
1291
1235
|
var _a;
|