@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/src/container.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import merge from "lodash/merge";
|
|
8
8
|
import { v4 as uuid } from "uuid";
|
|
9
9
|
import {
|
|
10
|
-
IDisposable,
|
|
10
|
+
IDisposable, ITelemetryProperties,
|
|
11
11
|
} from "@fluidframework/common-definitions";
|
|
12
12
|
import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
|
|
13
13
|
import {
|
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
ContainerWarning,
|
|
26
26
|
AttachState,
|
|
27
27
|
IThrottlingWarning,
|
|
28
|
-
IPendingLocalState,
|
|
29
28
|
ReadOnlyInfo,
|
|
30
29
|
IContainerLoadMode,
|
|
31
30
|
IFluidCodeDetails,
|
|
@@ -54,7 +53,8 @@ import {
|
|
|
54
53
|
} from "@fluidframework/driver-utils";
|
|
55
54
|
import {
|
|
56
55
|
isSystemMessage,
|
|
57
|
-
|
|
56
|
+
IProtocolHandler,
|
|
57
|
+
ProtocolOpHandlerWithClientValidation,
|
|
58
58
|
} from "@fluidframework/protocol-base";
|
|
59
59
|
import {
|
|
60
60
|
IClient,
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
IDocumentAttributes,
|
|
65
65
|
IDocumentMessage,
|
|
66
66
|
IProcessMessageResult,
|
|
67
|
+
IProtocolState,
|
|
67
68
|
IQuorumClients,
|
|
68
69
|
IQuorumProposals,
|
|
69
70
|
ISequencedClient,
|
|
@@ -98,7 +99,7 @@ import { DeltaManager, IConnectionArgs } from "./deltaManager";
|
|
|
98
99
|
import { DeltaManagerProxy } from "./deltaManagerProxy";
|
|
99
100
|
import { ILoaderOptions, Loader, RelativeLoader } from "./loader";
|
|
100
101
|
import { pkgVersion } from "./packageVersion";
|
|
101
|
-
import { ConnectionStateHandler
|
|
102
|
+
import { ConnectionStateHandler } from "./connectionStateHandler";
|
|
102
103
|
import { RetriableDocumentStorageService } from "./retriableDocumentStorageService";
|
|
103
104
|
import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService";
|
|
104
105
|
import { BlobOnlyStorage, ContainerStorageAdapter } from "./containerStorageAdapter";
|
|
@@ -140,6 +141,10 @@ export interface IContainerConfig {
|
|
|
140
141
|
* Client details provided in the override will be merged over the default client.
|
|
141
142
|
*/
|
|
142
143
|
clientDetailsOverride?: IClientDetails;
|
|
144
|
+
/**
|
|
145
|
+
* Serialized state from a previous instance of this container
|
|
146
|
+
*/
|
|
147
|
+
serializedContainerState?: IPendingContainerState;
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
/**
|
|
@@ -147,7 +152,7 @@ export interface IContainerConfig {
|
|
|
147
152
|
* Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
|
|
148
153
|
* up to date. Host may chose to wait in such case and retry resolving URI.
|
|
149
154
|
* Warning: Will wait infinitely for connection to establish if there is no connection.
|
|
150
|
-
* May result in deadlock if Container.
|
|
155
|
+
* May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
|
|
151
156
|
* @returns true: container is up to date, it processed all the ops that were know at the time of first connection
|
|
152
157
|
* false: storage does not provide indication of how far the client is. Container processed
|
|
153
158
|
* all the ops known to it, but it maybe still behind.
|
|
@@ -174,7 +179,8 @@ export async function waitContainerToCatchUp(container: IContainer) {
|
|
|
174
179
|
container.on("closed", closedCallback);
|
|
175
180
|
|
|
176
181
|
const waitForOps = () => {
|
|
177
|
-
assert(container.connectionState
|
|
182
|
+
assert(container.connectionState === ConnectionState.CatchingUp
|
|
183
|
+
|| container.connectionState === ConnectionState.Connected,
|
|
178
184
|
0x0cd /* "Container disconnected while waiting for ops!" */);
|
|
179
185
|
const hasCheckpointSequenceNumber = deltaManager.hasCheckpointSequenceNumber;
|
|
180
186
|
|
|
@@ -211,9 +217,7 @@ export async function waitContainerToCatchUp(container: IContainer) {
|
|
|
211
217
|
};
|
|
212
218
|
container.on(connectedEventName, callback);
|
|
213
219
|
|
|
214
|
-
|
|
215
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
216
|
-
container.resume!();
|
|
220
|
+
container.connect();
|
|
217
221
|
});
|
|
218
222
|
}
|
|
219
223
|
|
|
@@ -221,6 +225,18 @@ const getCodeProposal =
|
|
|
221
225
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
222
226
|
(quorum: IQuorumProposals) => quorum.get("code") ?? quorum.get("code2");
|
|
223
227
|
|
|
228
|
+
/**
|
|
229
|
+
* State saved by a container at close time, to be used to load a new instance
|
|
230
|
+
* of the container to the same state
|
|
231
|
+
*/
|
|
232
|
+
export interface IPendingContainerState {
|
|
233
|
+
pendingRuntimeState: unknown;
|
|
234
|
+
url: string;
|
|
235
|
+
protocol: IProtocolState;
|
|
236
|
+
term: number;
|
|
237
|
+
clientId?: string;
|
|
238
|
+
}
|
|
239
|
+
|
|
224
240
|
const summarizerClientType = "summarizer";
|
|
225
241
|
|
|
226
242
|
export class Container extends EventEmitterWithErrorHandling<IContainerEvents> implements IContainer {
|
|
@@ -232,7 +248,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
232
248
|
public static async load(
|
|
233
249
|
loader: Loader,
|
|
234
250
|
loadOptions: IContainerLoadOptions,
|
|
235
|
-
pendingLocalState?:
|
|
251
|
+
pendingLocalState?: IPendingContainerState,
|
|
236
252
|
): Promise<Container> {
|
|
237
253
|
const container = new Container(
|
|
238
254
|
loader,
|
|
@@ -240,21 +256,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
240
256
|
clientDetailsOverride: loadOptions.clientDetailsOverride,
|
|
241
257
|
resolvedUrl: loadOptions.resolvedUrl,
|
|
242
258
|
canReconnect: loadOptions.canReconnect,
|
|
259
|
+
serializedContainerState: pendingLocalState,
|
|
243
260
|
});
|
|
244
261
|
|
|
245
262
|
return PerformanceEvent.timedExecAsync(
|
|
246
263
|
container.mc.logger,
|
|
247
264
|
{ eventName: "Load" },
|
|
248
265
|
async (event) => new Promise<Container>((resolve, reject) => {
|
|
249
|
-
container._lifecycleState = "loading";
|
|
250
266
|
const version = loadOptions.version;
|
|
251
267
|
|
|
252
|
-
// always load unpaused with pending ops!
|
|
253
|
-
// It is also default mode in general.
|
|
254
268
|
const defaultMode: IContainerLoadMode = { opsBeforeReturn: "cached" };
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const mode: IContainerLoadMode =
|
|
269
|
+
// if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
|
|
270
|
+
// to return container, so ignore this value and use undefined for opsBeforeReturn
|
|
271
|
+
const mode: IContainerLoadMode = pendingLocalState
|
|
272
|
+
? { ...(loadOptions.loadMode ?? defaultMode), opsBeforeReturn: undefined }
|
|
273
|
+
: loadOptions.loadMode ?? defaultMode;
|
|
258
274
|
|
|
259
275
|
const onClosed = (err?: ICriticalContainerError) => {
|
|
260
276
|
// pre-0.58 error message: containerClosedWithoutErrorDuringLoad
|
|
@@ -298,7 +314,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
298
314
|
container.mc.logger,
|
|
299
315
|
{ eventName: "CreateDetached" },
|
|
300
316
|
async (_event) => {
|
|
301
|
-
container._lifecycleState = "loading";
|
|
302
317
|
await container.createDetached(codeDetails);
|
|
303
318
|
return container;
|
|
304
319
|
},
|
|
@@ -321,7 +336,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
321
336
|
{ eventName: "RehydrateDetachedFromSnapshot" },
|
|
322
337
|
async (_event) => {
|
|
323
338
|
const deserializedSummary = JSON.parse(snapshot) as ISummaryTree;
|
|
324
|
-
container._lifecycleState = "loading";
|
|
325
339
|
await container.rehydrateDetachedFromSnapshot(deserializedSummary);
|
|
326
340
|
return container;
|
|
327
341
|
},
|
|
@@ -336,19 +350,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
336
350
|
|
|
337
351
|
private readonly mc: MonitoringContext;
|
|
338
352
|
|
|
339
|
-
private _lifecycleState: "
|
|
340
|
-
|
|
341
|
-
private get loaded(): boolean {
|
|
342
|
-
return (this._lifecycleState !== "created" && this._lifecycleState !== "loading");
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
private set loaded(t: boolean) {
|
|
346
|
-
assert(t, 0x27d /* "Setting loaded state to false is not supported" */);
|
|
347
|
-
assert(this._lifecycleState !== "created", 0x27e /* "Must go through loading state before loaded" */);
|
|
353
|
+
private _lifecycleState: "loading" | "loaded" | "closing" | "closed" = "loading";
|
|
348
354
|
|
|
355
|
+
private setLoaded() {
|
|
349
356
|
// It's conceivable the container could be closed when this is called
|
|
350
357
|
// Only transition states if currently loading
|
|
351
358
|
if (this._lifecycleState === "loading") {
|
|
359
|
+
// Propagate current connection state through the system.
|
|
360
|
+
this.propagateConnectionState();
|
|
352
361
|
this._lifecycleState = "loaded";
|
|
353
362
|
}
|
|
354
363
|
}
|
|
@@ -384,7 +393,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
384
393
|
}
|
|
385
394
|
return this._context;
|
|
386
395
|
}
|
|
387
|
-
private _protocolHandler:
|
|
396
|
+
private _protocolHandler: IProtocolHandler | undefined;
|
|
388
397
|
private get protocolHandler() {
|
|
389
398
|
if (this._protocolHandler === undefined) {
|
|
390
399
|
throw new Error("Attempted to access protocolHandler before it was defined");
|
|
@@ -592,21 +601,24 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
592
601
|
this.logConnectionStateChangeTelemetry(value, oldState, reason),
|
|
593
602
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
594
603
|
maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
|
|
595
|
-
logConnectionIssue: (eventName: string) => {
|
|
604
|
+
logConnectionIssue: (eventName: string, details?: ITelemetryProperties) => {
|
|
596
605
|
// We get here when socket does not receive any ops on "write" connection, including
|
|
597
606
|
// its own join op. Attempt recovery option.
|
|
598
607
|
this._deltaManager.logConnectionIssue({
|
|
599
608
|
eventName,
|
|
600
|
-
duration: performance.now() - this.connectionTransitionTimes[ConnectionState.
|
|
609
|
+
duration: performance.now() - this.connectionTransitionTimes[ConnectionState.CatchingUp],
|
|
610
|
+
...(details === undefined ? {} : { details: JSON.stringify(details) }),
|
|
601
611
|
});
|
|
602
612
|
},
|
|
603
613
|
connectionStateChanged: () => {
|
|
604
|
-
if
|
|
614
|
+
// Fire events only if container is fully loaded and not closed
|
|
615
|
+
if (this._lifecycleState === "loaded") {
|
|
605
616
|
this.propagateConnectionState();
|
|
606
617
|
}
|
|
607
618
|
},
|
|
608
619
|
},
|
|
609
620
|
this.mc.logger,
|
|
621
|
+
config.serializedContainerState?.clientId,
|
|
610
622
|
);
|
|
611
623
|
|
|
612
624
|
this.on(savedContainerEvent, () => {
|
|
@@ -691,16 +703,34 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
691
703
|
}
|
|
692
704
|
|
|
693
705
|
public close(error?: ICriticalContainerError) {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
706
|
+
// 1. Ensure that close sequence is exactly the same no matter if it's initiated by host or by DeltaManager
|
|
707
|
+
// 2. We need to ensure that we deliver disconnect event to runtime properly. See connectionStateChanged
|
|
708
|
+
// handler. We only deliver events if container fully loaded. Transitioning from "loading" ->
|
|
709
|
+
// "closing" will lose that info (can also solve by tracking extra state).
|
|
710
|
+
this._deltaManager.close(error);
|
|
711
|
+
assert(this.connectionState === ConnectionState.Disconnected,
|
|
712
|
+
0x0cf /* "disconnect event was not raised!" */);
|
|
697
713
|
|
|
698
|
-
|
|
699
|
-
|
|
714
|
+
assert(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
private closeCore(error?: ICriticalContainerError) {
|
|
718
|
+
assert(!this.closed, 0x315 /* re-entrancy */);
|
|
700
719
|
|
|
720
|
+
try {
|
|
701
721
|
// Ensure that we raise all key events even if one of these throws
|
|
702
722
|
try {
|
|
703
|
-
|
|
723
|
+
// Raise event first, to ensure we capture _lifecycleState before transition.
|
|
724
|
+
// This gives us a chance to know what errors happened on open vs. on fully loaded container.
|
|
725
|
+
this.mc.logger.sendTelemetryEvent(
|
|
726
|
+
{
|
|
727
|
+
eventName: "ContainerClose",
|
|
728
|
+
category: error === undefined ? "generic" : "error",
|
|
729
|
+
},
|
|
730
|
+
error,
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
this._lifecycleState = "closing";
|
|
704
734
|
|
|
705
735
|
this._protocolHandler?.close();
|
|
706
736
|
|
|
@@ -708,9 +738,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
708
738
|
|
|
709
739
|
this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
|
|
710
740
|
|
|
711
|
-
assert(this.connectionState === ConnectionState.Disconnected,
|
|
712
|
-
0x0cf /* "disconnect event was not raised!" */);
|
|
713
|
-
|
|
714
741
|
this._storageService?.dispose();
|
|
715
742
|
|
|
716
743
|
// Notify storage about critical errors. They may be due to disconnect between client & server knowledge
|
|
@@ -721,14 +748,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
721
748
|
this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
|
|
722
749
|
}
|
|
723
750
|
|
|
724
|
-
this.mc.logger.sendTelemetryEvent(
|
|
725
|
-
{
|
|
726
|
-
eventName: "ContainerClose",
|
|
727
|
-
category: error === undefined ? "generic" : "error",
|
|
728
|
-
},
|
|
729
|
-
error,
|
|
730
|
-
);
|
|
731
|
-
|
|
732
751
|
this.emit("closed", error);
|
|
733
752
|
|
|
734
753
|
this.removeAllListeners();
|
|
@@ -748,9 +767,15 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
748
767
|
assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
|
|
749
768
|
assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
|
|
750
769
|
0x0d2 /* "resolved url should be valid Fluid url" */);
|
|
751
|
-
|
|
770
|
+
assert(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
|
|
771
|
+
assert(this._protocolHandler.attributes.term !== undefined,
|
|
772
|
+
0x30b /* Must have a valid protocol handler instance */);
|
|
773
|
+
const pendingState: IPendingContainerState = {
|
|
752
774
|
pendingRuntimeState: this.context.getPendingLocalState(),
|
|
753
775
|
url: this.resolvedUrl.url,
|
|
776
|
+
protocol: this.protocolHandler.getProtocolState(),
|
|
777
|
+
term: this._protocolHandler.attributes.term,
|
|
778
|
+
clientId: this.clientId,
|
|
754
779
|
};
|
|
755
780
|
|
|
756
781
|
this.close();
|
|
@@ -808,7 +833,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
808
833
|
// starting to attach the container to storage.
|
|
809
834
|
// Also, this should only be fired in detached container.
|
|
810
835
|
this._attachState = AttachState.Attaching;
|
|
811
|
-
this.context.notifyAttaching();
|
|
836
|
+
this.context.notifyAttaching(getSnapshotTreeFromSerializedContainer(summary));
|
|
812
837
|
}
|
|
813
838
|
|
|
814
839
|
// Actually go and create the resolved document
|
|
@@ -860,7 +885,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
860
885
|
summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
|
|
861
886
|
|
|
862
887
|
this._attachState = AttachState.Attaching;
|
|
863
|
-
this.context.notifyAttaching();
|
|
888
|
+
this.context.notifyAttaching(getSnapshotTreeFromSerializedContainer(summary));
|
|
864
889
|
|
|
865
890
|
await this.storageService.uploadSummaryWithContext(summary, {
|
|
866
891
|
referenceSequenceNumber: 0,
|
|
@@ -900,30 +925,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
900
925
|
);
|
|
901
926
|
}
|
|
902
927
|
|
|
903
|
-
/**
|
|
904
|
-
* Dictates whether or not the current container will automatically attempt to reconnect to the delta stream
|
|
905
|
-
* after receiving a disconnect event
|
|
906
|
-
* @param reconnect - Boolean indicating if reconnect should automatically occur
|
|
907
|
-
* @deprecated - 0.58, This API will be removed in 1.0
|
|
908
|
-
* Use `connect()` and `disconnect()` instead of `setAutoReconnect(true)` and `setAutoReconnect(false)` respectively
|
|
909
|
-
* See https://github.com/microsoft/FluidFramework/issues/9167 for context
|
|
910
|
-
*/
|
|
911
|
-
public setAutoReconnect(reconnect: boolean) {
|
|
912
|
-
if (this.closed) {
|
|
913
|
-
throw new Error("Attempting to setAutoReconnect() a closed Container");
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
const mode = reconnect ? ReconnectMode.Enabled : ReconnectMode.Disabled;
|
|
917
|
-
this.setAutoReconnectInternal(mode);
|
|
918
|
-
|
|
919
|
-
// If container state is not attached and resumed, then don't connect to delta stream. Also don't set the
|
|
920
|
-
// manual reconnection flag to true as we haven't made the initial connection yet.
|
|
921
|
-
if (reconnect && this._attachState === AttachState.Attached && this.resumedOpProcessingAfterLoad) {
|
|
922
|
-
// Ensure connection to web socket
|
|
923
|
-
this.connectToDeltaStream({ reason: "autoReconnect" });
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
928
|
private setAutoReconnectInternal(mode: ReconnectMode) {
|
|
928
929
|
const currentMode = this._deltaManager.connectionManager.reconnectMode;
|
|
929
930
|
|
|
@@ -987,23 +988,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
987
988
|
this.setAutoReconnectInternal(mode);
|
|
988
989
|
}
|
|
989
990
|
|
|
990
|
-
/**
|
|
991
|
-
* Have the container attempt to resume processing ops
|
|
992
|
-
* @deprecated - 0.58, This API will be removed in 1.0
|
|
993
|
-
* Use `connect()` instead
|
|
994
|
-
* See https://github.com/microsoft/FluidFramework/issues/9167 for context
|
|
995
|
-
*/
|
|
996
|
-
public resume() {
|
|
997
|
-
if (!this.closed) {
|
|
998
|
-
// Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
|
|
999
|
-
// If there is gap, we will learn about it once connected, but the gap should be small (if any),
|
|
1000
|
-
// assuming that resume() is called quickly after initial container boot.
|
|
1001
|
-
this.resumeInternal({ reason: "DocumentOpenResume", fetchOpsFromStorage: false });
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
991
|
private resumeInternal(args: IConnectionArgs) {
|
|
1006
|
-
assert(!this.closed, 0x0d9 /* "Attempting to
|
|
992
|
+
assert(!this.closed, 0x0d9 /* "Attempting to connect() a closed DeltaManager" */);
|
|
1007
993
|
|
|
1008
994
|
// Resume processing ops
|
|
1009
995
|
if (!this.resumedOpProcessingAfterLoad) {
|
|
@@ -1095,7 +1081,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1095
1081
|
private async load(
|
|
1096
1082
|
specifiedVersion: string | undefined,
|
|
1097
1083
|
loadMode: IContainerLoadMode,
|
|
1098
|
-
pendingLocalState?:
|
|
1084
|
+
pendingLocalState?: IPendingContainerState,
|
|
1099
1085
|
) {
|
|
1100
1086
|
if (this._resolvedUrl === undefined) {
|
|
1101
1087
|
throw new Error("Attempting to load without a resolved url");
|
|
@@ -1123,14 +1109,28 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1123
1109
|
this.connectToDeltaStream(connectionArgs);
|
|
1124
1110
|
}
|
|
1125
1111
|
|
|
1126
|
-
|
|
1112
|
+
if (!pendingLocalState) {
|
|
1113
|
+
await this.connectStorageService();
|
|
1114
|
+
} else {
|
|
1115
|
+
// if we have pendingLocalState we can load without storage; don't wait for connection
|
|
1116
|
+
this.connectStorageService().catch((error) => this.close(error));
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1127
1119
|
this._attachState = AttachState.Attached;
|
|
1128
1120
|
|
|
1129
1121
|
// Fetch specified snapshot.
|
|
1130
|
-
const { snapshot, versionId } =
|
|
1131
|
-
|
|
1122
|
+
const { snapshot, versionId } = pendingLocalState === undefined
|
|
1123
|
+
? await this.fetchSnapshotTree(specifiedVersion)
|
|
1124
|
+
: { snapshot: undefined, versionId: undefined };
|
|
1125
|
+
assert(snapshot !== undefined || pendingLocalState !== undefined, 0x237 /* "Snapshot should exist" */);
|
|
1132
1126
|
|
|
1133
|
-
const attributes =
|
|
1127
|
+
const attributes: IDocumentAttributes = pendingLocalState === undefined
|
|
1128
|
+
? await this.getDocumentAttributes(this.storageService, snapshot)
|
|
1129
|
+
: {
|
|
1130
|
+
sequenceNumber: pendingLocalState.protocol.sequenceNumber,
|
|
1131
|
+
minimumSequenceNumber: pendingLocalState.protocol.minimumSequenceNumber,
|
|
1132
|
+
term: pendingLocalState.term,
|
|
1133
|
+
};
|
|
1134
1134
|
|
|
1135
1135
|
let opsBeforeReturnP: Promise<void> | undefined;
|
|
1136
1136
|
|
|
@@ -1154,22 +1154,25 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1154
1154
|
|
|
1155
1155
|
// ...load in the existing quorum
|
|
1156
1156
|
// Initialize the protocol handler
|
|
1157
|
-
this._protocolHandler =
|
|
1158
|
-
await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot)
|
|
1157
|
+
this._protocolHandler = pendingLocalState === undefined
|
|
1158
|
+
? await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot)
|
|
1159
|
+
: await this.initializeProtocolState(
|
|
1160
|
+
attributes,
|
|
1161
|
+
pendingLocalState.protocol.members,
|
|
1162
|
+
pendingLocalState.protocol.proposals,
|
|
1163
|
+
pendingLocalState.protocol.values,
|
|
1164
|
+
);
|
|
1159
1165
|
|
|
1160
1166
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1161
1167
|
await this.instantiateContext(
|
|
1162
1168
|
true, // existing
|
|
1163
1169
|
codeDetails,
|
|
1164
1170
|
snapshot,
|
|
1165
|
-
pendingLocalState,
|
|
1171
|
+
pendingLocalState?.pendingRuntimeState,
|
|
1166
1172
|
);
|
|
1167
1173
|
|
|
1168
|
-
// Propagate current connection state through the system.
|
|
1169
|
-
this.propagateConnectionState();
|
|
1170
|
-
|
|
1171
1174
|
// Internal context is fully loaded at this point
|
|
1172
|
-
this.
|
|
1175
|
+
this.setLoaded();
|
|
1173
1176
|
|
|
1174
1177
|
// We might have hit some failure that did not manifest itself in exception in this flow,
|
|
1175
1178
|
// do not start op processing in such case - static version of Container.load() will handle it correctly.
|
|
@@ -1186,7 +1189,10 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1186
1189
|
|
|
1187
1190
|
switch (loadMode.deltaConnection) {
|
|
1188
1191
|
case undefined:
|
|
1189
|
-
|
|
1192
|
+
// Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
|
|
1193
|
+
// If there is gap, we will learn about it once connected, but the gap should be small (if any),
|
|
1194
|
+
// assuming that resumeInternal() is called quickly after initial container boot.
|
|
1195
|
+
this.resumeInternal({ reason: "DocumentLoad", fetchOpsFromStorage: false });
|
|
1190
1196
|
break;
|
|
1191
1197
|
case "delayed":
|
|
1192
1198
|
this.resumedOpProcessingAfterLoad = true;
|
|
@@ -1238,9 +1244,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1238
1244
|
false, // existing
|
|
1239
1245
|
);
|
|
1240
1246
|
|
|
1241
|
-
this.
|
|
1242
|
-
|
|
1243
|
-
this.loaded = true;
|
|
1247
|
+
this.setLoaded();
|
|
1244
1248
|
}
|
|
1245
1249
|
|
|
1246
1250
|
private async rehydrateDetachedFromSnapshot(detachedContainerSnapshot: ISummaryTree) {
|
|
@@ -1275,9 +1279,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1275
1279
|
snapshotTree,
|
|
1276
1280
|
);
|
|
1277
1281
|
|
|
1278
|
-
this.
|
|
1279
|
-
|
|
1280
|
-
this.propagateConnectionState();
|
|
1282
|
+
this.setLoaded();
|
|
1281
1283
|
}
|
|
1282
1284
|
|
|
1283
1285
|
private async connectStorageService(): Promise<void> {
|
|
@@ -1333,7 +1335,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1333
1335
|
attributes: IDocumentAttributes,
|
|
1334
1336
|
storage: IDocumentStorageService,
|
|
1335
1337
|
snapshot: ISnapshotTree | undefined,
|
|
1336
|
-
): Promise<
|
|
1338
|
+
): Promise<IProtocolHandler> {
|
|
1337
1339
|
let members: [string, ISequencedClient][] = [];
|
|
1338
1340
|
let proposals: [number, ISequencedProposal, string[]][] = [];
|
|
1339
1341
|
let values: [string, any][] = [];
|
|
@@ -1361,8 +1363,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1361
1363
|
members: [string, ISequencedClient][],
|
|
1362
1364
|
proposals: [number, ISequencedProposal, string[]][],
|
|
1363
1365
|
values: [string, any][],
|
|
1364
|
-
): Promise<
|
|
1365
|
-
const protocol = new
|
|
1366
|
+
): Promise<IProtocolHandler> {
|
|
1367
|
+
const protocol = new ProtocolOpHandlerWithClientValidation(
|
|
1366
1368
|
attributes.minimumSequenceNumber,
|
|
1367
1369
|
attributes.sequenceNumber,
|
|
1368
1370
|
attributes.term,
|
|
@@ -1379,13 +1381,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1379
1381
|
});
|
|
1380
1382
|
|
|
1381
1383
|
// Track membership changes and update connection state accordingly
|
|
1382
|
-
|
|
1383
|
-
this.connectionStateHandler.receivedAddMemberEvent(clientId);
|
|
1384
|
-
});
|
|
1385
|
-
|
|
1386
|
-
protocol.quorum.on("removeMember", (clientId) => {
|
|
1387
|
-
this.connectionStateHandler.receivedRemoveMemberEvent(clientId);
|
|
1388
|
-
});
|
|
1384
|
+
this.connectionStateHandler.initProtocol(protocol);
|
|
1389
1385
|
|
|
1390
1386
|
protocol.quorum.on("addProposal", (proposal: ISequencedProposal) => {
|
|
1391
1387
|
if (proposal.key === "code" || proposal.key === "code2") {
|
|
@@ -1413,19 +1409,11 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1413
1409
|
}
|
|
1414
1410
|
|
|
1415
1411
|
private captureProtocolSummary(): ISummaryTree {
|
|
1416
|
-
const quorumSnapshot = this.protocolHandler.
|
|
1417
|
-
|
|
1418
|
-
// Save attributes for the document
|
|
1419
|
-
const documentAttributes: IDocumentAttributes = {
|
|
1420
|
-
minimumSequenceNumber: this.protocolHandler.minimumSequenceNumber,
|
|
1421
|
-
sequenceNumber: this.protocolHandler.sequenceNumber,
|
|
1422
|
-
term: this.protocolHandler.term,
|
|
1423
|
-
};
|
|
1424
|
-
|
|
1412
|
+
const quorumSnapshot = this.protocolHandler.snapshot();
|
|
1425
1413
|
const summary: ISummaryTree = {
|
|
1426
1414
|
tree: {
|
|
1427
1415
|
attributes: {
|
|
1428
|
-
content: JSON.stringify(
|
|
1416
|
+
content: JSON.stringify(this.protocolHandler.attributes),
|
|
1429
1417
|
type: SummaryType.Blob,
|
|
1430
1418
|
},
|
|
1431
1419
|
quorumMembers: {
|
|
@@ -1540,7 +1528,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1540
1528
|
});
|
|
1541
1529
|
|
|
1542
1530
|
deltaManager.on("closed", (error?: ICriticalContainerError) => {
|
|
1543
|
-
this.
|
|
1531
|
+
this.closeCore(error);
|
|
1544
1532
|
});
|
|
1545
1533
|
|
|
1546
1534
|
return deltaManager;
|
|
@@ -1611,6 +1599,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1611
1599
|
online: OnlineStatus[isOnline()],
|
|
1612
1600
|
lastVisible: this.lastVisible !== undefined ? performance.now() - this.lastVisible : undefined,
|
|
1613
1601
|
checkpointSequenceNumber,
|
|
1602
|
+
quorumSize: this._protocolHandler?.quorum.getMembers().size,
|
|
1614
1603
|
...this._deltaManager.connectionProps,
|
|
1615
1604
|
});
|
|
1616
1605
|
|
|
@@ -1629,11 +1618,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1629
1618
|
}
|
|
1630
1619
|
|
|
1631
1620
|
const state = this.connectionState === ConnectionState.Connected;
|
|
1632
|
-
|
|
1621
|
+
|
|
1622
|
+
// Both protocol and context should not be undefined if we got so far.
|
|
1623
|
+
|
|
1624
|
+
if (this._context?.disposed === false) {
|
|
1633
1625
|
this.context.setConnectionState(state, this.clientId);
|
|
1634
1626
|
}
|
|
1635
|
-
|
|
1636
|
-
this.protocolHandler.quorum.setConnectionState(state, this.clientId);
|
|
1627
|
+
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1637
1628
|
raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
|
|
1638
1629
|
|
|
1639
1630
|
if (logOpsOnReconnect) {
|
|
@@ -1682,36 +1673,22 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1682
1673
|
}
|
|
1683
1674
|
|
|
1684
1675
|
private processRemoteMessage(message: ISequencedDocumentMessage): IProcessMessageResult {
|
|
1685
|
-
// Check and report if we're getting messages from a clientId that we previously
|
|
1686
|
-
// flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
|
|
1687
|
-
if (message.clientId != null) {
|
|
1688
|
-
let errorMsg: string | undefined;
|
|
1689
|
-
const client: ILocalSequencedClient | undefined =
|
|
1690
|
-
this.getQuorum().getMember(message.clientId);
|
|
1691
|
-
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
1692
|
-
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
1693
|
-
errorMsg = "Remote message's clientId is missing from the quorum";
|
|
1694
|
-
} else if (client?.shouldHaveLeft === true && message.type !== MessageType.NoOp) {
|
|
1695
|
-
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
1696
|
-
errorMsg = "Remote message's clientId already should have left";
|
|
1697
|
-
}
|
|
1698
|
-
if (errorMsg !== undefined) {
|
|
1699
|
-
const error = new DataCorruptionError(
|
|
1700
|
-
errorMsg,
|
|
1701
|
-
extractSafePropertiesFromMessage(message));
|
|
1702
|
-
this.close(normalizeError(error));
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
|
|
1706
1676
|
const local = this.clientId === message.clientId;
|
|
1707
1677
|
|
|
1678
|
+
// Allow the protocol handler to process the message
|
|
1679
|
+
let result: IProcessMessageResult = { immediateNoOp: false };
|
|
1680
|
+
try {
|
|
1681
|
+
result = this.protocolHandler.processMessage(message, local);
|
|
1682
|
+
} catch (error) {
|
|
1683
|
+
this.close(wrapError(error, (errorMessage) =>
|
|
1684
|
+
new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1708
1687
|
// Forward non system messages to the loaded runtime for processing
|
|
1709
1688
|
if (!isSystemMessage(message)) {
|
|
1710
1689
|
this.context.process(message, local, undefined);
|
|
1711
1690
|
}
|
|
1712
1691
|
|
|
1713
|
-
// Allow the protocol handler to process the message
|
|
1714
|
-
const result = this.protocolHandler.processMessage(message, local);
|
|
1715
1692
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
1716
1693
|
if (this.activeConnection()) {
|
|
1717
1694
|
if (this.collabWindowTracker === undefined) {
|
|
@@ -1719,15 +1696,17 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1719
1696
|
// That means that if relay service changes settings, such changes will impact only newly booted
|
|
1720
1697
|
// clients.
|
|
1721
1698
|
// All existing will continue to use settings they got earlier.
|
|
1722
|
-
|
|
1699
|
+
assert(
|
|
1700
|
+
this.serviceConfiguration !== undefined,
|
|
1701
|
+
0x2e4 /* "there should be service config for active connection" */);
|
|
1723
1702
|
this.collabWindowTracker = new CollabWindowTracker(
|
|
1724
1703
|
(type, contents) => {
|
|
1725
1704
|
assert(this.activeConnection(),
|
|
1726
1705
|
0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
|
|
1727
1706
|
this.submitMessage(type, contents);
|
|
1728
1707
|
},
|
|
1729
|
-
noopTimeFrequency,
|
|
1730
|
-
noopCountFrequency,
|
|
1708
|
+
this.serviceConfiguration.noopTimeFrequency,
|
|
1709
|
+
this.serviceConfiguration.noopCountFrequency,
|
|
1731
1710
|
);
|
|
1732
1711
|
}
|
|
1733
1712
|
this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
|
|
@@ -1738,29 +1717,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1738
1717
|
return result;
|
|
1739
1718
|
}
|
|
1740
1719
|
|
|
1741
|
-
/**
|
|
1742
|
-
* #260 (ADO)
|
|
1743
|
-
* back-compat: noopTimeFrequency & noopCountFrequency properties were added to
|
|
1744
|
-
* IClientConfiguration in 0.59.3000. During the integration, we must read the
|
|
1745
|
-
* available configuration from the loader options.
|
|
1746
|
-
*/
|
|
1747
|
-
private getNoopConfig(): [number | undefined, number | undefined] {
|
|
1748
|
-
assert(
|
|
1749
|
-
this.serviceConfiguration !== undefined,
|
|
1750
|
-
0x2e2, /* "there should be service config for active connection" */
|
|
1751
|
-
);
|
|
1752
|
-
|
|
1753
|
-
if (this.serviceConfiguration.noopTimeFrequency !== undefined ||
|
|
1754
|
-
this.serviceConfiguration.noopCountFrequency !== undefined) {
|
|
1755
|
-
return [
|
|
1756
|
-
this.serviceConfiguration.noopTimeFrequency as number,
|
|
1757
|
-
this.serviceConfiguration.noopCountFrequency as number,
|
|
1758
|
-
];
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
return [this.loader.services.options?.noopTimeFrequency, this.loader.services.options?.noopCountFrequency];
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
1720
|
private submitSignal(message: any) {
|
|
1765
1721
|
this._deltaManager.submitSignal(JSON.stringify(message));
|
|
1766
1722
|
}
|
|
@@ -1807,7 +1763,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1807
1763
|
private async instantiateContextDetached(
|
|
1808
1764
|
existing: boolean,
|
|
1809
1765
|
snapshot?: ISnapshotTree,
|
|
1810
|
-
pendingLocalState?: unknown,
|
|
1811
1766
|
) {
|
|
1812
1767
|
const codeDetails = this.getCodeDetailsFromQuorum();
|
|
1813
1768
|
if (codeDetails === undefined) {
|
|
@@ -1818,7 +1773,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1818
1773
|
existing,
|
|
1819
1774
|
codeDetails,
|
|
1820
1775
|
snapshot,
|
|
1821
|
-
pendingLocalState,
|
|
1822
1776
|
);
|
|
1823
1777
|
}
|
|
1824
1778
|
|