@fluidframework/container-loader 2.0.0-internal.1.4.2 → 2.0.0-internal.2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +21 -8
- package/dist/audience.d.ts +0 -4
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +6 -11
- package/dist/audience.js.map +1 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +30 -4
- package/dist/connectionManager.js.map +1 -1
- package/dist/container.d.ts +0 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +7 -22
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +1 -1
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +2 -2
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.js +1 -1
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +35 -4
- package/dist/deltaManager.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts +8 -3
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +34 -8
- package/dist/protocol.js.map +1 -1
- package/lib/audience.d.ts +0 -4
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js +6 -11
- package/lib/audience.js.map +1 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +30 -4
- package/lib/connectionManager.js.map +1 -1
- package/lib/container.d.ts +0 -1
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +7 -22
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +1 -1
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +2 -2
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.js +1 -1
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +36 -5
- package/lib/deltaManager.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts +8 -3
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +33 -7
- package/lib/protocol.js.map +1 -1
- package/package.json +15 -18
- package/src/audience.ts +6 -12
- package/src/connectionManager.ts +34 -7
- package/src/container.ts +8 -24
- package/src/containerContext.ts +2 -2
- package/src/containerStorageAdapter.ts +1 -1
- package/src/deltaManager.ts +43 -8
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +31 -8
package/src/connectionManager.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "@fluidframework/container-definitions";
|
|
19
19
|
import { GenericError, UsageError } from "@fluidframework/container-utils";
|
|
20
20
|
import {
|
|
21
|
+
IAnyDriverError,
|
|
21
22
|
IDocumentService,
|
|
22
23
|
IDocumentDeltaConnection,
|
|
23
24
|
IDocumentDeltaConnectionEvents,
|
|
@@ -27,7 +28,6 @@ import {
|
|
|
27
28
|
createWriteError,
|
|
28
29
|
createGenericNetworkError,
|
|
29
30
|
getRetryDelayFromError,
|
|
30
|
-
IAnyDriverError,
|
|
31
31
|
waitForConnectedState,
|
|
32
32
|
DeltaStreamConnectionForbiddenError,
|
|
33
33
|
logNetworkFailure,
|
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
IConnectionManagerFactoryArgs,
|
|
60
60
|
} from "./contracts";
|
|
61
61
|
import { DeltaQueue } from "./deltaQueue";
|
|
62
|
+
import { SignalType } from "./protocol";
|
|
62
63
|
|
|
63
64
|
const MaxReconnectDelayInMs = 8000;
|
|
64
65
|
const InitialReconnectDelayInMs = 1000;
|
|
@@ -713,17 +714,43 @@ export class ConnectionManager implements IConnectionManager {
|
|
|
713
714
|
initialMessages,
|
|
714
715
|
this.connectFirstConnection ? "InitialOps" : "ReconnectOps");
|
|
715
716
|
|
|
716
|
-
if (connection.initialSignals !== undefined) {
|
|
717
|
-
for (const signal of connection.initialSignals) {
|
|
718
|
-
this.props.signalHandler(signal);
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
717
|
const details = ConnectionManager.detailsFromConnection(connection);
|
|
723
718
|
details.checkpointSequenceNumber = checkpointSequenceNumber;
|
|
724
719
|
this.props.connectHandler(details);
|
|
725
720
|
|
|
726
721
|
this.connectFirstConnection = false;
|
|
722
|
+
|
|
723
|
+
// Synthesize clear & join signals out of initialClients state.
|
|
724
|
+
// This allows us to have single way to process signals, and makes it simpler to initialize
|
|
725
|
+
// protocol in Container.
|
|
726
|
+
const clearSignal: ISignalMessage = {
|
|
727
|
+
clientId: null, // system message
|
|
728
|
+
content: JSON.stringify({
|
|
729
|
+
type: SignalType.Clear,
|
|
730
|
+
}),
|
|
731
|
+
};
|
|
732
|
+
this.props.signalHandler(clearSignal);
|
|
733
|
+
|
|
734
|
+
for (const priorClient of connection.initialClients ?? []) {
|
|
735
|
+
const joinSignal: ISignalMessage = {
|
|
736
|
+
clientId: null, // system signal
|
|
737
|
+
content: JSON.stringify({
|
|
738
|
+
type: SignalType.ClientJoin,
|
|
739
|
+
content: priorClient, // ISignalClient
|
|
740
|
+
}),
|
|
741
|
+
};
|
|
742
|
+
this.props.signalHandler(joinSignal);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Unfortunately, there is no defined order between initialSignals (including join & leave signals)
|
|
746
|
+
// and connection.initialClients. In practice, connection.initialSignals quite often contains join signal
|
|
747
|
+
// for "self" and connection.initialClients does not contain "self", so we have to process them after
|
|
748
|
+
// "clear" signal above.
|
|
749
|
+
if (connection.initialSignals !== undefined) {
|
|
750
|
+
for (const signal of connection.initialSignals) {
|
|
751
|
+
this.props.signalHandler(signal);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
727
754
|
}
|
|
728
755
|
|
|
729
756
|
/**
|
package/src/container.ts
CHANGED
|
@@ -64,7 +64,6 @@ import {
|
|
|
64
64
|
ISequencedClient,
|
|
65
65
|
ISequencedDocumentMessage,
|
|
66
66
|
ISequencedProposal,
|
|
67
|
-
ISignalClient,
|
|
68
67
|
ISignalMessage,
|
|
69
68
|
ISnapshotTree,
|
|
70
69
|
ISummaryContent,
|
|
@@ -412,7 +411,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
412
411
|
private readonly clientDetailsOverride: IClientDetails | undefined;
|
|
413
412
|
private readonly _deltaManager: DeltaManager<ConnectionManager>;
|
|
414
413
|
private service: IDocumentService | undefined;
|
|
415
|
-
private _initialClients: ISignalClient[] | undefined;
|
|
416
414
|
|
|
417
415
|
private _context: ContainerContext | undefined;
|
|
418
416
|
private get context() {
|
|
@@ -619,7 +617,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
619
617
|
this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
|
|
620
618
|
|
|
621
619
|
const summarizeProtocolTree =
|
|
622
|
-
this.mc.config.getBoolean("Fluid.Container.
|
|
620
|
+
this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2")
|
|
623
621
|
?? this.loader.services.options.summarizeProtocolTree;
|
|
624
622
|
|
|
625
623
|
this.options = {
|
|
@@ -639,7 +637,10 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
639
637
|
}
|
|
640
638
|
this.logConnectionStateChangeTelemetry(value, oldState, reason);
|
|
641
639
|
if (this._lifecycleState === "loaded") {
|
|
642
|
-
this.propagateConnectionState(
|
|
640
|
+
this.propagateConnectionState(
|
|
641
|
+
false /* initial transition */,
|
|
642
|
+
value === ConnectionState.Disconnected ? reason : undefined /* disconnectedReason */,
|
|
643
|
+
);
|
|
643
644
|
}
|
|
644
645
|
},
|
|
645
646
|
shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
|
|
@@ -1383,11 +1384,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1383
1384
|
attributes,
|
|
1384
1385
|
quorumSnapshot,
|
|
1385
1386
|
(key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })),
|
|
1386
|
-
this._initialClients ?? [],
|
|
1387
1387
|
);
|
|
1388
1388
|
|
|
1389
|
-
this._initialClients = undefined;
|
|
1390
|
-
|
|
1391
1389
|
const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
|
|
1392
1390
|
|
|
1393
1391
|
protocol.quorum.on("error", (error) => {
|
|
@@ -1512,20 +1510,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1512
1510
|
deltaManager.inboundSignal.pause();
|
|
1513
1511
|
|
|
1514
1512
|
deltaManager.on("connect", (details: IConnectionDetails, _opsBehind?: number) => {
|
|
1515
|
-
if (this._protocolHandler === undefined) {
|
|
1516
|
-
// Store the initial clients so that they can be submitted to the
|
|
1517
|
-
// protocol handler when it is created.
|
|
1518
|
-
this._initialClients = details.initialClients;
|
|
1519
|
-
} else {
|
|
1520
|
-
// When reconnecting, the protocol handler is already created,
|
|
1521
|
-
// so we can update the audience right now.
|
|
1522
|
-
this._protocolHandler.audience.clear();
|
|
1523
|
-
|
|
1524
|
-
for (const priorClient of details.initialClients ?? []) {
|
|
1525
|
-
this._protocolHandler.audience.addMember(priorClient.clientId, priorClient.client);
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
1513
|
this.connectionStateHandler.receivedConnectEvent(
|
|
1530
1514
|
this.connectionMode,
|
|
1531
1515
|
details,
|
|
@@ -1629,7 +1613,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1629
1613
|
}
|
|
1630
1614
|
}
|
|
1631
1615
|
|
|
1632
|
-
private propagateConnectionState(initialTransition: boolean) {
|
|
1616
|
+
private propagateConnectionState(initialTransition: boolean, disconnectedReason?: string) {
|
|
1633
1617
|
// When container loaded, we want to propagate initial connection state.
|
|
1634
1618
|
// After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
|
|
1635
1619
|
// This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
|
|
@@ -1652,7 +1636,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1652
1636
|
|
|
1653
1637
|
this.setContextConnectedState(state, this._deltaManager.connectionManager.readOnlyInfo.readonly ?? false);
|
|
1654
1638
|
this.protocolHandler.setConnectionState(state, this.clientId);
|
|
1655
|
-
raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
|
|
1639
|
+
raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
|
|
1656
1640
|
|
|
1657
1641
|
if (logOpsOnReconnect) {
|
|
1658
1642
|
this.mc.logger.sendTelemetryEvent(
|
|
@@ -1724,7 +1708,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
|
|
|
1724
1708
|
const result = this.protocolHandler.processMessage(message, local);
|
|
1725
1709
|
|
|
1726
1710
|
// Forward messages to the loaded runtime for processing
|
|
1727
|
-
this.context.process(message, local
|
|
1711
|
+
this.context.process(message, local);
|
|
1728
1712
|
|
|
1729
1713
|
// Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
|
|
1730
1714
|
if (this.activeConnection()) {
|
package/src/containerContext.ts
CHANGED
|
@@ -243,8 +243,8 @@ export class ContainerContext implements IContainerContext {
|
|
|
243
243
|
runtime.setConnectionState(connected, clientId);
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
public process(message: ISequencedDocumentMessage, local: boolean
|
|
247
|
-
this.runtime.process(message, local
|
|
246
|
+
public process(message: ISequencedDocumentMessage, local: boolean) {
|
|
247
|
+
this.runtime.process(message, local);
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
public processSignal(message: ISignalMessage, local: boolean) {
|
|
@@ -176,7 +176,7 @@ class BlobOnlyStorage implements IDocumentStorageService {
|
|
|
176
176
|
// some browsers may not populate stack unless exception is thrown
|
|
177
177
|
throw new Error("BlobOnlyStorage not implemented method used");
|
|
178
178
|
} catch (err) {
|
|
179
|
-
this.logger.
|
|
179
|
+
this.logger.sendTelemetryEvent({ eventName: "BlobOnlyStorageWrongCall" }, err);
|
|
180
180
|
throw err;
|
|
181
181
|
}
|
|
182
182
|
}
|
package/src/deltaManager.ts
CHANGED
|
@@ -42,7 +42,8 @@ import {
|
|
|
42
42
|
} from "@fluidframework/protocol-definitions";
|
|
43
43
|
import {
|
|
44
44
|
NonRetryableError,
|
|
45
|
-
|
|
45
|
+
isRuntimeMessage,
|
|
46
|
+
MessageType2,
|
|
46
47
|
} from "@fluidframework/driver-utils";
|
|
47
48
|
import {
|
|
48
49
|
ThrottlingWarning,
|
|
@@ -72,6 +73,25 @@ export interface IDeltaManagerInternalEvents extends IDeltaManagerEvents {
|
|
|
72
73
|
(event: "closed", listener: (error?: ICriticalContainerError) => void);
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Determines if message was sent by client, not service
|
|
78
|
+
*/
|
|
79
|
+
function isClientMessage(message: ISequencedDocumentMessage | IDocumentMessage): boolean {
|
|
80
|
+
if (isRuntimeMessage(message)) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
switch (message.type) {
|
|
84
|
+
case MessageType.Propose:
|
|
85
|
+
case MessageType.Reject:
|
|
86
|
+
case MessageType.NoOp:
|
|
87
|
+
case MessageType2.Accept:
|
|
88
|
+
case MessageType.Summarize:
|
|
89
|
+
return true;
|
|
90
|
+
default:
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
75
95
|
/**
|
|
76
96
|
* Manages the flow of both inbound and outbound messages. This class ensures that shared objects receive delta
|
|
77
97
|
* messages in order regardless of possible network conditions or timings causing out of order delivery.
|
|
@@ -218,6 +238,8 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
218
238
|
return -1;
|
|
219
239
|
}
|
|
220
240
|
|
|
241
|
+
assert(isClientMessage(message), 0x419 /* client sends non-client message */);
|
|
242
|
+
|
|
221
243
|
if (contents !== undefined) {
|
|
222
244
|
this.opsSize += contents.length;
|
|
223
245
|
}
|
|
@@ -795,13 +817,16 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
795
817
|
this.currentlyProcessingOps = true;
|
|
796
818
|
this.lastProcessedMessage = message;
|
|
797
819
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
)
|
|
820
|
+
const isString = typeof message.clientId === "string";
|
|
821
|
+
assert(message.clientId === null || isString, 0x41a /* undefined or string */);
|
|
822
|
+
// All client messages are coming from some client, and should have clientId,
|
|
823
|
+
// and non-client message should not have clientId. But, there are two exceptions:
|
|
824
|
+
// 1. (Legacy) We can see message.type === "attach" or "chunkedOp" for legacy files before RTM
|
|
825
|
+
// 2. Non-immediate noops (contents: null) can be sent by service without clientId
|
|
826
|
+
if (!isString && isClientMessage(message) && message.type !== MessageType.NoOp) {
|
|
827
|
+
throw new DataCorruptionError("Mismatch in clientId",
|
|
828
|
+
{ ...extractSafePropertiesFromMessage(message), messageType: message.type });
|
|
829
|
+
}
|
|
805
830
|
|
|
806
831
|
// TODO Remove after SPO picks up the latest build.
|
|
807
832
|
if (
|
|
@@ -822,6 +847,16 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
822
847
|
clientId: this.connectionManager.clientId,
|
|
823
848
|
});
|
|
824
849
|
}
|
|
850
|
+
|
|
851
|
+
// Client ops: MSN has to be lower than sequence #, as client can continue to send ops with same
|
|
852
|
+
// reference sequence number as this op.
|
|
853
|
+
// System ops (when no clients are connected) are the only ops where equation is possible.
|
|
854
|
+
const diff = message.sequenceNumber - message.minimumSequenceNumber;
|
|
855
|
+
if (diff < 0 || diff === 0 && message.clientId !== null) {
|
|
856
|
+
throw new DataCorruptionError("MSN has to be lower than sequence #",
|
|
857
|
+
extractSafePropertiesFromMessage(message));
|
|
858
|
+
}
|
|
859
|
+
|
|
825
860
|
this.minSequenceNumber = message.minimumSequenceNumber;
|
|
826
861
|
|
|
827
862
|
if (message.sequenceNumber !== this.lastProcessedSequenceNumber + 1) {
|
package/src/packageVersion.ts
CHANGED
package/src/protocol.ts
CHANGED
|
@@ -20,6 +20,13 @@ import {
|
|
|
20
20
|
} from "@fluidframework/protocol-definitions";
|
|
21
21
|
import { canBeCoalescedByService } from "@fluidframework/driver-utils";
|
|
22
22
|
|
|
23
|
+
// ADO: #1986: Start using enum from protocol-base.
|
|
24
|
+
export enum SignalType {
|
|
25
|
+
ClientJoin = "join", // same value as MessageType.ClientJoin,
|
|
26
|
+
ClientLeave = "leave", // same value as MessageType.ClientLeave,
|
|
27
|
+
Clear = "clear", // used only by client for synthetic signals
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
/**
|
|
24
31
|
* Function to be used for creating a protocol handler.
|
|
25
32
|
*/
|
|
@@ -27,7 +34,6 @@ export type ProtocolHandlerBuilder = (
|
|
|
27
34
|
attributes: IDocumentAttributes,
|
|
28
35
|
snapshot: IQuorumSnapshot,
|
|
29
36
|
sendProposal: (key: string, value: any) => number,
|
|
30
|
-
initialClients: ISignalClient[],
|
|
31
37
|
) => IProtocolHandler;
|
|
32
38
|
|
|
33
39
|
export interface IProtocolHandler extends IBaseProtocolHandler {
|
|
@@ -40,7 +46,6 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
|
|
|
40
46
|
attributes: IDocumentAttributes,
|
|
41
47
|
quorumSnapshot: IQuorumSnapshot,
|
|
42
48
|
sendProposal: (key: string, value: any) => number,
|
|
43
|
-
initialClients: ISignalClient[],
|
|
44
49
|
readonly audience: IAudienceOwner,
|
|
45
50
|
) {
|
|
46
51
|
super(
|
|
@@ -53,8 +58,11 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
|
|
|
53
58
|
sendProposal,
|
|
54
59
|
);
|
|
55
60
|
|
|
56
|
-
for
|
|
57
|
-
|
|
61
|
+
// Join / leave signals are ignored for "write" clients in favor of join / leave ops
|
|
62
|
+
this.quorum.on("addMember", (clientId, details) => audience.addMember(clientId, details.client));
|
|
63
|
+
this.quorum.on("removeMember", (clientId) => audience.removeMember(clientId));
|
|
64
|
+
for (const [clientId, details] of this.quorum.getMembers()) {
|
|
65
|
+
this.audience.addMember(clientId, details.client);
|
|
58
66
|
}
|
|
59
67
|
}
|
|
60
68
|
|
|
@@ -81,14 +89,29 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
|
|
|
81
89
|
public processSignal(message: ISignalMessage) {
|
|
82
90
|
const innerContent = message.content as { content: any; type: string; };
|
|
83
91
|
switch (innerContent.type) {
|
|
84
|
-
case
|
|
92
|
+
case SignalType.Clear: {
|
|
93
|
+
const members = this.audience.getMembers();
|
|
94
|
+
for (const [clientId, client] of members) {
|
|
95
|
+
if (client.mode === "read") {
|
|
96
|
+
this.audience.removeMember(clientId);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case SignalType.ClientJoin: {
|
|
85
102
|
const newClient = innerContent.content as ISignalClient;
|
|
86
|
-
|
|
103
|
+
// Ignore write clients - quorum will control such clients.
|
|
104
|
+
if (newClient.client.mode === "read") {
|
|
105
|
+
this.audience.addMember(newClient.clientId, newClient.client);
|
|
106
|
+
}
|
|
87
107
|
break;
|
|
88
108
|
}
|
|
89
|
-
case
|
|
109
|
+
case SignalType.ClientLeave: {
|
|
90
110
|
const leftClientId = innerContent.content as string;
|
|
91
|
-
|
|
111
|
+
// Ignore write clients - quorum will control such clients.
|
|
112
|
+
if (this.audience.getMember(leftClientId)?.mode === "read") {
|
|
113
|
+
this.audience.removeMember(leftClientId);
|
|
114
|
+
}
|
|
92
115
|
break;
|
|
93
116
|
}
|
|
94
117
|
default: break;
|