@fluidframework/container-runtime 0.58.3000-61081 → 0.59.2000-61729
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blobManager.d.ts +13 -1
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +52 -0
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.js +8 -8
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +27 -3
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +100 -14
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.js +8 -1
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +9 -3
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +22 -6
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +13 -5
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +39 -18
- package/dist/dataStores.js.map +1 -1
- package/dist/deltaScheduler.d.ts +4 -5
- package/dist/deltaScheduler.d.ts.map +1 -1
- package/dist/deltaScheduler.js +54 -35
- package/dist/deltaScheduler.js.map +1 -1
- package/dist/garbageCollection.d.ts +31 -27
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +76 -75
- package/dist/garbageCollection.js.map +1 -1
- package/dist/orderedClientElection.d.ts.map +1 -1
- package/dist/orderedClientElection.js +2 -2
- package/dist/orderedClientElection.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/summarizerClientElection.d.ts.map +1 -1
- package/dist/summarizerClientElection.js +8 -0
- package/dist/summarizerClientElection.js.map +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +2 -3
- package/dist/summaryGenerator.js.map +1 -1
- package/lib/blobManager.d.ts +13 -1
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +52 -0
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.js +8 -8
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +27 -3
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +101 -15
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.js +8 -1
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +9 -3
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +22 -6
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +13 -5
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +39 -18
- package/lib/dataStores.js.map +1 -1
- package/lib/deltaScheduler.d.ts +4 -5
- package/lib/deltaScheduler.d.ts.map +1 -1
- package/lib/deltaScheduler.js +54 -35
- package/lib/deltaScheduler.js.map +1 -1
- package/lib/garbageCollection.d.ts +31 -27
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +75 -74
- package/lib/garbageCollection.js.map +1 -1
- package/lib/orderedClientElection.d.ts.map +1 -1
- package/lib/orderedClientElection.js +2 -2
- package/lib/orderedClientElection.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/summarizerClientElection.d.ts.map +1 -1
- package/lib/summarizerClientElection.js +8 -0
- package/lib/summarizerClientElection.js.map +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +2 -3
- package/lib/summaryGenerator.js.map +1 -1
- package/package.json +33 -21
- package/src/blobManager.ts +60 -1
- package/src/connectionTelemetry.ts +9 -9
- package/src/containerRuntime.ts +106 -17
- package/src/dataStore.ts +7 -1
- package/src/dataStoreContext.ts +22 -7
- package/src/dataStores.ts +40 -19
- package/src/deltaScheduler.ts +65 -39
- package/src/garbageCollection.ts +92 -78
- package/src/orderedClientElection.ts +2 -1
- package/src/packageVersion.ts +1 -1
- package/src/summarizerClientElection.ts +8 -0
- package/src/summaryGenerator.ts +1 -7
package/src/containerRuntime.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
FluidObject,
|
|
11
11
|
IFluidHandle,
|
|
12
12
|
IFluidHandleContext,
|
|
13
|
-
IFluidObject,
|
|
14
13
|
IFluidRouter,
|
|
15
14
|
IRequest,
|
|
16
15
|
IResponse,
|
|
@@ -101,6 +100,7 @@ import {
|
|
|
101
100
|
seqFromTree,
|
|
102
101
|
calculateStats,
|
|
103
102
|
} from "@fluidframework/runtime-utils";
|
|
103
|
+
import { GCDataBuilder } from "@fluidframework/garbage-collector";
|
|
104
104
|
import { v4 as uuid } from "uuid";
|
|
105
105
|
import { ContainerFluidHandleContext } from "./containerHandleContext";
|
|
106
106
|
import { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
@@ -141,6 +141,7 @@ import { formExponentialFn, Throttler } from "./throttler";
|
|
|
141
141
|
import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
|
|
142
142
|
import {
|
|
143
143
|
GarbageCollector,
|
|
144
|
+
GCNodeType,
|
|
144
145
|
gcTreeKey,
|
|
145
146
|
IGarbageCollectionRuntime,
|
|
146
147
|
IGarbageCollector,
|
|
@@ -637,7 +638,7 @@ export class ScheduleManager {
|
|
|
637
638
|
|
|
638
639
|
// This could be the beginning of a new batch or an individual message.
|
|
639
640
|
this.emitter.emit("batchBegin", message);
|
|
640
|
-
this.deltaScheduler.batchBegin();
|
|
641
|
+
this.deltaScheduler.batchBegin(message);
|
|
641
642
|
|
|
642
643
|
const batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;
|
|
643
644
|
if (batch) {
|
|
@@ -658,7 +659,7 @@ export class ScheduleManager {
|
|
|
658
659
|
this.hitError = true;
|
|
659
660
|
this.batchClientId = undefined;
|
|
660
661
|
this.emitter.emit("batchEnd", error, message);
|
|
661
|
-
this.deltaScheduler.batchEnd();
|
|
662
|
+
this.deltaScheduler.batchEnd(message);
|
|
662
663
|
return;
|
|
663
664
|
}
|
|
664
665
|
|
|
@@ -668,7 +669,7 @@ export class ScheduleManager {
|
|
|
668
669
|
if (this.batchClientId === undefined || batch === false) {
|
|
669
670
|
this.batchClientId = undefined;
|
|
670
671
|
this.emitter.emit("batchEnd", undefined, message);
|
|
671
|
-
this.deltaScheduler.batchEnd();
|
|
672
|
+
this.deltaScheduler.batchEnd(message);
|
|
672
673
|
return;
|
|
673
674
|
}
|
|
674
675
|
}
|
|
@@ -907,7 +908,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
907
908
|
return this._flushMode;
|
|
908
909
|
}
|
|
909
910
|
|
|
910
|
-
public get scope():
|
|
911
|
+
public get scope(): FluidObject {
|
|
911
912
|
return this.containerScope;
|
|
912
913
|
}
|
|
913
914
|
|
|
@@ -1076,13 +1077,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1076
1077
|
this.garbageCollector = GarbageCollector.create(
|
|
1077
1078
|
this,
|
|
1078
1079
|
this.runtimeOptions.gcOptions,
|
|
1079
|
-
(
|
|
1080
|
-
(nodePath: string) => this.dataStores.getNodePackagePath(nodePath),
|
|
1081
|
-
/**
|
|
1082
|
-
* Returns the timestamp of the last message seen by this client. This is used by garbage collector as
|
|
1083
|
-
* the current reference timestamp for tracking unreferenced objects.
|
|
1084
|
-
*/
|
|
1085
|
-
() => this.deltaManager.lastMessage?.timestamp ?? this.messageAtLastSummary?.timestamp,
|
|
1080
|
+
(nodePath: string) => this.getGCNodePackagePath(nodePath),
|
|
1086
1081
|
() => this.messageAtLastSummary?.timestamp,
|
|
1087
1082
|
context.baseSnapshot,
|
|
1088
1083
|
async <T>(id: string) => readAndParse<T>(this.storage, id),
|
|
@@ -1134,7 +1129,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1134
1129
|
),
|
|
1135
1130
|
(id: string) => this.summarizerNode.deleteChild(id),
|
|
1136
1131
|
this.mc.logger,
|
|
1137
|
-
async () => this.garbageCollector.
|
|
1132
|
+
async () => this.garbageCollector.getBaseGCDetails(),
|
|
1138
1133
|
(path: string, timestampMs: number, packagePath?: readonly string[]) => this.garbageCollector.nodeUpdated(
|
|
1139
1134
|
path,
|
|
1140
1135
|
"Changed",
|
|
@@ -1833,7 +1828,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1833
1828
|
*/
|
|
1834
1829
|
private async createRootDataStoreLegacy(pkg: string | string[], rootDataStoreId: string): Promise<IFluidRouter> {
|
|
1835
1830
|
const fluidDataStore = await this._createDataStore(pkg, true /* isRoot */, rootDataStoreId);
|
|
1836
|
-
|
|
1831
|
+
// back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel. For
|
|
1832
|
+
// older versions, we still have to call bindToContext.
|
|
1833
|
+
if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
|
|
1834
|
+
fluidDataStore.makeVisibleAndAttachGraph();
|
|
1835
|
+
} else {
|
|
1836
|
+
fluidDataStore.bindToContext();
|
|
1837
|
+
}
|
|
1837
1838
|
return fluidDataStore;
|
|
1838
1839
|
}
|
|
1839
1840
|
|
|
@@ -1905,7 +1906,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1905
1906
|
const fluidDataStore = await this.dataStores._createFluidDataStoreContext(
|
|
1906
1907
|
Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
|
|
1907
1908
|
if (isRoot) {
|
|
1908
|
-
|
|
1909
|
+
// back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel.
|
|
1910
|
+
// For older versions, we still have to call bindToContext.
|
|
1911
|
+
if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
|
|
1912
|
+
fluidDataStore.makeVisibleAndAttachGraph();
|
|
1913
|
+
} else {
|
|
1914
|
+
fluidDataStore.bindToContext();
|
|
1915
|
+
}
|
|
1909
1916
|
this.logger.sendTelemetryEvent({
|
|
1910
1917
|
eventName: "Root datastore with props",
|
|
1911
1918
|
hasProps: props !== undefined,
|
|
@@ -2111,7 +2118,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2111
2118
|
* @param fullGC - true to bypass optimizations and force full generation of GC data.
|
|
2112
2119
|
*/
|
|
2113
2120
|
public async getGCData(fullGC?: boolean): Promise<IGarbageCollectionData> {
|
|
2114
|
-
|
|
2121
|
+
const builder = new GCDataBuilder();
|
|
2122
|
+
const dsGCData = await this.dataStores.getGCData(fullGC);
|
|
2123
|
+
builder.addNodes(dsGCData.gcNodes);
|
|
2124
|
+
|
|
2125
|
+
const blobsGCData = this.blobManager.getGCData(fullGC);
|
|
2126
|
+
builder.addNodes(blobsGCData.gcNodes);
|
|
2127
|
+
return builder.getGCData();
|
|
2115
2128
|
}
|
|
2116
2129
|
|
|
2117
2130
|
/**
|
|
@@ -2127,7 +2140,83 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2127
2140
|
// always referenced, so the used routes is only self-route (empty string).
|
|
2128
2141
|
this.summarizerNode.updateUsedRoutes([""]);
|
|
2129
2142
|
|
|
2130
|
-
|
|
2143
|
+
const dataStoreUsedRoutes: string[] = [];
|
|
2144
|
+
for (const route of usedRoutes) {
|
|
2145
|
+
if (route.split("/")[1] !== BlobManager.basePath) {
|
|
2146
|
+
dataStoreUsedRoutes.push(route);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes, gcTimestamp);
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
/**
|
|
2154
|
+
* When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
|
|
2155
|
+
* scenarios with accessing deleted content.
|
|
2156
|
+
* @param unusedRoutes - The routes that are unused in all data stores in this Container.
|
|
2157
|
+
*/
|
|
2158
|
+
public deleteUnusedRoutes(unusedRoutes: string[]) {
|
|
2159
|
+
const blobManagerUnusedRoutes: string[] = [];
|
|
2160
|
+
const dataStoreUnusedRoutes: string[] = [];
|
|
2161
|
+
for (const route of unusedRoutes) {
|
|
2162
|
+
if (this.isBlobPath(route)) {
|
|
2163
|
+
blobManagerUnusedRoutes.push(route);
|
|
2164
|
+
} else {
|
|
2165
|
+
dataStoreUnusedRoutes.push(route);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
|
|
2170
|
+
this.dataStores.deleteUnusedRoutes(dataStoreUnusedRoutes);
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
/**
|
|
2174
|
+
* Returns a server generated referenced timestamp to be used to track unreferenced nodes by GC.
|
|
2175
|
+
*/
|
|
2176
|
+
public getCurrentReferenceTimestampMs(): number | undefined {
|
|
2177
|
+
// Use the timestamp of the last message seen by this client as that is server generated. If no messages have
|
|
2178
|
+
// been processed, use the timestamp of the message from the last summary.
|
|
2179
|
+
return this.deltaManager.lastMessage?.timestamp ?? this.messageAtLastSummary?.timestamp;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
/**
|
|
2183
|
+
* Returns the type of the GC node. Currently, there are nodes that belong to data store and nodes that belong
|
|
2184
|
+
* to the blob manager.
|
|
2185
|
+
*/
|
|
2186
|
+
public getNodeType(nodePath: string): GCNodeType {
|
|
2187
|
+
if (this.isBlobPath(nodePath)) {
|
|
2188
|
+
return GCNodeType.Blob;
|
|
2189
|
+
}
|
|
2190
|
+
if (this.dataStores.isDataStoreNode(nodePath)) {
|
|
2191
|
+
return GCNodeType.DataStore;
|
|
2192
|
+
}
|
|
2193
|
+
// Root node ("/") and DDS nodes belong to "Other" node types.
|
|
2194
|
+
return GCNodeType.Other;
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
/**
|
|
2198
|
+
* Called by GC to retrieve the package path of the node with the given path. The node should belong to a
|
|
2199
|
+
* data store or an attachment blob.
|
|
2200
|
+
*/
|
|
2201
|
+
public getGCNodePackagePath(nodePath: string): readonly string[] | undefined {
|
|
2202
|
+
// If the node is a blob, return "_blobs" as the package path.
|
|
2203
|
+
if (this.isBlobPath(nodePath)) {
|
|
2204
|
+
return ["_blobs"];
|
|
2205
|
+
}
|
|
2206
|
+
const dataStorePkgPath = this.dataStores.getDataStorePackagePath(nodePath);
|
|
2207
|
+
assert(dataStorePkgPath !== undefined, 0x2d6 /* "Package path requested for unknown node type." */);
|
|
2208
|
+
return dataStorePkgPath;
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
/**
|
|
2212
|
+
* Returns whether a given path is for attachment blobs that are in the format - "/BlobManager.basePath/...".
|
|
2213
|
+
*/
|
|
2214
|
+
private isBlobPath(path: string): boolean {
|
|
2215
|
+
const pathParts = path.split("/");
|
|
2216
|
+
if (pathParts.length < 2 || pathParts[1] !== BlobManager.basePath) {
|
|
2217
|
+
return false;
|
|
2218
|
+
}
|
|
2219
|
+
return true;
|
|
2131
2220
|
}
|
|
2132
2221
|
|
|
2133
2222
|
/**
|
package/src/dataStore.ts
CHANGED
|
@@ -75,7 +75,13 @@ class DataStore implements IDataStore {
|
|
|
75
75
|
alias,
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
-
this.
|
|
78
|
+
// back-compat 0.58.2000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel. For
|
|
79
|
+
// older versions, we still have to call bindToContext.
|
|
80
|
+
if (this.fluidDataStoreChannel.makeVisibleAndAttachGraph !== undefined) {
|
|
81
|
+
this.fluidDataStoreChannel.makeVisibleAndAttachGraph();
|
|
82
|
+
} else {
|
|
83
|
+
this.fluidDataStoreChannel.bindToContext();
|
|
84
|
+
}
|
|
79
85
|
|
|
80
86
|
if (this.runtime.attachState === AttachState.Detached) {
|
|
81
87
|
const localResult = this.datastores.processAliasMessageCore(message);
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -132,7 +132,7 @@ export interface ILocalFluidDataStoreContextProps extends IFluidDataStoreContext
|
|
|
132
132
|
readonly pkg: Readonly<string[]> | undefined;
|
|
133
133
|
readonly snapshotTree: ISnapshotTree | undefined;
|
|
134
134
|
readonly isRootDataStore: boolean | undefined;
|
|
135
|
-
readonly
|
|
135
|
+
readonly makeLocallyVisibleFn: () => void;
|
|
136
136
|
/**
|
|
137
137
|
* @deprecated 0.16 Issue #1635, #3631
|
|
138
138
|
*/
|
|
@@ -261,7 +261,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
261
261
|
private readonly existing: boolean,
|
|
262
262
|
private bindState: BindState,
|
|
263
263
|
public readonly isLocalDataStore: boolean,
|
|
264
|
-
|
|
264
|
+
private readonly makeLocallyVisibleFn: () => void,
|
|
265
265
|
) {
|
|
266
266
|
super();
|
|
267
267
|
|
|
@@ -284,7 +284,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
284
284
|
assert(this.bindState === BindState.NotBound, 0x13b /* "datastore context is already in bound state" */);
|
|
285
285
|
this.bindState = BindState.Binding;
|
|
286
286
|
assert(this.channel !== undefined, 0x13c /* "undefined channel on datastore context" */);
|
|
287
|
-
|
|
287
|
+
this.makeLocallyVisible();
|
|
288
288
|
this.bindState = BindState.Bound;
|
|
289
289
|
};
|
|
290
290
|
|
|
@@ -627,6 +627,15 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
627
627
|
return this._containerRuntime.submitDataStoreSignal(this.id, type, content);
|
|
628
628
|
}
|
|
629
629
|
|
|
630
|
+
/**
|
|
631
|
+
* This is called by the data store channel when it becomes locally visible indicating that it is ready to become
|
|
632
|
+
* globally visible now.
|
|
633
|
+
*/
|
|
634
|
+
public makeLocallyVisible() {
|
|
635
|
+
assert(this.channel !== undefined, 0x2cf /* "undefined channel on datastore context" */);
|
|
636
|
+
this.makeLocallyVisibleFn();
|
|
637
|
+
}
|
|
638
|
+
|
|
630
639
|
protected bindRuntime(channel: IFluidDataStoreChannel) {
|
|
631
640
|
if (this.channel) {
|
|
632
641
|
throw new Error("Runtime already bound");
|
|
@@ -864,7 +873,7 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
864
873
|
props.snapshotTree !== undefined ? true : false /* existing */,
|
|
865
874
|
props.snapshotTree ? BindState.Bound : BindState.NotBound,
|
|
866
875
|
true /* isLocalDataStore */,
|
|
867
|
-
props.
|
|
876
|
+
props.makeLocallyVisibleFn,
|
|
868
877
|
);
|
|
869
878
|
|
|
870
879
|
this.snapshotTree = props.snapshotTree;
|
|
@@ -993,7 +1002,7 @@ export class LocalDetachedFluidDataStoreContext
|
|
|
993
1002
|
|
|
994
1003
|
public async attachRuntime(
|
|
995
1004
|
registry: IProvideFluidDataStoreFactory,
|
|
996
|
-
|
|
1005
|
+
dataStoreChannel: IFluidDataStoreChannel)
|
|
997
1006
|
{
|
|
998
1007
|
assert(this.detachedRuntimeCreation, 0x154 /* "runtime creation is already attached" */);
|
|
999
1008
|
assert(this.channelDeferred === undefined, 0x155 /* "channel deferral is already set" */);
|
|
@@ -1009,10 +1018,16 @@ export class LocalDetachedFluidDataStoreContext
|
|
|
1009
1018
|
this.detachedRuntimeCreation = false;
|
|
1010
1019
|
this.channelDeferred = new Deferred<IFluidDataStoreChannel>();
|
|
1011
1020
|
|
|
1012
|
-
super.bindRuntime(
|
|
1021
|
+
super.bindRuntime(dataStoreChannel);
|
|
1013
1022
|
|
|
1014
1023
|
if (await this.isRoot()) {
|
|
1015
|
-
|
|
1024
|
+
// back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel.
|
|
1025
|
+
// For older versions, we still have to call bindToContext.
|
|
1026
|
+
if (dataStoreChannel.makeVisibleAndAttachGraph !== undefined) {
|
|
1027
|
+
dataStoreChannel.makeVisibleAndAttachGraph();
|
|
1028
|
+
} else {
|
|
1029
|
+
dataStoreChannel.bindToContext();
|
|
1030
|
+
}
|
|
1016
1031
|
}
|
|
1017
1032
|
}
|
|
1018
1033
|
|
package/src/dataStores.ts
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
CreateSummarizerNodeSource,
|
|
19
19
|
IAttachMessage,
|
|
20
20
|
IEnvelope,
|
|
21
|
-
IFluidDataStoreChannel,
|
|
22
21
|
IFluidDataStoreContextDetached,
|
|
23
22
|
IGarbageCollectionData,
|
|
24
23
|
IGarbageCollectionDetailsBase,
|
|
@@ -157,7 +156,7 @@ export class DataStores implements IDisposable {
|
|
|
157
156
|
key,
|
|
158
157
|
{ type: CreateSummarizerNodeSource.FromSummary },
|
|
159
158
|
),
|
|
160
|
-
|
|
159
|
+
makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(key),
|
|
161
160
|
snapshotTree,
|
|
162
161
|
isRootDataStore: undefined,
|
|
163
162
|
writeGCDataAtRoot: this.writeGCDataAtRoot,
|
|
@@ -291,14 +290,20 @@ export class DataStores implements IDisposable {
|
|
|
291
290
|
return this.aliasMap.get(id) !== undefined || this.contexts.get(id) !== undefined;
|
|
292
291
|
}
|
|
293
292
|
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
/**
|
|
294
|
+
* Make the data stores locally visible in the container graph by moving the data store context from unbound to
|
|
295
|
+
* bound list. This data store can now be reached from the root.
|
|
296
|
+
* @param id - The id of the data store context to make visible.
|
|
297
|
+
*/
|
|
298
|
+
private makeDataStoreLocallyVisible(id: string): void {
|
|
296
299
|
const localContext = this.contexts.getUnbound(id);
|
|
297
300
|
assert(!!localContext, 0x15f /* "Could not find unbound context to bind" */);
|
|
298
301
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
+
/**
|
|
303
|
+
* If the container is not detached, it is globally visible to all clients. This data store should also be
|
|
304
|
+
* globally visible. Move it to attaching state and send an "attach" op for it.
|
|
305
|
+
* If the container is detached, this data store will be part of the summary that makes the container attached.
|
|
306
|
+
*/
|
|
302
307
|
if (this.runtime.attachState !== AttachState.Detached) {
|
|
303
308
|
localContext.emit("attaching");
|
|
304
309
|
const message = localContext.generateAttachMessage();
|
|
@@ -308,7 +313,7 @@ export class DataStores implements IDisposable {
|
|
|
308
313
|
this.attachOpFiredForDataStore.add(id);
|
|
309
314
|
}
|
|
310
315
|
|
|
311
|
-
this.contexts.bind(
|
|
316
|
+
this.contexts.bind(id);
|
|
312
317
|
}
|
|
313
318
|
|
|
314
319
|
public createDetachedDataStoreCore(
|
|
@@ -326,7 +331,7 @@ export class DataStores implements IDisposable {
|
|
|
326
331
|
id,
|
|
327
332
|
{ type: CreateSummarizerNodeSource.Local },
|
|
328
333
|
),
|
|
329
|
-
|
|
334
|
+
makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(id),
|
|
330
335
|
snapshotTree: undefined,
|
|
331
336
|
isRootDataStore: isRoot,
|
|
332
337
|
writeGCDataAtRoot: this.writeGCDataAtRoot,
|
|
@@ -347,7 +352,7 @@ export class DataStores implements IDisposable {
|
|
|
347
352
|
id,
|
|
348
353
|
{ type: CreateSummarizerNodeSource.Local },
|
|
349
354
|
),
|
|
350
|
-
|
|
355
|
+
makeLocallyVisibleFn: () => this.makeDataStoreLocallyVisible(id),
|
|
351
356
|
snapshotTree: undefined,
|
|
352
357
|
isRootDataStore: isRoot,
|
|
353
358
|
writeGCDataAtRoot: this.writeGCDataAtRoot,
|
|
@@ -587,7 +592,14 @@ export class DataStores implements IDisposable {
|
|
|
587
592
|
*/
|
|
588
593
|
public deleteUnusedRoutes(unusedRoutes: string[]) {
|
|
589
594
|
for (const route of unusedRoutes) {
|
|
590
|
-
const
|
|
595
|
+
const pathParts = route.split("/");
|
|
596
|
+
// Delete data store only if its route (/datastoreId) is in unusedRoutes. We don't want to delete a data
|
|
597
|
+
// store based on its DDS being unused.
|
|
598
|
+
if (pathParts.length > 2) {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
const dataStoreId = pathParts[1];
|
|
602
|
+
assert(this.contexts.has(dataStoreId), 0x2d7 /* `${dataStoreId} is not a data store` */);
|
|
591
603
|
// Delete the contexts of unused data stores.
|
|
592
604
|
this.contexts.delete(dataStoreId);
|
|
593
605
|
// Delete the summarizer node of the unused data stores.
|
|
@@ -611,15 +623,24 @@ export class DataStores implements IDisposable {
|
|
|
611
623
|
}
|
|
612
624
|
|
|
613
625
|
/**
|
|
614
|
-
*
|
|
615
|
-
|
|
626
|
+
* Called during GC to retrieve the package path of a data store node with the given path.
|
|
627
|
+
*/
|
|
628
|
+
public getDataStorePackagePath(nodePath: string): readonly string[] | undefined {
|
|
629
|
+
// If the node belongs to a data store, return its package path if the data store is loaded. For DDSs, we return
|
|
630
|
+
// the package path of the data store that contains it.
|
|
631
|
+
const context = this.contexts.get(nodePath.split("/")[1]);
|
|
632
|
+
return context?.isLoaded ? context.packagePath : undefined;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Called by GC to know if a node is a data store or not. Data store ids are of the format "/dataStoreId".
|
|
616
637
|
*/
|
|
617
|
-
public
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
return
|
|
638
|
+
public isDataStoreNode(nodePath: string): boolean {
|
|
639
|
+
const pathParts = nodePath.split("/");
|
|
640
|
+
if (pathParts.length === 2 && this.contexts.has(pathParts[1])) {
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
return false;
|
|
623
644
|
}
|
|
624
645
|
}
|
|
625
646
|
|
package/src/deltaScheduler.ts
CHANGED
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
ISequencedDocumentMessage,
|
|
12
12
|
} from "@fluidframework/protocol-definitions";
|
|
13
13
|
|
|
14
|
+
import {
|
|
15
|
+
TelemetryLogger,
|
|
16
|
+
} from "@fluidframework/telemetry-utils";
|
|
14
17
|
/**
|
|
15
18
|
* DeltaScheduler is responsible for the scheduling of inbound delta queue in cases where there
|
|
16
19
|
* is more than one op a particular run of the queue. It does not schedule if there is just one
|
|
@@ -25,18 +28,13 @@ import {
|
|
|
25
28
|
export class DeltaScheduler {
|
|
26
29
|
private readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>;
|
|
27
30
|
// The time for processing ops in a single turn.
|
|
28
|
-
public static readonly processingTime =
|
|
31
|
+
public static readonly processingTime = 50;
|
|
29
32
|
|
|
30
33
|
// The increase in time for processing ops after each turn.
|
|
31
34
|
private readonly processingTimeIncrement = 10;
|
|
32
35
|
|
|
33
36
|
private processingStartTime: number | undefined;
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
// This keeps track of whether the delta scheduler is scheduling a particular run of the
|
|
37
|
-
// the inbound delta queue. Basically, every time the delta queue starts processing with
|
|
38
|
-
// more than one op, this will be set to true until the run completes.
|
|
39
|
-
private isScheduling: boolean = false;
|
|
37
|
+
private currentAllowedProcessingTimeForTurn: number = DeltaScheduler.processingTime;
|
|
40
38
|
|
|
41
39
|
// This keeps track of the number of times inbound queue has been scheduled. After a particular
|
|
42
40
|
// count, we log telemetry for the number of ops processed, the time and number of turns it took
|
|
@@ -44,9 +42,13 @@ export class DeltaScheduler {
|
|
|
44
42
|
private schedulingCount: number = 0;
|
|
45
43
|
|
|
46
44
|
private schedulingLog: {
|
|
47
|
-
|
|
45
|
+
opsRemainingToProcess: number;
|
|
48
46
|
totalProcessingTime: number;
|
|
49
47
|
numberOfTurns: number;
|
|
48
|
+
numberOfBatchesProcessed: number;
|
|
49
|
+
lastSequenceNumber: number;
|
|
50
|
+
firstSequenceNumber: number;
|
|
51
|
+
startTime: number;
|
|
50
52
|
} | undefined;
|
|
51
53
|
|
|
52
54
|
constructor(
|
|
@@ -57,50 +59,72 @@ export class DeltaScheduler {
|
|
|
57
59
|
this.deltaManager.inbound.on("idle", () => { this.inboundQueueIdle(); });
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
public batchBegin() {
|
|
62
|
+
public batchBegin(message: ISequencedDocumentMessage) {
|
|
61
63
|
if (!this.processingStartTime) {
|
|
62
64
|
this.processingStartTime = performance.now();
|
|
63
65
|
}
|
|
66
|
+
if (this.schedulingLog === undefined && this.schedulingCount % 500 === 0) {
|
|
67
|
+
// Every 500th time we are scheduling the inbound queue, we log telemetry for the
|
|
68
|
+
// number of ops processed, the time and number of turns it took to process the ops.
|
|
69
|
+
this.schedulingLog = {
|
|
70
|
+
opsRemainingToProcess: 0,
|
|
71
|
+
numberOfTurns: 1,
|
|
72
|
+
totalProcessingTime: 0,
|
|
73
|
+
numberOfBatchesProcessed: 0,
|
|
74
|
+
firstSequenceNumber: message.sequenceNumber,
|
|
75
|
+
lastSequenceNumber: message.sequenceNumber,
|
|
76
|
+
startTime: performance.now(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
64
79
|
}
|
|
65
80
|
|
|
66
|
-
public batchEnd() {
|
|
67
|
-
if (this.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (this.schedulingCount % 2000 === 0) {
|
|
73
|
-
this.schedulingLog = {
|
|
74
|
-
numberOfOps: this.deltaManager.inbound.length,
|
|
75
|
-
numberOfTurns: 1,
|
|
76
|
-
totalProcessingTime: 0,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
81
|
+
public batchEnd(message: ISequencedDocumentMessage) {
|
|
82
|
+
if (this.schedulingLog) {
|
|
83
|
+
this.schedulingLog.numberOfBatchesProcessed++;
|
|
84
|
+
this.schedulingLog.lastSequenceNumber = message.sequenceNumber;
|
|
85
|
+
this.schedulingLog.opsRemainingToProcess = this.deltaManager.inbound.length;
|
|
86
|
+
}
|
|
80
87
|
|
|
88
|
+
if (this.shouldRunScheduler()) {
|
|
89
|
+
const currentTime = performance.now();
|
|
81
90
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
82
|
-
const elapsedTime =
|
|
83
|
-
if (elapsedTime > this.
|
|
91
|
+
const elapsedTime = currentTime - this.processingStartTime!;
|
|
92
|
+
if (elapsedTime > this.currentAllowedProcessingTimeForTurn) {
|
|
84
93
|
// We have processed ops for more than the total processing time. So, pause the
|
|
85
94
|
// queue, yield the thread and schedule a resume.
|
|
86
95
|
|
|
87
96
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
88
97
|
this.deltaManager.inbound.pause();
|
|
89
|
-
setTimeout(() => {
|
|
90
|
-
this.deltaManager.inbound.resume();
|
|
91
|
-
});
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
// Increase the total processing time. Keep doing this after each turn until all the ops have
|
|
99
|
+
// Increase the total processing time. Keep doing this after each turn until all the ops have
|
|
95
100
|
// been processed. This way we keep the responsiveness at the beginning while also making sure
|
|
96
101
|
// that all the ops process fairly quickly.
|
|
97
|
-
this.
|
|
102
|
+
this.currentAllowedProcessingTimeForTurn += this.processingTimeIncrement;
|
|
98
103
|
|
|
99
104
|
// If we are logging the telemetry this time, update the telemetry log object.
|
|
100
105
|
if (this.schedulingLog) {
|
|
101
106
|
this.schedulingLog.numberOfTurns++;
|
|
102
107
|
this.schedulingLog.totalProcessingTime += elapsedTime;
|
|
103
108
|
}
|
|
109
|
+
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
if (this.schedulingLog) {
|
|
112
|
+
this.logger.sendTelemetryEvent({
|
|
113
|
+
eventName: "InboundOpsPartialProcessingTime",
|
|
114
|
+
duration: TelemetryLogger.formatTick(elapsedTime),
|
|
115
|
+
opsProcessed: this.schedulingLog.lastSequenceNumber -
|
|
116
|
+
this.schedulingLog.firstSequenceNumber + 1,
|
|
117
|
+
opsRemainingToProcess: this.deltaManager.inbound.length,
|
|
118
|
+
processingTime: TelemetryLogger.formatTick(this.schedulingLog.totalProcessingTime),
|
|
119
|
+
numberOfTurns: this.schedulingLog.numberOfTurns,
|
|
120
|
+
batchesProcessed: this.schedulingLog.numberOfBatchesProcessed,
|
|
121
|
+
timeToResume: TelemetryLogger.formatTick(performance.now() - currentTime),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
this.deltaManager.inbound.resume();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
this.processingStartTime = undefined;
|
|
104
128
|
}
|
|
105
129
|
}
|
|
106
130
|
}
|
|
@@ -109,14 +133,19 @@ export class DeltaScheduler {
|
|
|
109
133
|
if (this.schedulingLog) {
|
|
110
134
|
// Add the time taken for processing the final ops to the total processing time in the
|
|
111
135
|
// telemetry log object.
|
|
136
|
+
const currentTime = performance.now();
|
|
112
137
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
113
|
-
this.schedulingLog.totalProcessingTime +=
|
|
138
|
+
this.schedulingLog.totalProcessingTime += currentTime - this.processingStartTime!;
|
|
114
139
|
|
|
115
140
|
this.logger.sendTelemetryEvent({
|
|
116
141
|
eventName: "InboundOpsProcessingTime",
|
|
117
|
-
|
|
142
|
+
opsRemainingToProcess: this.schedulingLog.opsRemainingToProcess,
|
|
118
143
|
numberOfTurns: this.schedulingLog.numberOfTurns,
|
|
119
|
-
processingTime: this.schedulingLog.totalProcessingTime,
|
|
144
|
+
processingTime: TelemetryLogger.formatTick(this.schedulingLog.totalProcessingTime),
|
|
145
|
+
opsProcessed: this.schedulingLog.lastSequenceNumber - this.schedulingLog.firstSequenceNumber + 1,
|
|
146
|
+
batchesProcessed: this.schedulingLog.numberOfBatchesProcessed,
|
|
147
|
+
duration: TelemetryLogger.formatTick(currentTime - this.schedulingLog.startTime),
|
|
148
|
+
schedulingCount: this.schedulingCount,
|
|
120
149
|
});
|
|
121
150
|
|
|
122
151
|
this.schedulingLog = undefined;
|
|
@@ -124,14 +153,11 @@ export class DeltaScheduler {
|
|
|
124
153
|
|
|
125
154
|
// If we scheduled this batch of the inbound queue, increment the counter that tracks the
|
|
126
155
|
// number of times we have done this.
|
|
127
|
-
|
|
128
|
-
this.isScheduling = false;
|
|
129
|
-
this.schedulingCount++;
|
|
130
|
-
}
|
|
156
|
+
this.schedulingCount++;
|
|
131
157
|
|
|
132
158
|
// Reset the processing times.
|
|
133
159
|
this.processingStartTime = undefined;
|
|
134
|
-
this.
|
|
160
|
+
this.currentAllowedProcessingTimeForTurn = DeltaScheduler.processingTime;
|
|
135
161
|
}
|
|
136
162
|
|
|
137
163
|
/**
|