@fluidframework/container-loader 1.3.0 → 2.0.0-dev.1.4.5.105745
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 +8 -21
- package/.mocharc.js +12 -0
- 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 +29 -0
- package/dist/catchUpMonitor.d.ts.map +1 -0
- package/dist/catchUpMonitor.js +43 -0
- package/dist/catchUpMonitor.js.map +1 -0
- package/dist/collabWindowTracker.d.ts +1 -1
- package/dist/collabWindowTracker.d.ts.map +1 -1
- package/dist/collabWindowTracker.js +12 -4
- package/dist/collabWindowTracker.js.map +1 -1
- package/dist/connectionManager.d.ts +5 -5
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +13 -18
- 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 +84 -22
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +172 -59
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +30 -17
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +173 -165
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +18 -7
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +18 -8
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +11 -25
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +51 -17
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/contracts.d.ts +5 -5
- package/dist/contracts.js.map +1 -1
- package/dist/deltaManager.d.ts +4 -1
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +33 -6
- package/dist/deltaManager.js.map +1 -1
- package/dist/deltaQueue.js +3 -3
- 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 +8 -1
- 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 +53 -0
- package/dist/protocol.js.map +1 -0
- package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
- package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.d.ts +2 -2
- package/dist/retriableDocumentStorageService.d.ts.map +1 -1
- package/dist/retriableDocumentStorageService.js +2 -2
- package/dist/retriableDocumentStorageService.js.map +1 -1
- 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 +29 -0
- package/lib/catchUpMonitor.d.ts.map +1 -0
- package/lib/catchUpMonitor.js +39 -0
- package/lib/catchUpMonitor.js.map +1 -0
- package/lib/collabWindowTracker.d.ts +1 -1
- package/lib/collabWindowTracker.d.ts.map +1 -1
- package/lib/collabWindowTracker.js +13 -5
- package/lib/collabWindowTracker.js.map +1 -1
- package/lib/connectionManager.d.ts +5 -5
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +14 -21
- 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 +84 -22
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +171 -59
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +30 -17
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +176 -168
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +18 -7
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +19 -9
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +11 -25
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +51 -16
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/contracts.d.ts +5 -5
- package/lib/contracts.js.map +1 -1
- package/lib/deltaManager.d.ts +4 -1
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +35 -8
- package/lib/deltaManager.js.map +1 -1
- package/lib/deltaQueue.js +3 -3
- 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 +8 -1
- 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 +49 -0
- package/lib/protocol.js.map +1 -0
- package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
- package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.d.ts +2 -2
- package/lib/retriableDocumentStorageService.d.ts.map +1 -1
- package/lib/retriableDocumentStorageService.js +2 -2
- package/lib/retriableDocumentStorageService.js.map +1 -1
- package/package.json +26 -20
- package/src/audience.ts +2 -2
- package/src/catchUpMonitor.ts +59 -0
- package/src/collabWindowTracker.ts +15 -6
- package/src/connectionManager.ts +23 -27
- package/src/connectionState.ts +0 -6
- package/src/connectionStateHandler.ts +235 -70
- package/src/container.ts +223 -209
- package/src/containerContext.ts +22 -8
- package/src/containerStorageAdapter.ts +71 -16
- package/src/contracts.ts +7 -7
- package/src/deltaManager.ts +42 -11
- package/src/deltaQueue.ts +3 -3
- package/src/index.ts +4 -0
- package/src/loader.ts +14 -3
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +97 -0
- package/src/retriableDocumentStorageService.ts +8 -2
package/src/containerContext.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
-
import {
|
|
7
|
+
import { LazyPromise } from "@fluidframework/common-utils";
|
|
8
8
|
import {
|
|
9
9
|
IAudience,
|
|
10
10
|
IContainerContext,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
ICodeDetailsLoader,
|
|
23
23
|
IFluidModuleWithDetails,
|
|
24
24
|
ISnapshotTreeWithBlobContents,
|
|
25
|
+
IBatchMessage,
|
|
25
26
|
} from "@fluidframework/container-definitions";
|
|
26
27
|
import {
|
|
27
28
|
IRequest,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
ISummaryTree,
|
|
43
44
|
IVersion,
|
|
44
45
|
MessageType,
|
|
46
|
+
ISummaryContent,
|
|
45
47
|
} from "@fluidframework/protocol-definitions";
|
|
46
48
|
import { PerformanceEvent } from "@fluidframework/telemetry-utils";
|
|
47
49
|
import { Container } from "./container";
|
|
@@ -59,6 +61,8 @@ export class ContainerContext implements IContainerContext {
|
|
|
59
61
|
quorum: IQuorum,
|
|
60
62
|
loader: ILoader,
|
|
61
63
|
submitFn: (type: MessageType, contents: any, batch: boolean, appData: any) => number,
|
|
64
|
+
submitSummaryFn: (summaryOp: ISummaryContent) => number,
|
|
65
|
+
submitBatchFn: (batch: IBatchMessage[]) => number,
|
|
62
66
|
submitSignalFn: (contents: any) => void,
|
|
63
67
|
closeFn: (error?: ICriticalContainerError) => void,
|
|
64
68
|
version: string,
|
|
@@ -76,6 +80,8 @@ export class ContainerContext implements IContainerContext {
|
|
|
76
80
|
quorum,
|
|
77
81
|
loader,
|
|
78
82
|
submitFn,
|
|
83
|
+
submitSummaryFn,
|
|
84
|
+
submitBatchFn,
|
|
79
85
|
submitSignalFn,
|
|
80
86
|
closeFn,
|
|
81
87
|
version,
|
|
@@ -107,8 +113,13 @@ export class ContainerContext implements IContainerContext {
|
|
|
107
113
|
return this.container.clientDetails;
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
private _connected: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* When true, ops are free to flow
|
|
119
|
+
* When false, ops should be kept as pending or rejected
|
|
120
|
+
*/
|
|
110
121
|
public get connected(): boolean {
|
|
111
|
-
return this.
|
|
122
|
+
return this._connected;
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
public get canSummarize(): boolean {
|
|
@@ -166,6 +177,9 @@ export class ContainerContext implements IContainerContext {
|
|
|
166
177
|
quorum: IQuorum,
|
|
167
178
|
public readonly loader: ILoader,
|
|
168
179
|
public readonly submitFn: (type: MessageType, contents: any, batch: boolean, appData: any) => number,
|
|
180
|
+
public readonly submitSummaryFn: (summaryOp: ISummaryContent) => number,
|
|
181
|
+
/** @returns clientSequenceNumber of last message in a batch */
|
|
182
|
+
public readonly submitBatchFn: (batch: IBatchMessage[]) => number,
|
|
169
183
|
public readonly submitSignalFn: (contents: any) => void,
|
|
170
184
|
public readonly closeFn: (error?: ICriticalContainerError) => void,
|
|
171
185
|
public readonly version: string,
|
|
@@ -174,6 +188,7 @@ export class ContainerContext implements IContainerContext {
|
|
|
174
188
|
public readonly pendingLocalState?: unknown,
|
|
175
189
|
|
|
176
190
|
) {
|
|
191
|
+
this._connected = this.container.connected;
|
|
177
192
|
this._quorum = quorum;
|
|
178
193
|
this.taggedLogger = container.subLogger;
|
|
179
194
|
this._fluidModuleP = new LazyPromise<IFluidModuleWithDetails>(
|
|
@@ -183,9 +198,10 @@ export class ContainerContext implements IContainerContext {
|
|
|
183
198
|
}
|
|
184
199
|
|
|
185
200
|
/**
|
|
186
|
-
* @deprecated
|
|
187
|
-
* ContainerContext should only take an IQuorumClients
|
|
188
|
-
*
|
|
201
|
+
* @deprecated Temporary migratory API, to be removed when customers no longer need it.
|
|
202
|
+
* When removed, `ContainerContext` should only take an {@link @fluidframework/container-definitions#IQuorumClients}
|
|
203
|
+
* rather than an {@link @fluidframework/protocol-definitions#IQuorum}.
|
|
204
|
+
* See {@link @fluidframework/container-definitions#IContainerContext} for more details.
|
|
189
205
|
*/
|
|
190
206
|
public getSpecifiedCodeDetails(): IFluidCodeDetails | undefined {
|
|
191
207
|
return (this._quorum.get("code") ?? this._quorum.get("code2")) as IFluidCodeDetails | undefined;
|
|
@@ -223,9 +239,7 @@ export class ContainerContext implements IContainerContext {
|
|
|
223
239
|
|
|
224
240
|
public setConnectionState(connected: boolean, clientId?: string) {
|
|
225
241
|
const runtime = this.runtime;
|
|
226
|
-
|
|
227
|
-
assert(connected === this.connected, 0x0de /* "Mismatch in connection state while setting" */);
|
|
228
|
-
|
|
242
|
+
this._connected = connected;
|
|
229
243
|
runtime.setConnectionState(connected, clientId);
|
|
230
244
|
}
|
|
231
245
|
|
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
6
|
+
import { IDisposable, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
+
import { assert } from "@fluidframework/common-utils";
|
|
7
8
|
import { ISnapshotTreeWithBlobContents } from "@fluidframework/container-definitions";
|
|
8
9
|
import {
|
|
10
|
+
FetchSource,
|
|
11
|
+
IDocumentService,
|
|
9
12
|
IDocumentStorageService,
|
|
10
13
|
IDocumentStorageServicePolicies,
|
|
11
14
|
ISummaryContext,
|
|
12
15
|
} from "@fluidframework/driver-definitions";
|
|
16
|
+
import { UsageError } from "@fluidframework/driver-utils";
|
|
13
17
|
import {
|
|
14
18
|
ICreateBlobResponse,
|
|
15
19
|
ISnapshotTree,
|
|
@@ -18,14 +22,52 @@ import {
|
|
|
18
22
|
IVersion,
|
|
19
23
|
} from "@fluidframework/protocol-definitions";
|
|
20
24
|
import { IDetachedBlobStorage } from "./loader";
|
|
25
|
+
import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService";
|
|
26
|
+
import { RetriableDocumentStorageService } from "./retriableDocumentStorageService";
|
|
21
27
|
|
|
22
28
|
/**
|
|
23
29
|
* This class wraps the actual storage and make sure no wrong apis are called according to
|
|
24
30
|
* container attach state.
|
|
25
31
|
*/
|
|
26
|
-
export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
32
|
+
export class ContainerStorageAdapter implements IDocumentStorageService, IDisposable {
|
|
27
33
|
private readonly blobContents: { [id: string]: ArrayBufferLike; } = {};
|
|
28
|
-
|
|
34
|
+
private _storageService: IDocumentStorageService & Partial<IDisposable>;
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
detachedBlobStorage: IDetachedBlobStorage | undefined,
|
|
38
|
+
private readonly logger: ITelemetryLogger,
|
|
39
|
+
private readonly captureProtocolSummary?: () => ISummaryTree,
|
|
40
|
+
) {
|
|
41
|
+
this._storageService = new BlobOnlyStorage(detachedBlobStorage, logger);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
disposed: boolean = false;
|
|
45
|
+
dispose(error?: Error): void {
|
|
46
|
+
this._storageService?.dispose?.(error);
|
|
47
|
+
this.disposed = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public async connectToService(service: IDocumentService): Promise<void> {
|
|
51
|
+
if (!(this._storageService instanceof BlobOnlyStorage)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const storageService = await service.connectToStorage();
|
|
56
|
+
const retriableStorage = this._storageService =
|
|
57
|
+
new RetriableDocumentStorageService(
|
|
58
|
+
storageService,
|
|
59
|
+
this.logger);
|
|
60
|
+
|
|
61
|
+
if (this.captureProtocolSummary !== undefined) {
|
|
62
|
+
this.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
|
|
63
|
+
this._storageService =
|
|
64
|
+
new ProtocolTreeStorageService(retriableStorage, this.captureProtocolSummary);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ensure we did not lose that policy in the process of wrapping
|
|
68
|
+
assert(storageService.policies?.minBlobSize === this._storageService.policies?.minBlobSize,
|
|
69
|
+
0x0e0 /* "lost minBlobSize policy" */);
|
|
70
|
+
}
|
|
29
71
|
|
|
30
72
|
public loadSnapshotForRehydratingContainer(snapshotTree: ISnapshotTreeWithBlobContents) {
|
|
31
73
|
this.getBlobContents(snapshotTree);
|
|
@@ -44,17 +86,17 @@ export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
|
44
86
|
// back-compat 0.40 containerRuntime requests policies even in detached container if storage is present
|
|
45
87
|
// and storage is always present in >=0.41.
|
|
46
88
|
try {
|
|
47
|
-
return this.
|
|
89
|
+
return this._storageService.policies;
|
|
48
90
|
} catch (e) {}
|
|
49
91
|
return undefined;
|
|
50
92
|
}
|
|
51
93
|
|
|
52
94
|
public get repositoryUrl(): string {
|
|
53
|
-
return this.
|
|
95
|
+
return this._storageService.repositoryUrl;
|
|
54
96
|
}
|
|
55
97
|
|
|
56
98
|
public async getSnapshotTree(version?: IVersion, scenarioName?: string): Promise<ISnapshotTree | null> {
|
|
57
|
-
return this.
|
|
99
|
+
return this._storageService.getSnapshotTree(version, scenarioName);
|
|
58
100
|
}
|
|
59
101
|
|
|
60
102
|
public async readBlob(id: string): Promise<ArrayBufferLike> {
|
|
@@ -62,23 +104,28 @@ export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
|
62
104
|
if (blob !== undefined) {
|
|
63
105
|
return blob;
|
|
64
106
|
}
|
|
65
|
-
return this.
|
|
107
|
+
return this._storageService.readBlob(id);
|
|
66
108
|
}
|
|
67
109
|
|
|
68
|
-
public async getVersions(
|
|
69
|
-
|
|
110
|
+
public async getVersions(
|
|
111
|
+
versionId: string | null,
|
|
112
|
+
count: number,
|
|
113
|
+
scenarioName?: string,
|
|
114
|
+
fetchSource?: FetchSource,
|
|
115
|
+
): Promise<IVersion[]> {
|
|
116
|
+
return this._storageService.getVersions(versionId, count, scenarioName, fetchSource);
|
|
70
117
|
}
|
|
71
118
|
|
|
72
119
|
public async uploadSummaryWithContext(summary: ISummaryTree, context: ISummaryContext): Promise<string> {
|
|
73
|
-
return this.
|
|
120
|
+
return this._storageService.uploadSummaryWithContext(summary, context);
|
|
74
121
|
}
|
|
75
122
|
|
|
76
123
|
public async downloadSummary(handle: ISummaryHandle): Promise<ISummaryTree> {
|
|
77
|
-
return this.
|
|
124
|
+
return this._storageService.downloadSummary(handle);
|
|
78
125
|
}
|
|
79
126
|
|
|
80
127
|
public async createBlob(file: ArrayBufferLike): Promise<ICreateBlobResponse> {
|
|
81
|
-
return this.
|
|
128
|
+
return this._storageService.createBlob(file);
|
|
82
129
|
}
|
|
83
130
|
}
|
|
84
131
|
|
|
@@ -86,18 +133,25 @@ export class ContainerStorageAdapter implements IDocumentStorageService {
|
|
|
86
133
|
* Storage which only supports createBlob() and readBlob(). This is used with IDetachedBlobStorage to support
|
|
87
134
|
* blobs in detached containers.
|
|
88
135
|
*/
|
|
89
|
-
|
|
136
|
+
class BlobOnlyStorage implements IDocumentStorageService {
|
|
90
137
|
constructor(
|
|
91
|
-
private readonly
|
|
138
|
+
private readonly detachedStorage: IDetachedBlobStorage | undefined,
|
|
92
139
|
private readonly logger: ITelemetryLogger,
|
|
93
140
|
) { }
|
|
94
141
|
|
|
95
142
|
public async createBlob(content: ArrayBufferLike): Promise<ICreateBlobResponse> {
|
|
96
|
-
return this.
|
|
143
|
+
return this.verifyStorage().createBlob(content);
|
|
97
144
|
}
|
|
98
145
|
|
|
99
146
|
public async readBlob(blobId: string): Promise<ArrayBufferLike> {
|
|
100
|
-
return this.
|
|
147
|
+
return this.verifyStorage().readBlob(blobId);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private verifyStorage(): IDetachedBlobStorage {
|
|
151
|
+
if (this.detachedStorage === undefined) {
|
|
152
|
+
throw new UsageError("Real storage calls not allowed in Unattached container");
|
|
153
|
+
}
|
|
154
|
+
return this.detachedStorage;
|
|
101
155
|
}
|
|
102
156
|
|
|
103
157
|
public get policies(): IDocumentStorageServicePolicies | undefined {
|
|
@@ -117,6 +171,7 @@ export class BlobOnlyStorage implements IDocumentStorageService {
|
|
|
117
171
|
/* eslint-enable @typescript-eslint/unbound-method */
|
|
118
172
|
|
|
119
173
|
private notCalled(): never {
|
|
174
|
+
this.verifyStorage();
|
|
120
175
|
try {
|
|
121
176
|
// some browsers may not populate stack unless exception is thrown
|
|
122
177
|
throw new Error("BlobOnlyStorage not implemented method used");
|
package/src/contracts.ts
CHANGED
|
@@ -56,13 +56,13 @@ export interface IConnectionManager {
|
|
|
56
56
|
|
|
57
57
|
readonly readOnlyInfo: ReadOnlyInfo;
|
|
58
58
|
|
|
59
|
-
// Various connectivity
|
|
59
|
+
// Various connectivity properties for telemetry describing type of current connection
|
|
60
60
|
// Things like connection mode, service info, etc.
|
|
61
61
|
// Called when connection state changes (connect / disconnect)
|
|
62
62
|
readonly connectionProps: ITelemetryProperties;
|
|
63
63
|
|
|
64
64
|
// Verbose information about connection logged to telemetry in case of issues with
|
|
65
|
-
// maintaining
|
|
65
|
+
// maintaining healthy connection, including op gaps, not receiving join op in time, etc.
|
|
66
66
|
// Contains details information, like sequence numbers at connection time, initial ops info, etc.
|
|
67
67
|
readonly connectionVerboseProps: ITelemetryProperties;
|
|
68
68
|
|
|
@@ -73,7 +73,7 @@ export interface IConnectionManager {
|
|
|
73
73
|
prepareMessageToSend(message: Omit<IDocumentMessage, "clientSequenceNumber">): IDocumentMessage | undefined;
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* Called before
|
|
76
|
+
* Called before incoming message is processed. Incoming messages can be combing from connection,
|
|
77
77
|
* but also could come from storage.
|
|
78
78
|
* This call allows connection manager to adjust knowledge about acked ops sent on previous connection.
|
|
79
79
|
* Can be called at any time, including when there is no active connection.
|
|
@@ -107,11 +107,11 @@ export interface IConnectionManager {
|
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* This interface represents a set of callbacks provided by DeltaManager to IConnectionManager on its creation
|
|
110
|
-
* IConnectionManager instance will use them to communicate to DeltaManager
|
|
110
|
+
* IConnectionManager instance will use them to communicate to DeltaManager about various events.
|
|
111
111
|
*/
|
|
112
112
|
export interface IConnectionManagerFactoryArgs {
|
|
113
113
|
/**
|
|
114
|
-
* Called by connection manager for each
|
|
114
|
+
* Called by connection manager for each incoming op. Some ops maybe delivered before
|
|
115
115
|
* connectHandler is called (initial ops on socket connection)
|
|
116
116
|
*/
|
|
117
117
|
readonly incomingOpHandler: (messages: ISequencedDocumentMessage[], reason: string) => void;
|
|
@@ -131,8 +131,8 @@ export interface IConnectionManagerFactoryArgs {
|
|
|
131
131
|
readonly reconnectionDelayHandler: (delayMs: number, error: unknown) => void;
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
|
-
* Called by connection manager
|
|
135
|
-
* Expects dispose() call in
|
|
134
|
+
* Called by connection manager whenever critical error happens and container should be closed.
|
|
135
|
+
* Expects dispose() call in response to this call.
|
|
136
136
|
*/
|
|
137
137
|
readonly closeHandler: (error?: any) => void;
|
|
138
138
|
|
package/src/deltaManager.ts
CHANGED
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
normalizeError,
|
|
26
26
|
logIfFalse,
|
|
27
27
|
safeRaiseEvent,
|
|
28
|
+
MonitoringContext,
|
|
29
|
+
loggerToMonitoringContext,
|
|
28
30
|
} from "@fluidframework/telemetry-utils";
|
|
29
31
|
import {
|
|
30
32
|
IDocumentDeltaStorageService,
|
|
@@ -47,6 +49,7 @@ import {
|
|
|
47
49
|
DataCorruptionError,
|
|
48
50
|
extractSafePropertiesFromMessage,
|
|
49
51
|
DataProcessingError,
|
|
52
|
+
UsageError,
|
|
50
53
|
} from "@fluidframework/container-utils";
|
|
51
54
|
import { DeltaQueue } from "./deltaQueue";
|
|
52
55
|
import {
|
|
@@ -89,6 +92,15 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
89
92
|
private pending: ISequencedDocumentMessage[] = [];
|
|
90
93
|
private fetchReason: string | undefined;
|
|
91
94
|
|
|
95
|
+
private readonly mc: MonitoringContext;
|
|
96
|
+
|
|
97
|
+
// A boolean used to assert that ops are not being sent while processing another op.
|
|
98
|
+
private currentlyProcessingOps: boolean = false;
|
|
99
|
+
|
|
100
|
+
// Feature gate that closes a container when sending an op if the container is
|
|
101
|
+
// concurrently processing another op
|
|
102
|
+
private readonly preventConcurrentOpSend: boolean = true;
|
|
103
|
+
|
|
92
104
|
// The minimum sequence number and last sequence number received from the server
|
|
93
105
|
private minSequenceNumber: number = 0;
|
|
94
106
|
|
|
@@ -113,6 +125,8 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
113
125
|
private previouslyProcessedMessage: ISequencedDocumentMessage | undefined;
|
|
114
126
|
|
|
115
127
|
// The sequence number we initially loaded from
|
|
128
|
+
// In case of reading from a snapshot or pending state, its value will be equal to
|
|
129
|
+
// the last message that got serialized.
|
|
116
130
|
private initSequenceNumber: number = 0;
|
|
117
131
|
|
|
118
132
|
private readonly _inbound: DeltaQueue<ISequencedDocumentMessage>;
|
|
@@ -185,9 +199,12 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
185
199
|
public get readOnlyInfo() { return this.connectionManager.readOnlyInfo; }
|
|
186
200
|
public get clientDetails() { return this.connectionManager.clientDetails; }
|
|
187
201
|
|
|
188
|
-
public submit(type: MessageType, contents
|
|
202
|
+
public submit(type: MessageType, contents?: string, batch = false, metadata?: any) {
|
|
203
|
+
if (this.currentlyProcessingOps && this.preventConcurrentOpSend) {
|
|
204
|
+
this.close(new UsageError("Making changes to data model is disallowed while processing ops."));
|
|
205
|
+
}
|
|
189
206
|
const messagePartial: Omit<IDocumentMessage, "clientSequenceNumber"> = {
|
|
190
|
-
contents
|
|
207
|
+
contents,
|
|
191
208
|
metadata,
|
|
192
209
|
referenceSequenceNumber: this.lastProcessedSequenceNumber,
|
|
193
210
|
type,
|
|
@@ -196,13 +213,14 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
196
213
|
if (!batch) {
|
|
197
214
|
this.flush();
|
|
198
215
|
}
|
|
199
|
-
|
|
200
216
|
const message = this.connectionManager.prepareMessageToSend(messagePartial);
|
|
201
217
|
if (message === undefined) {
|
|
202
218
|
return -1;
|
|
203
219
|
}
|
|
204
220
|
|
|
205
|
-
|
|
221
|
+
if (contents !== undefined) {
|
|
222
|
+
this.opsSize += contents.length;
|
|
223
|
+
}
|
|
206
224
|
|
|
207
225
|
this.messageBuffer.push(message);
|
|
208
226
|
|
|
@@ -211,22 +229,32 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
211
229
|
if (!batch) {
|
|
212
230
|
this.flush();
|
|
213
231
|
}
|
|
214
|
-
|
|
215
232
|
return message.clientSequenceNumber;
|
|
216
233
|
}
|
|
217
234
|
|
|
218
235
|
public submitSignal(content: any) { return this.connectionManager.submitSignal(content); }
|
|
219
236
|
|
|
220
237
|
public flush() {
|
|
221
|
-
|
|
238
|
+
const batch = this.messageBuffer;
|
|
239
|
+
if (batch.length === 0) {
|
|
222
240
|
return;
|
|
223
241
|
}
|
|
224
242
|
|
|
243
|
+
this.messageBuffer = [];
|
|
244
|
+
|
|
225
245
|
// The prepareFlush event allows listeners to append metadata to the batch prior to submission.
|
|
226
|
-
this.emit("prepareSend",
|
|
246
|
+
this.emit("prepareSend", batch);
|
|
227
247
|
|
|
228
|
-
|
|
229
|
-
|
|
248
|
+
if (batch.length === 1) {
|
|
249
|
+
assert(batch[0].metadata?.batch === undefined, 0x3c9 /* no batch markup on single message */);
|
|
250
|
+
} else {
|
|
251
|
+
assert(batch[0].metadata?.batch === true, 0x3ca /* no start batch markup */);
|
|
252
|
+
assert(batch[batch.length - 1].metadata?.batch === false, 0x3cb /* no end batch markup */);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.connectionManager.sendMessages(batch);
|
|
256
|
+
|
|
257
|
+
assert(this.messageBuffer.length === 0, 0x3cc /* reentrancy */);
|
|
230
258
|
}
|
|
231
259
|
|
|
232
260
|
public get connectionProps(): ITelemetryProperties {
|
|
@@ -293,7 +321,8 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
293
321
|
};
|
|
294
322
|
|
|
295
323
|
this.connectionManager = createConnectionManager(props);
|
|
296
|
-
|
|
324
|
+
this.mc = loggerToMonitoringContext(logger);
|
|
325
|
+
this.preventConcurrentOpSend = this.mc.config.getBoolean("Fluid.Container.ConcurrentOpSend") === true;
|
|
297
326
|
this._inbound = new DeltaQueue<ISequencedDocumentMessage>(
|
|
298
327
|
(op) => {
|
|
299
328
|
this.processInboundMessage(op);
|
|
@@ -762,6 +791,8 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
762
791
|
|
|
763
792
|
private processInboundMessage(message: ISequencedDocumentMessage): void {
|
|
764
793
|
const startTime = Date.now();
|
|
794
|
+
assert(!this.currentlyProcessingOps, 0x3af /* Already processing ops. */);
|
|
795
|
+
this.currentlyProcessingOps = true;
|
|
765
796
|
this.lastProcessedMessage = message;
|
|
766
797
|
|
|
767
798
|
// All non-system messages are coming from some client, and should have clientId
|
|
@@ -816,7 +847,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
816
847
|
throw new Error("Attempted to process an inbound message without a handler attached");
|
|
817
848
|
}
|
|
818
849
|
this.handler.process(message);
|
|
819
|
-
|
|
850
|
+
this.currentlyProcessingOps = false;
|
|
820
851
|
const endTime = Date.now();
|
|
821
852
|
|
|
822
853
|
// Should be last, after changing this.lastProcessedSequenceNumber above, as many callers
|
package/src/deltaQueue.ts
CHANGED
|
@@ -114,9 +114,9 @@ export class DeltaQueue<T>
|
|
|
114
114
|
if (this.anythingToProcess() && this.processingPromise === undefined) {
|
|
115
115
|
// Use a resolved promise to start the processing on a separate stack.
|
|
116
116
|
this.processingPromise = Promise.resolve().then(() => {
|
|
117
|
-
assert(this.processingPromise !== undefined,
|
|
117
|
+
assert(this.processingPromise !== undefined, 0x37f /* reentrancy? */);
|
|
118
118
|
const result = this.processDeltas();
|
|
119
|
-
assert(this.processingPromise !== undefined,
|
|
119
|
+
assert(this.processingPromise !== undefined, 0x380 /* reentrancy? */);
|
|
120
120
|
// WARNING: Do not move next line to .finally() clause!
|
|
121
121
|
// It runs async and creates a race condition where incoming ensureProcessing() call observes
|
|
122
122
|
// from previous run while previous run is over (but finally clause was not scheduled yet)
|
|
@@ -128,7 +128,7 @@ export class DeltaQueue<T>
|
|
|
128
128
|
this.emit("error", error);
|
|
129
129
|
return { count: 0, duration: 0 };
|
|
130
130
|
});
|
|
131
|
-
assert(this.processingPromise !== undefined,
|
|
131
|
+
assert(this.processingPromise !== undefined, 0x381 /* processDeltas() should run async */);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
|
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) {
|
|
@@ -132,7 +133,7 @@ export interface ILoaderOptions extends ILoaderOptions1 {
|
|
|
132
133
|
|
|
133
134
|
/**
|
|
134
135
|
* @deprecated IFluidModuleWithDetails interface is moved to
|
|
135
|
-
* {@link @fluidframework/container-
|
|
136
|
+
* {@link @fluidframework/container-definitions#IFluidModuleWithDetails}
|
|
136
137
|
* to have all the code loading modules in one package. #8193
|
|
137
138
|
* Encapsulates a module entry point with corresponding code details.
|
|
138
139
|
*/
|
|
@@ -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,97 @@
|
|
|
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
|
+
import { canBeCoalescedByService } from "@fluidframework/driver-utils";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Function to be used for creating a protocol handler.
|
|
25
|
+
*/
|
|
26
|
+
export type ProtocolHandlerBuilder = (
|
|
27
|
+
attributes: IDocumentAttributes,
|
|
28
|
+
snapshot: IQuorumSnapshot,
|
|
29
|
+
sendProposal: (key: string, value: any) => number,
|
|
30
|
+
initialClients: ISignalClient[],
|
|
31
|
+
) => IProtocolHandler;
|
|
32
|
+
|
|
33
|
+
export interface IProtocolHandler extends IBaseProtocolHandler {
|
|
34
|
+
readonly audience: IAudienceOwner;
|
|
35
|
+
processSignal(message: ISignalMessage);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandler {
|
|
39
|
+
constructor(
|
|
40
|
+
attributes: IDocumentAttributes,
|
|
41
|
+
quorumSnapshot: IQuorumSnapshot,
|
|
42
|
+
sendProposal: (key: string, value: any) => number,
|
|
43
|
+
initialClients: ISignalClient[],
|
|
44
|
+
readonly audience: IAudienceOwner,
|
|
45
|
+
) {
|
|
46
|
+
super(
|
|
47
|
+
attributes.minimumSequenceNumber,
|
|
48
|
+
attributes.sequenceNumber,
|
|
49
|
+
attributes.term,
|
|
50
|
+
quorumSnapshot.members,
|
|
51
|
+
quorumSnapshot.proposals,
|
|
52
|
+
quorumSnapshot.values,
|
|
53
|
+
sendProposal,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
for (const initialClient of initialClients) {
|
|
57
|
+
this.audience.addMember(initialClient.clientId, initialClient.client);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public processMessage(message: ISequencedDocumentMessage, local: boolean): IProcessMessageResult {
|
|
62
|
+
const client: ILocalSequencedClient | undefined = this.quorum.getMember(message.clientId);
|
|
63
|
+
|
|
64
|
+
// Check and report if we're getting messages from a clientId that we previously
|
|
65
|
+
// flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
|
|
66
|
+
if (message.clientId != null) {
|
|
67
|
+
if (client === undefined && message.type !== MessageType.ClientJoin) {
|
|
68
|
+
// pre-0.58 error message: messageClientIdMissingFromQuorum
|
|
69
|
+
throw new Error("Remote message's clientId is missing from the quorum");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (client?.shouldHaveLeft === true && !canBeCoalescedByService(message)) {
|
|
73
|
+
// pre-0.58 error message: messageClientIdShouldHaveLeft
|
|
74
|
+
throw new Error("Remote message's clientId already should have left");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return super.processMessage(message, local);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public processSignal(message: ISignalMessage) {
|
|
82
|
+
const innerContent = message.content as { content: any; type: string; };
|
|
83
|
+
switch (innerContent.type) {
|
|
84
|
+
case MessageType.ClientJoin: {
|
|
85
|
+
const newClient = innerContent.content as ISignalClient;
|
|
86
|
+
this.audience.addMember(newClient.clientId, newClient.client);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case MessageType.ClientLeave: {
|
|
90
|
+
const leftClientId = innerContent.content as string;
|
|
91
|
+
this.audience.removeMember(leftClientId);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
default: break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|