@fluidframework/container-loader 1.2.2 → 2.0.0-internal.1.0.0.81589
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/dist/audience.d.ts +2 -2
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.d.ts +40 -0
- package/dist/catchUpMonitor.d.ts.map +1 -0
- package/dist/catchUpMonitor.js +74 -0
- package/dist/catchUpMonitor.js.map +1 -0
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +0 -1
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionState.d.ts +0 -5
- package/dist/connectionState.d.ts.map +1 -1
- package/dist/connectionState.js +0 -5
- package/dist/connectionState.js.map +1 -1
- package/dist/connectionStateHandler.d.ts +12 -4
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +47 -15
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +8 -6
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +82 -46
- package/dist/container.js.map +1 -1
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +6 -6
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts +4 -1
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaQueue.d.ts +9 -2
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +31 -26
- package/dist/deltaQueue.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +7 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +4 -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/protocol.d.ts +22 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +52 -0
- package/dist/protocol.js.map +1 -0
- package/lib/audience.d.ts +2 -2
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.d.ts +40 -0
- package/lib/catchUpMonitor.d.ts.map +1 -0
- package/lib/catchUpMonitor.js +69 -0
- package/lib/catchUpMonitor.js.map +1 -0
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +0 -1
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionState.d.ts +0 -5
- package/lib/connectionState.d.ts.map +1 -1
- package/lib/connectionState.js +0 -5
- package/lib/connectionState.js.map +1 -1
- package/lib/connectionStateHandler.d.ts +12 -4
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +47 -15
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +8 -6
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +82 -46
- package/lib/container.js.map +1 -1
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +6 -6
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts +4 -1
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaQueue.d.ts +9 -2
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +32 -27
- package/lib/deltaQueue.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts +7 -0
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +4 -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/protocol.d.ts +22 -0
- package/lib/protocol.d.ts.map +1 -0
- package/lib/protocol.js +48 -0
- package/lib/protocol.js.map +1 -0
- package/package.json +23 -15
- package/src/audience.ts +2 -2
- package/src/catchUpMonitor.ts +99 -0
- package/src/connectionManager.ts +0 -1
- package/src/connectionState.ts +0 -6
- package/src/connectionStateHandler.ts +55 -15
- package/src/container.ts +115 -63
- package/src/deltaManager.ts +6 -4
- package/src/deltaQueue.ts +34 -28
- package/src/index.ts +4 -0
- package/src/loader.ts +13 -2
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +96 -0
package/src/deltaQueue.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { IDeltaQueue, IDeltaQueueEvents } from "@fluidframework/container-definitions";
|
|
7
|
-
import { assert, performance,
|
|
7
|
+
import { assert, performance, TypedEventEmitter } from "@fluidframework/common-utils";
|
|
8
8
|
import Deque from "double-ended-queue";
|
|
9
9
|
|
|
10
10
|
export interface IDeltaQueueWriter<T> {
|
|
@@ -30,7 +30,7 @@ export class DeltaQueue<T>
|
|
|
30
30
|
* When processing is ongoing, holds a deferred that will resolve once processing stops.
|
|
31
31
|
* Undefined when not processing.
|
|
32
32
|
*/
|
|
33
|
-
private
|
|
33
|
+
private processingPromise: Promise<{ count: number; duration: number; }> | undefined;
|
|
34
34
|
|
|
35
35
|
public get disposed(): boolean {
|
|
36
36
|
return this.isDisposed;
|
|
@@ -48,13 +48,11 @@ export class DeltaQueue<T>
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
public get idle(): boolean {
|
|
51
|
-
return this.
|
|
51
|
+
return this.processingPromise === undefined && this.q.length === 0;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
public async waitTillProcessingDone()
|
|
55
|
-
|
|
56
|
-
return this.processingDeferred.promise;
|
|
57
|
-
}
|
|
54
|
+
public async waitTillProcessingDone() {
|
|
55
|
+
return this.processingPromise ?? { count: 0, duration: 0 };
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
/**
|
|
@@ -98,7 +96,7 @@ export class DeltaQueue<T>
|
|
|
98
96
|
this.pauseCount++;
|
|
99
97
|
// If called from within the processing loop, we are in the middle of processing an op. Return a promise
|
|
100
98
|
// that will resolve when processing has actually stopped.
|
|
101
|
-
|
|
99
|
+
await this.waitTillProcessingDone();
|
|
102
100
|
}
|
|
103
101
|
|
|
104
102
|
public resume(): void {
|
|
@@ -113,20 +111,31 @@ export class DeltaQueue<T>
|
|
|
113
111
|
* not already started.
|
|
114
112
|
*/
|
|
115
113
|
private ensureProcessing() {
|
|
116
|
-
if (
|
|
117
|
-
this.processingDeferred = new Deferred<void>();
|
|
114
|
+
if (this.anythingToProcess() && this.processingPromise === undefined) {
|
|
118
115
|
// Use a resolved promise to start the processing on a separate stack.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
this.processDeltas();
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
116
|
+
this.processingPromise = Promise.resolve().then(() => {
|
|
117
|
+
assert(this.processingPromise !== undefined, "reentrancy?");
|
|
118
|
+
const result = this.processDeltas();
|
|
119
|
+
assert(this.processingPromise !== undefined, "reentrancy?");
|
|
120
|
+
// WARNING: Do not move next line to .finally() clause!
|
|
121
|
+
// It runs async and creates a race condition where incoming ensureProcessing() call observes
|
|
122
|
+
// from previous run while previous run is over (but finally clause was not scheduled yet)
|
|
123
|
+
this.processingPromise = undefined;
|
|
124
|
+
return result;
|
|
125
|
+
}).catch((error) => {
|
|
126
|
+
this.error = error;
|
|
127
|
+
this.processingPromise = undefined;
|
|
128
|
+
this.emit("error", error);
|
|
129
|
+
return { count: 0, duration: 0 };
|
|
126
130
|
});
|
|
131
|
+
assert(this.processingPromise !== undefined, "processDeltas() should run async");
|
|
127
132
|
}
|
|
128
133
|
}
|
|
129
134
|
|
|
135
|
+
private anythingToProcess() {
|
|
136
|
+
return this.q.length !== 0 && !this.paused && this.error === undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
130
139
|
/**
|
|
131
140
|
* Executes the delta processing loop until a stop condition is reached.
|
|
132
141
|
*/
|
|
@@ -136,24 +145,21 @@ export class DeltaQueue<T>
|
|
|
136
145
|
|
|
137
146
|
// For grouping to work we must process all local messages immediately and in the single turn.
|
|
138
147
|
// So loop over them until no messages to process, we have become paused, or hit an error.
|
|
139
|
-
while (
|
|
148
|
+
while (this.anythingToProcess()) {
|
|
140
149
|
// Get the next message in the queue
|
|
141
150
|
const next = this.q.shift();
|
|
142
151
|
count++;
|
|
143
152
|
// Process the message.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this.emit("op", next);
|
|
149
|
-
} catch (error) {
|
|
150
|
-
this.error = error;
|
|
151
|
-
this.emit("error", error);
|
|
152
|
-
}
|
|
153
|
+
// We know next is defined since we did a length check just prior to shifting.
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
155
|
+
this.worker(next!);
|
|
156
|
+
this.emit("op", next);
|
|
153
157
|
}
|
|
154
158
|
|
|
159
|
+
const duration = performance.now() - start;
|
|
155
160
|
if (this.q.length === 0) {
|
|
156
|
-
this.emit("idle", count,
|
|
161
|
+
this.emit("idle", count, duration);
|
|
157
162
|
}
|
|
163
|
+
return { count, duration };
|
|
158
164
|
}
|
|
159
165
|
}
|
package/src/index.ts
CHANGED
package/src/loader.ts
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
import { Container, IPendingContainerState } from "./container";
|
|
49
49
|
import { IParsedUrl, parseUrl } from "./utils";
|
|
50
50
|
import { pkgVersion } from "./packageVersion";
|
|
51
|
+
import { ProtocolHandlerBuilder } from "./protocol";
|
|
51
52
|
|
|
52
53
|
function canUseCache(request: IRequest): boolean {
|
|
53
54
|
if (request.headers === undefined) {
|
|
@@ -211,7 +212,13 @@ export interface ILoaderProps {
|
|
|
211
212
|
/**
|
|
212
213
|
* The configuration provider which may be used to control features.
|
|
213
214
|
*/
|
|
214
|
-
|
|
215
|
+
readonly configProvider?: IConfigProviderBase;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Optional property for allowing the container to use a custom
|
|
219
|
+
* protocol implementation for handling the quorum and/or the audience.
|
|
220
|
+
*/
|
|
221
|
+
readonly protocolHandlerBuilder?: ProtocolHandlerBuilder;
|
|
215
222
|
}
|
|
216
223
|
|
|
217
224
|
/**
|
|
@@ -278,6 +285,7 @@ export class Loader implements IHostLoader {
|
|
|
278
285
|
private readonly containers = new Map<string, Promise<Container>>();
|
|
279
286
|
public readonly services: ILoaderServices;
|
|
280
287
|
private readonly mc: MonitoringContext;
|
|
288
|
+
private readonly protocolHandlerBuilder: ProtocolHandlerBuilder | undefined;
|
|
281
289
|
|
|
282
290
|
constructor(loaderProps: ILoaderProps) {
|
|
283
291
|
const scope: FluidObject<ILoader> = { ...loaderProps.scope };
|
|
@@ -306,6 +314,7 @@ export class Loader implements IHostLoader {
|
|
|
306
314
|
};
|
|
307
315
|
this.mc = loggerToMonitoringContext(
|
|
308
316
|
ChildLogger.create(this.services.subLogger, "Loader"));
|
|
317
|
+
this.protocolHandlerBuilder = loaderProps.protocolHandlerBuilder;
|
|
309
318
|
}
|
|
310
319
|
|
|
311
320
|
public get IFluidRouter(): IFluidRouter { return this; }
|
|
@@ -314,6 +323,7 @@ export class Loader implements IHostLoader {
|
|
|
314
323
|
const container = await Container.createDetached(
|
|
315
324
|
this,
|
|
316
325
|
codeDetails,
|
|
326
|
+
this.protocolHandlerBuilder,
|
|
317
327
|
);
|
|
318
328
|
|
|
319
329
|
if (this.cachingEnabled) {
|
|
@@ -330,7 +340,7 @@ export class Loader implements IHostLoader {
|
|
|
330
340
|
}
|
|
331
341
|
|
|
332
342
|
public async rehydrateDetachedContainerFromSnapshot(snapshot: string): Promise<IContainer> {
|
|
333
|
-
return Container.rehydrateDetachedFromSnapshot(this, snapshot);
|
|
343
|
+
return Container.rehydrateDetachedFromSnapshot(this, snapshot, this.protocolHandlerBuilder);
|
|
334
344
|
}
|
|
335
345
|
|
|
336
346
|
public async resolve(request: IRequest, pendingLocalState?: string): Promise<IContainer> {
|
|
@@ -482,6 +492,7 @@ export class Loader implements IHostLoader {
|
|
|
482
492
|
loadMode: request.headers?.[LoaderHeader.loadMode],
|
|
483
493
|
},
|
|
484
494
|
pendingLocalState,
|
|
495
|
+
this.protocolHandlerBuilder,
|
|
485
496
|
);
|
|
486
497
|
}
|
|
487
498
|
}
|
package/src/packageVersion.ts
CHANGED
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { IAudienceOwner } from "@fluidframework/container-definitions";
|
|
7
|
+
import {
|
|
8
|
+
ILocalSequencedClient,
|
|
9
|
+
IProtocolHandler as IBaseProtocolHandler,
|
|
10
|
+
IQuorumSnapshot,
|
|
11
|
+
ProtocolOpHandler,
|
|
12
|
+
} from "@fluidframework/protocol-base";
|
|
13
|
+
import {
|
|
14
|
+
IDocumentAttributes,
|
|
15
|
+
IProcessMessageResult,
|
|
16
|
+
ISequencedDocumentMessage,
|
|
17
|
+
ISignalClient,
|
|
18
|
+
ISignalMessage,
|
|
19
|
+
MessageType,
|
|
20
|
+
} from "@fluidframework/protocol-definitions";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Function to be used for creating a protocol handler.
|
|
24
|
+
*/
|
|
25
|
+
export type ProtocolHandlerBuilder = (
|
|
26
|
+
attributes: IDocumentAttributes,
|
|
27
|
+
snapshot: IQuorumSnapshot,
|
|
28
|
+
sendProposal: (key: string, value: any) => number,
|
|
29
|
+
initialClients: ISignalClient[],
|
|
30
|
+
) => IProtocolHandler;
|
|
31
|
+
|
|
32
|
+
export interface IProtocolHandler extends IBaseProtocolHandler {
|
|
33
|
+
readonly audience: IAudienceOwner;
|
|
34
|
+
processSignal(message: ISignalMessage);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandler {
|
|
38
|
+
constructor(
|
|
39
|
+
attributes: IDocumentAttributes,
|
|
40
|
+
quorumSnapshot: IQuorumSnapshot,
|
|
41
|
+
sendProposal: (key: string, value: any) => number,
|
|
42
|
+
initialClients: ISignalClient[],
|
|
43
|
+
readonly audience: IAudienceOwner,
|
|
44
|
+
) {
|
|
45
|
+
super(
|
|
46
|
+
attributes.minimumSequenceNumber,
|
|
47
|
+
attributes.sequenceNumber,
|
|
48
|
+
attributes.term,
|
|
49
|
+
quorumSnapshot.members,
|
|
50
|
+
quorumSnapshot.proposals,
|
|
51
|
+
quorumSnapshot.values,
|
|
52
|
+
sendProposal,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
for (const initialClient of initialClients) {
|
|
56
|
+
this.audience.addMember(initialClient.clientId, initialClient.client);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public processMessage(message: ISequencedDocumentMessage, local: boolean): IProcessMessageResult {
|
|
61
|
+
const client: ILocalSequencedClient | undefined = this.quorum.getMember(message.clientId);
|
|
62
|
+
|
|
63
|
+
// Check and report if we're getting messages from a clientId that we previously
|
|
64
|
+
// flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
|
|
65
|
+
if (message.clientId != null) {
|
|
66
|
+
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
67
|
+
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
68
|
+
throw new Error("Remote message's clientId is missing from the quorum");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (client?.shouldHaveLeft === true && message.type !== MessageType.NoOp) {
|
|
72
|
+
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
73
|
+
throw new Error("Remote message's clientId already should have left");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return super.processMessage(message, local);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public processSignal(message: ISignalMessage) {
|
|
81
|
+
const innerContent = message.content as { content: any; type: string; };
|
|
82
|
+
switch (innerContent.type) {
|
|
83
|
+
case MessageType.ClientJoin: {
|
|
84
|
+
const newClient = innerContent.content as ISignalClient;
|
|
85
|
+
this.audience.addMember(newClient.clientId, newClient.client);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case MessageType.ClientLeave: {
|
|
89
|
+
const leftClientId = innerContent.content as string;
|
|
90
|
+
this.audience.removeMember(leftClientId);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
default: break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|