@fluidframework/container-loader 2.0.0-internal.3.0.1 → 2.0.0-internal.3.1.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 +18 -21
- package/.mocharc.js +2 -2
- package/README.md +45 -43
- package/api-extractor.json +2 -2
- package/closeAndGetPendingLocalState.md +51 -0
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js.map +1 -1
- package/dist/catchUpMonitor.d.ts.map +1 -1
- package/dist/catchUpMonitor.js.map +1 -1
- package/dist/collabWindowTracker.d.ts.map +1 -1
- package/dist/collabWindowTracker.js.map +1 -1
- package/dist/connectionManager.d.ts +2 -2
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +50 -23
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionState.d.ts.map +1 -1
- package/dist/connectionState.js.map +1 -1
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +35 -16
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +96 -46
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +6 -2
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +2 -4
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +3 -3
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +56 -27
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaManagerProxy.d.ts.map +1 -1
- package/dist/deltaManagerProxy.js.map +1 -1
- package/dist/deltaQueue.d.ts.map +1 -1
- package/dist/deltaQueue.js +4 -2
- 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 +3 -3
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +16 -8
- package/dist/loader.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +2 -1
- package/dist/protocol.js.map +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/protocolTreeDocumentStorageService.js.map +1 -1
- package/dist/quorum.d.ts.map +1 -1
- package/dist/quorum.js.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +6 -2
- package/dist/retriableDocumentStorageService.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +6 -4
- package/dist/utils.js.map +1 -1
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js.map +1 -1
- package/lib/catchUpMonitor.d.ts.map +1 -1
- package/lib/catchUpMonitor.js.map +1 -1
- package/lib/collabWindowTracker.d.ts.map +1 -1
- package/lib/collabWindowTracker.js.map +1 -1
- package/lib/connectionManager.d.ts +2 -2
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +52 -25
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionState.d.ts.map +1 -1
- package/lib/connectionState.js.map +1 -1
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +35 -16
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +100 -50
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +6 -2
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +2 -4
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts.map +1 -1
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +3 -3
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +58 -29
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaManagerProxy.d.ts.map +1 -1
- package/lib/deltaManagerProxy.js.map +1 -1
- package/lib/deltaQueue.d.ts.map +1 -1
- package/lib/deltaQueue.js +4 -2
- 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 +3 -3
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +16 -8
- package/lib/loader.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +2 -1
- package/lib/protocol.js.map +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/protocolTreeDocumentStorageService.js.map +1 -1
- package/lib/quorum.d.ts.map +1 -1
- package/lib/quorum.js.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +6 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +6 -4
- package/lib/utils.js.map +1 -1
- package/package.json +115 -114
- package/prettier.config.cjs +1 -1
- package/src/audience.ts +51 -46
- package/src/catchUpMonitor.ts +39 -37
- package/src/collabWindowTracker.ts +75 -70
- package/src/connectionManager.ts +1006 -943
- package/src/connectionState.ts +19 -19
- package/src/connectionStateHandler.ts +544 -465
- package/src/container.ts +2056 -1895
- package/src/containerContext.ts +350 -340
- package/src/containerStorageAdapter.ts +163 -153
- package/src/contracts.ts +155 -153
- package/src/deltaManager.ts +1069 -992
- package/src/deltaManagerProxy.ts +143 -137
- package/src/deltaQueue.ts +155 -151
- package/src/index.ts +14 -17
- package/src/loader.ts +408 -403
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +93 -87
- package/src/protocolTreeDocumentStorageService.ts +30 -33
- package/src/quorum.ts +34 -34
- package/src/retriableDocumentStorageService.ts +118 -102
- package/src/utils.ts +89 -82
- package/tsconfig.esnext.json +6 -6
- package/tsconfig.json +8 -12
package/src/packageVersion.ts
CHANGED
package/src/protocol.ts
CHANGED
|
@@ -5,116 +5,122 @@
|
|
|
5
5
|
|
|
6
6
|
import { IAudienceOwner } from "@fluidframework/container-definitions";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
ILocalSequencedClient,
|
|
9
|
+
IProtocolHandler as IBaseProtocolHandler,
|
|
10
|
+
IQuorumSnapshot,
|
|
11
|
+
ProtocolOpHandler,
|
|
12
12
|
} from "@fluidframework/protocol-base";
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
IDocumentAttributes,
|
|
15
|
+
IProcessMessageResult,
|
|
16
|
+
ISequencedDocumentMessage,
|
|
17
|
+
ISignalClient,
|
|
18
|
+
ISignalMessage,
|
|
19
|
+
MessageType,
|
|
20
20
|
} from "@fluidframework/protocol-definitions";
|
|
21
21
|
import { canBeCoalescedByService } from "@fluidframework/driver-utils";
|
|
22
22
|
|
|
23
23
|
// ADO: #1986: Start using enum from protocol-base.
|
|
24
24
|
export enum SignalType {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Function to be used for creating a protocol handler.
|
|
32
32
|
*/
|
|
33
33
|
export type ProtocolHandlerBuilder = (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
attributes: IDocumentAttributes,
|
|
35
|
+
snapshot: IQuorumSnapshot,
|
|
36
|
+
sendProposal: (key: string, value: any) => number,
|
|
37
37
|
) => IProtocolHandler;
|
|
38
38
|
|
|
39
39
|
export interface IProtocolHandler extends IBaseProtocolHandler {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
readonly audience: IAudienceOwner;
|
|
41
|
+
processSignal(message: ISignalMessage);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandler {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
constructor(
|
|
46
|
+
attributes: IDocumentAttributes,
|
|
47
|
+
quorumSnapshot: IQuorumSnapshot,
|
|
48
|
+
sendProposal: (key: string, value: any) => number,
|
|
49
|
+
readonly audience: IAudienceOwner,
|
|
50
|
+
) {
|
|
51
|
+
super(
|
|
52
|
+
attributes.minimumSequenceNumber,
|
|
53
|
+
attributes.sequenceNumber,
|
|
54
|
+
attributes.term,
|
|
55
|
+
quorumSnapshot.members,
|
|
56
|
+
quorumSnapshot.proposals,
|
|
57
|
+
quorumSnapshot.values,
|
|
58
|
+
sendProposal,
|
|
59
|
+
);
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
// Join / leave signals are ignored for "write" clients in favor of join / leave ops
|
|
62
|
+
this.quorum.on("addMember", (clientId, details) =>
|
|
63
|
+
audience.addMember(clientId, details.client),
|
|
64
|
+
);
|
|
65
|
+
this.quorum.on("removeMember", (clientId) => audience.removeMember(clientId));
|
|
66
|
+
for (const [clientId, details] of this.quorum.getMembers()) {
|
|
67
|
+
this.audience.addMember(clientId, details.client);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
public processMessage(
|
|
72
|
+
message: ISequencedDocumentMessage,
|
|
73
|
+
local: boolean,
|
|
74
|
+
): IProcessMessageResult {
|
|
75
|
+
const client: ILocalSequencedClient | undefined = this.quorum.getMember(message.clientId);
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
// Check and report if we're getting messages from a clientId that we previously
|
|
78
|
+
// flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
|
|
79
|
+
if (message.clientId != null) {
|
|
80
|
+
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
81
|
+
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
82
|
+
throw new Error("Remote message's clientId is missing from the quorum");
|
|
83
|
+
}
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
if (client?.shouldHaveLeft === true && !canBeCoalescedByService(message)) {
|
|
86
|
+
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
87
|
+
throw new Error("Remote message's clientId already should have left");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
85
90
|
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
return super.processMessage(message, local);
|
|
92
|
+
}
|
|
88
93
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
94
|
+
public processSignal(message: ISignalMessage) {
|
|
95
|
+
const innerContent = message.content as { content: any; type: string };
|
|
96
|
+
switch (innerContent.type) {
|
|
97
|
+
case SignalType.Clear: {
|
|
98
|
+
const members = this.audience.getMembers();
|
|
99
|
+
for (const [clientId, client] of members) {
|
|
100
|
+
if (client.mode === "read") {
|
|
101
|
+
this.audience.removeMember(clientId);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
case SignalType.ClientJoin: {
|
|
107
|
+
const newClient = innerContent.content as ISignalClient;
|
|
108
|
+
// Ignore write clients - quorum will control such clients.
|
|
109
|
+
if (newClient.client.mode === "read") {
|
|
110
|
+
this.audience.addMember(newClient.clientId, newClient.client);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case SignalType.ClientLeave: {
|
|
115
|
+
const leftClientId = innerContent.content as string;
|
|
116
|
+
// Ignore write clients - quorum will control such clients.
|
|
117
|
+
if (this.audience.getMember(leftClientId)?.mode === "read") {
|
|
118
|
+
this.audience.removeMember(leftClientId);
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
default:
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
120
126
|
}
|
|
@@ -4,42 +4,39 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { IDisposable } from "@fluidframework/common-definitions";
|
|
7
|
-
import {
|
|
8
|
-
IDocumentStorageService,
|
|
9
|
-
ISummaryContext,
|
|
10
|
-
} from "@fluidframework/driver-definitions";
|
|
7
|
+
import { IDocumentStorageService, ISummaryContext } from "@fluidframework/driver-definitions";
|
|
11
8
|
import { combineAppAndProtocolSummary } from "@fluidframework/driver-utils";
|
|
12
|
-
import {
|
|
13
|
-
ISummaryTree,
|
|
14
|
-
} from "@fluidframework/protocol-definitions";
|
|
9
|
+
import { ISummaryTree } from "@fluidframework/protocol-definitions";
|
|
15
10
|
|
|
16
11
|
export class ProtocolTreeStorageService implements IDocumentStorageService, IDisposable {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly internalStorageService: IDocumentStorageService & IDisposable,
|
|
14
|
+
private readonly generateProtocolTree: () => ISummaryTree,
|
|
15
|
+
) {}
|
|
16
|
+
public get policies() {
|
|
17
|
+
return this.internalStorageService.policies;
|
|
18
|
+
}
|
|
19
|
+
public get repositoryUrl() {
|
|
20
|
+
return this.internalStorageService.repositoryUrl;
|
|
21
|
+
}
|
|
22
|
+
public get disposed() {
|
|
23
|
+
return this.internalStorageService.disposed;
|
|
24
|
+
}
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
getSnapshotTree = this.internalStorageService.getSnapshotTree.bind(this.internalStorageService);
|
|
27
|
+
getVersions = this.internalStorageService.getVersions.bind(this.internalStorageService);
|
|
28
|
+
createBlob = this.internalStorageService.createBlob.bind(this.internalStorageService);
|
|
29
|
+
readBlob = this.internalStorageService.readBlob.bind(this.internalStorageService);
|
|
30
|
+
downloadSummary = this.internalStorageService.downloadSummary.bind(this.internalStorageService);
|
|
31
|
+
dispose = this.internalStorageService.dispose.bind(this.internalStorageService);
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
async uploadSummaryWithContext(
|
|
34
|
+
summary: ISummaryTree,
|
|
35
|
+
context: ISummaryContext,
|
|
36
|
+
): Promise<string> {
|
|
37
|
+
return this.internalStorageService.uploadSummaryWithContext(
|
|
38
|
+
combineAppAndProtocolSummary(summary, this.generateProtocolTree()),
|
|
39
|
+
context,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
45
42
|
}
|
package/src/quorum.ts
CHANGED
|
@@ -5,55 +5,55 @@
|
|
|
5
5
|
import { assert, EventForwarder, doIfNotDisposed } from "@fluidframework/common-utils";
|
|
6
6
|
import { IFluidCodeDetails } from "@fluidframework/core-interfaces";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
ICommittedProposal,
|
|
9
|
+
IQuorum,
|
|
10
|
+
IQuorumEvents,
|
|
11
|
+
ISequencedClient,
|
|
12
12
|
} from "@fluidframework/protocol-definitions";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Proxies Quorum events.
|
|
16
16
|
*/
|
|
17
17
|
export class QuorumProxy extends EventForwarder<IQuorumEvents> implements IQuorum {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
public readonly propose: (key: string, value: any) => Promise<void>;
|
|
19
|
+
public readonly has: (key: string) => boolean;
|
|
20
|
+
public readonly get: (key: string) => any;
|
|
21
|
+
public readonly getMembers: () => Map<string, ISequencedClient>;
|
|
22
|
+
public readonly getMember: (clientId: string) => ISequencedClient | undefined;
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
constructor(quorum: IQuorum) {
|
|
25
|
+
super(quorum);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
// This is heavily used object, increase limit at which Node prints warnings.
|
|
28
|
+
super.setMaxListeners(50);
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
this.propose = doIfNotDisposed(this, quorum.propose.bind(quorum));
|
|
31
|
+
this.has = doIfNotDisposed(this, quorum.has.bind(quorum));
|
|
32
|
+
this.get = doIfNotDisposed(this, quorum.get.bind(quorum));
|
|
33
|
+
this.getMembers = doIfNotDisposed(this, quorum.getMembers.bind(quorum));
|
|
34
|
+
this.getMember = doIfNotDisposed(this, quorum.getMember.bind(quorum));
|
|
35
|
+
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export function getCodeDetailsFromQuorumValues(
|
|
39
|
-
|
|
39
|
+
quorumValues: [string, ICommittedProposal][],
|
|
40
40
|
): IFluidCodeDetails {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const qValuesMap = new Map(quorumValues);
|
|
42
|
+
const proposal = qValuesMap.get("code");
|
|
43
|
+
assert(proposal !== undefined, 0x2dc /* "Cannot find code proposal" */);
|
|
44
|
+
return proposal?.value as IFluidCodeDetails;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export function initQuorumValuesFromCodeDetails(
|
|
48
|
-
|
|
48
|
+
source: IFluidCodeDetails,
|
|
49
49
|
): [string, ICommittedProposal][] {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
// Seed the base quorum to be an empty list with a code quorum set
|
|
51
|
+
const committedCodeProposal: ICommittedProposal = {
|
|
52
|
+
key: "code",
|
|
53
|
+
value: source,
|
|
54
|
+
approvalSequenceNumber: 0,
|
|
55
|
+
commitSequenceNumber: 0,
|
|
56
|
+
sequenceNumber: 0,
|
|
57
|
+
};
|
|
58
|
+
return [["code", committedCodeProposal]];
|
|
59
59
|
}
|
|
@@ -6,124 +6,140 @@
|
|
|
6
6
|
import { assert } from "@fluidframework/common-utils";
|
|
7
7
|
import { GenericError } from "@fluidframework/container-utils";
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
FetchSource,
|
|
10
|
+
IDocumentStorageService,
|
|
11
|
+
IDocumentStorageServicePolicies,
|
|
12
|
+
ISummaryContext,
|
|
13
13
|
} from "@fluidframework/driver-definitions";
|
|
14
14
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
ICreateBlobResponse,
|
|
16
|
+
ISnapshotTree,
|
|
17
|
+
ISummaryHandle,
|
|
18
|
+
ISummaryTree,
|
|
19
|
+
IVersion,
|
|
20
20
|
} from "@fluidframework/protocol-definitions";
|
|
21
21
|
import { IDisposable, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
22
22
|
import { runWithRetry } from "@fluidframework/driver-utils";
|
|
23
23
|
|
|
24
24
|
export class RetriableDocumentStorageService implements IDocumentStorageService, IDisposable {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
25
|
+
private _disposed = false;
|
|
26
|
+
constructor(
|
|
27
|
+
private readonly internalStorageService: IDocumentStorageService,
|
|
28
|
+
private readonly logger: ITelemetryLogger,
|
|
29
|
+
) {}
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
public get policies(): IDocumentStorageServicePolicies | undefined {
|
|
32
|
+
return this.internalStorageService.policies;
|
|
33
|
+
}
|
|
34
|
+
public get disposed() {
|
|
35
|
+
return this._disposed;
|
|
36
|
+
}
|
|
37
|
+
public dispose() {
|
|
38
|
+
this._disposed = true;
|
|
39
|
+
}
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
public get repositoryUrl(): string {
|
|
42
|
+
return this.internalStorageService.repositoryUrl;
|
|
43
|
+
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
public async getSnapshotTree(
|
|
46
|
+
version?: IVersion,
|
|
47
|
+
scenarioName?: string,
|
|
48
|
+
): Promise<ISnapshotTree | null> {
|
|
49
|
+
return this.runWithRetry(
|
|
50
|
+
async () => this.internalStorageService.getSnapshotTree(version, scenarioName),
|
|
51
|
+
"storage_getSnapshotTree",
|
|
52
|
+
);
|
|
53
|
+
}
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
public async readBlob(id: string): Promise<ArrayBufferLike> {
|
|
56
|
+
return this.runWithRetry(
|
|
57
|
+
async () => this.internalStorageService.readBlob(id),
|
|
58
|
+
"storage_readBlob",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
public async getVersions(
|
|
63
|
+
versionId: string | null,
|
|
64
|
+
count: number,
|
|
65
|
+
scenarioName?: string,
|
|
66
|
+
fetchSource?: FetchSource,
|
|
67
|
+
): Promise<IVersion[]> {
|
|
68
|
+
return this.runWithRetry(
|
|
69
|
+
async () =>
|
|
70
|
+
this.internalStorageService.getVersions(
|
|
71
|
+
versionId,
|
|
72
|
+
count,
|
|
73
|
+
scenarioName,
|
|
74
|
+
fetchSource,
|
|
75
|
+
),
|
|
76
|
+
"storage_getVersions",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
69
79
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
public async uploadSummaryWithContext(
|
|
81
|
+
summary: ISummaryTree,
|
|
82
|
+
context: ISummaryContext,
|
|
83
|
+
): Promise<string> {
|
|
84
|
+
// Not using retry loop here. Couple reasons:
|
|
85
|
+
// 1. If client lost connectivity, then retry loop will result in uploading stale summary
|
|
86
|
+
// by stale summarizer after connectivity comes back. It will cause failures for this client and for
|
|
87
|
+
// real (new) summarizer. This problem in particular should be solved in future by supplying abort handle
|
|
88
|
+
// on all APIs and caller (ContainerRuntime.submitSummary) aborting call on loss of connectivity
|
|
89
|
+
// 2. Similar, if we get 429 with retryAfter = 10 minutes, it's likely not the right call to retry summary
|
|
90
|
+
// upload in 10 minutes - it's better to keep processing ops and retry later. Though caller needs to take
|
|
91
|
+
// retryAfter into account!
|
|
92
|
+
// But retry loop is required for creation flow (Container.attach)
|
|
93
|
+
assert(
|
|
94
|
+
(context.referenceSequenceNumber === 0) === (context.ackHandle === undefined),
|
|
95
|
+
0x251 /* "creation summary has to have seq=0 && handle === undefined" */,
|
|
96
|
+
);
|
|
97
|
+
if (context.referenceSequenceNumber !== 0) {
|
|
98
|
+
return this.internalStorageService.uploadSummaryWithContext(summary, context);
|
|
99
|
+
}
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
// Creation flow with attachment blobs - need to do retries!
|
|
102
|
+
return this.runWithRetry(
|
|
103
|
+
async () => this.internalStorageService.uploadSummaryWithContext(summary, context),
|
|
104
|
+
"storage_uploadSummaryWithContext",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
92
107
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
108
|
+
public async downloadSummary(handle: ISummaryHandle): Promise<ISummaryTree> {
|
|
109
|
+
return this.runWithRetry(
|
|
110
|
+
async () => this.internalStorageService.downloadSummary(handle),
|
|
111
|
+
"storage_downloadSummary",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
99
114
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
public async createBlob(file: ArrayBufferLike): Promise<ICreateBlobResponse> {
|
|
116
|
+
return this.runWithRetry(
|
|
117
|
+
async () => this.internalStorageService.createBlob(file),
|
|
118
|
+
"storage_createBlob",
|
|
119
|
+
);
|
|
120
|
+
}
|
|
106
121
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
private checkStorageDisposed(callName: string, error: unknown) {
|
|
123
|
+
if (this._disposed) {
|
|
124
|
+
this.logger.sendTelemetryEvent(
|
|
125
|
+
{
|
|
126
|
+
eventName: `${callName}_abortedStorageDisposed`,
|
|
127
|
+
fetchCallName: callName, // fetchCallName matches logs in runWithRetry.ts
|
|
128
|
+
},
|
|
129
|
+
error,
|
|
130
|
+
);
|
|
131
|
+
// pre-0.58 error message: storageServiceDisposedCannotRetry
|
|
132
|
+
throw new GenericError("Storage Service is disposed. Cannot retry", {
|
|
133
|
+
canRetry: false,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
118
138
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
onRetry: (_delayInMs: number, error: unknown) => this.checkStorageDisposed(callName, error),
|
|
126
|
-
},
|
|
127
|
-
);
|
|
128
|
-
}
|
|
139
|
+
private async runWithRetry<T>(api: () => Promise<T>, callName: string): Promise<T> {
|
|
140
|
+
return runWithRetry(api, callName, this.logger, {
|
|
141
|
+
onRetry: (_delayInMs: number, error: unknown) =>
|
|
142
|
+
this.checkStorageDisposed(callName, error),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
129
145
|
}
|