@fluidframework/container-loader 1.2.7 → 2.0.0-dev.1.3.0.96595
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/.mocharc.js +12 -0
- package/dist/audience.d.ts +2 -6
- package/dist/audience.d.ts.map +1 -1
- package/dist/audience.js +6 -11
- 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 +43 -22
- 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 +29 -17
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +181 -171
- 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 +39 -12
- 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 +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 +27 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +79 -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 -6
- package/lib/audience.d.ts.map +1 -1
- package/lib/audience.js +6 -11
- 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 +44 -25
- 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 +29 -17
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +184 -174
- 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 +41 -14
- 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 +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 +27 -0
- package/lib/protocol.d.ts.map +1 -0
- package/lib/protocol.js +75 -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 +27 -19
- package/src/audience.ts +8 -14
- package/src/catchUpMonitor.ts +59 -0
- package/src/collabWindowTracker.ts +15 -6
- package/src/connectionManager.ts +56 -33
- package/src/connectionState.ts +0 -6
- package/src/connectionStateHandler.ts +235 -70
- package/src/container.ts +241 -218
- package/src/containerContext.ts +22 -8
- package/src/containerStorageAdapter.ts +71 -16
- package/src/contracts.ts +7 -7
- package/src/deltaManager.ts +48 -15
- package/src/deltaQueue.ts +34 -28
- package/src/index.ts +4 -0
- package/src/loader.ts +14 -3
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +120 -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);
|
|
@@ -410,7 +439,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
410
439
|
|
|
411
440
|
if (prefetchType !== "none") {
|
|
412
441
|
const cacheOnly = prefetchType === "cached";
|
|
413
|
-
await this.fetchMissingDeltasCore(
|
|
442
|
+
await this.fetchMissingDeltasCore(`DocumentOpen_${prefetchType}`, cacheOnly);
|
|
414
443
|
|
|
415
444
|
// Keep going with fetching ops from storage once we have all cached ops in.
|
|
416
445
|
// But do not block load and make this request async / not blocking this api.
|
|
@@ -418,7 +447,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
418
447
|
// (which in most cases will happen when we are done processing cached ops)
|
|
419
448
|
if (cacheOnly) {
|
|
420
449
|
// fire and forget
|
|
421
|
-
this.fetchMissingDeltas("
|
|
450
|
+
this.fetchMissingDeltas("PostDocumentOpen");
|
|
422
451
|
}
|
|
423
452
|
}
|
|
424
453
|
|
|
@@ -453,6 +482,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
453
482
|
private async getDeltas(
|
|
454
483
|
from: number, // inclusive
|
|
455
484
|
to: number | undefined, // exclusive
|
|
485
|
+
fetchReason: string,
|
|
456
486
|
callback: (messages: ISequencedDocumentMessage[]) => void,
|
|
457
487
|
cacheOnly: boolean) {
|
|
458
488
|
const docService = this.serviceProvider();
|
|
@@ -473,7 +503,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
473
503
|
// received through delta stream. Validate that before moving forward.
|
|
474
504
|
if (this.lastQueuedSequenceNumber >= lastExpectedOp) {
|
|
475
505
|
this.logger.sendPerformanceEvent({
|
|
476
|
-
reason:
|
|
506
|
+
reason: fetchReason,
|
|
477
507
|
eventName: "ExtraStorageCall",
|
|
478
508
|
early: true,
|
|
479
509
|
from,
|
|
@@ -521,7 +551,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
521
551
|
to, // exclusive
|
|
522
552
|
controller.signal,
|
|
523
553
|
cacheOnly,
|
|
524
|
-
|
|
554
|
+
fetchReason);
|
|
525
555
|
|
|
526
556
|
// eslint-disable-next-line no-constant-condition
|
|
527
557
|
while (true) {
|
|
@@ -761,6 +791,8 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
761
791
|
|
|
762
792
|
private processInboundMessage(message: ISequencedDocumentMessage): void {
|
|
763
793
|
const startTime = Date.now();
|
|
794
|
+
assert(!this.currentlyProcessingOps, 0x3af /* Already processing ops. */);
|
|
795
|
+
this.currentlyProcessingOps = true;
|
|
764
796
|
this.lastProcessedMessage = message;
|
|
765
797
|
|
|
766
798
|
// All non-system messages are coming from some client, and should have clientId
|
|
@@ -815,7 +847,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
815
847
|
throw new Error("Attempted to process an inbound message without a handler attached");
|
|
816
848
|
}
|
|
817
849
|
this.handler.process(message);
|
|
818
|
-
|
|
850
|
+
this.currentlyProcessingOps = false;
|
|
819
851
|
const endTime = Date.now();
|
|
820
852
|
|
|
821
853
|
// Should be last, after changing this.lastProcessedSequenceNumber above, as many callers
|
|
@@ -876,6 +908,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
|
|
|
876
908
|
await this.getDeltas(
|
|
877
909
|
from,
|
|
878
910
|
to,
|
|
911
|
+
fetchReason,
|
|
879
912
|
(messages) => {
|
|
880
913
|
this.refreshDelayInfo(this.deltaStorageDelayId);
|
|
881
914
|
this.enqueueMessages(messages, fetchReason);
|
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, 0x37f /* reentrancy? */);
|
|
118
|
+
const result = this.processDeltas();
|
|
119
|
+
assert(this.processingPromise !== undefined, 0x380 /* 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, 0x381 /* 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
|
}
|