@fluidframework/container-runtime 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connectionTelemetry.d.ts +19 -0
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +21 -21
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerRuntime.d.ts +13 -1
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +99 -11
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +14 -3
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreRegistry.d.ts +0 -4
- package/dist/dataStoreRegistry.d.ts.map +1 -1
- package/dist/dataStoreRegistry.js +12 -1
- package/dist/dataStoreRegistry.js.map +1 -1
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +4 -4
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +20 -24
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +40 -117
- package/dist/garbageCollection.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/lib/connectionTelemetry.d.ts +19 -0
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +21 -21
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +13 -1
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +101 -13
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +15 -4
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreRegistry.d.ts +0 -4
- package/lib/dataStoreRegistry.d.ts.map +1 -1
- package/lib/dataStoreRegistry.js +12 -1
- package/lib/dataStoreRegistry.js.map +1 -1
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +5 -5
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +20 -24
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +39 -115
- package/lib/garbageCollection.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/package.json +20 -48
- package/src/connectionTelemetry.ts +58 -37
- package/src/containerRuntime.ts +119 -26
- package/src/dataStore.ts +21 -4
- package/src/dataStoreRegistry.ts +8 -1
- package/src/dataStores.ts +6 -5
- package/src/garbageCollection.ts +66 -158
- package/src/packageVersion.ts +1 -1
|
@@ -19,17 +19,17 @@ import { assert, performance } from "@fluidframework/common-utils";
|
|
|
19
19
|
export const latencyThreshold = 5000;
|
|
20
20
|
|
|
21
21
|
// Phases in OpPerfTelemetry:
|
|
22
|
-
// 1. Op
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// 3. Op
|
|
22
|
+
// 1. Op is added to DeltaManager (DM) buffer.
|
|
23
|
+
// 2. Op is sent to service (op leaves outbound queue).
|
|
24
|
+
// - Note: We do not know for sure when op is sent, we only track when it is added to outbound queue.
|
|
25
|
+
// If outbound queue is paused, time queue is paused is counted as network time.
|
|
26
|
+
// 3. Op received from service back (pushed to inbound queue).
|
|
27
27
|
// 4. Op is processed.
|
|
28
28
|
interface IOpPerfTelemetryProperties {
|
|
29
29
|
/** Measure time between (1) and (2) - Measure time outbound op is sitting in queue due to active batch */
|
|
30
|
-
|
|
31
|
-
/** Measure time between (2) and (3) - Track how long
|
|
32
|
-
|
|
30
|
+
durationOutboundBatching: number; // was durationOutboundQueue in previous versions
|
|
31
|
+
/** Measure time between (2) and (3) - Track how long it took for op to be acked by service */
|
|
32
|
+
durationNetwork: number; // was durationInboundQueue
|
|
33
33
|
/** Measure time between (3) and (4) - Time between DM's inbound "push" event until DM's "op" event */
|
|
34
34
|
durationInboundToProcessing: number;
|
|
35
35
|
/** Length of the DeltaManager's inbound queue at the time of the DM's inbound "push" event (3) */
|
|
@@ -41,11 +41,11 @@ interface IOpPerfTelemetryProperties {
|
|
|
41
41
|
*/
|
|
42
42
|
interface IOpPerfTimings {
|
|
43
43
|
/** Starting time for (1) */
|
|
44
|
-
|
|
44
|
+
submitOpEventTime: number;
|
|
45
45
|
/** Starting time for (2) */
|
|
46
|
-
|
|
46
|
+
outboundPushEventTime: number;
|
|
47
47
|
/** Starting time for (3) */
|
|
48
|
-
|
|
48
|
+
inboundPushEventTime: number;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
class OpPerfTelemetry {
|
|
@@ -102,26 +102,27 @@ class OpPerfTelemetry {
|
|
|
102
102
|
this.opPerfData = {};
|
|
103
103
|
this.connectionOpSeqNumber = undefined;
|
|
104
104
|
this.firstConnection = false;
|
|
105
|
+
this.pongCount = 0;
|
|
105
106
|
});
|
|
106
107
|
|
|
107
108
|
this.deltaManager.outbound.on("push", (messages) => {
|
|
108
109
|
for (const msg of messages) {
|
|
109
110
|
if (msg.type === MessageType.Operation &&
|
|
110
111
|
this.clientSequenceNumberForLatencyStatistics === msg.clientSequenceNumber) {
|
|
111
|
-
assert(this.opProcessingTimes.
|
|
112
|
-
0x2c8 /* "
|
|
113
|
-
assert(this.opPerfData.
|
|
114
|
-
0x2c9 /* "
|
|
115
|
-
this.opProcessingTimes.
|
|
112
|
+
assert(this.opProcessingTimes.outboundPushEventTime === undefined,
|
|
113
|
+
0x2c8 /* "outboundPushEventTime should be undefined" */);
|
|
114
|
+
assert(this.opPerfData.durationNetwork === undefined,
|
|
115
|
+
0x2c9 /* "durationNetwork should be undefined" */);
|
|
116
|
+
this.opProcessingTimes.outboundPushEventTime = Date.now();
|
|
116
117
|
|
|
117
|
-
assert(this.opPerfData.
|
|
118
|
-
0x2ca /* "
|
|
118
|
+
assert(this.opPerfData.durationOutboundBatching === undefined,
|
|
119
|
+
0x2ca /* "durationOutboundBatching should be undefined" */);
|
|
119
120
|
|
|
120
|
-
assert(this.opProcessingTimes.
|
|
121
|
-
0x2cb /* "
|
|
121
|
+
assert(this.opProcessingTimes.submitOpEventTime !== undefined,
|
|
122
|
+
0x2cb /* "submitOpEventTime should be undefined" */);
|
|
122
123
|
|
|
123
|
-
this.opPerfData.
|
|
124
|
-
- this.opProcessingTimes.
|
|
124
|
+
this.opPerfData.durationOutboundBatching = this.opProcessingTimes.outboundPushEventTime
|
|
125
|
+
- this.opProcessingTimes.submitOpEventTime;
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
});
|
|
@@ -130,11 +131,11 @@ class OpPerfTelemetry {
|
|
|
130
131
|
if (this.clientId === message.clientId &&
|
|
131
132
|
message.type === MessageType.Operation &&
|
|
132
133
|
this.clientSequenceNumberForLatencyStatistics === message.clientSequenceNumber &&
|
|
133
|
-
this.opProcessingTimes.
|
|
134
|
-
this.opProcessingTimes.
|
|
135
|
-
this.opPerfData.
|
|
136
|
-
- this.opProcessingTimes.
|
|
137
|
-
this.opProcessingTimes.
|
|
134
|
+
this.opProcessingTimes.outboundPushEventTime !== undefined) {
|
|
135
|
+
this.opProcessingTimes.inboundPushEventTime = Date.now();
|
|
136
|
+
this.opPerfData.durationNetwork = this.opProcessingTimes.inboundPushEventTime
|
|
137
|
+
- this.opProcessingTimes.outboundPushEventTime;
|
|
138
|
+
this.opProcessingTimes.outboundPushEventTime = undefined;
|
|
138
139
|
this.opPerfData.lengthInboundQueue = this.deltaManager.inbound.length;
|
|
139
140
|
}
|
|
140
141
|
});
|
|
@@ -173,12 +174,11 @@ class OpPerfTelemetry {
|
|
|
173
174
|
private recordPingTime(latency: number) {
|
|
174
175
|
this.pingLatency = latency;
|
|
175
176
|
// logging one in every 1000 pongs, including the first time, if it is a "write" client.
|
|
176
|
-
if (this.pongCount %
|
|
177
|
+
if (this.pongCount % 100 === 0 && this.deltaManager.active) {
|
|
177
178
|
this.logger.sendPerformanceEvent({
|
|
178
179
|
eventName: "DeltaLatency",
|
|
179
180
|
duration: latency,
|
|
180
181
|
});
|
|
181
|
-
this.pongCount = 0;
|
|
182
182
|
}
|
|
183
183
|
this.pongCount++;
|
|
184
184
|
}
|
|
@@ -187,11 +187,11 @@ class OpPerfTelemetry {
|
|
|
187
187
|
// start with first client op and measure latency every 500 client ops
|
|
188
188
|
if (this.clientSequenceNumberForLatencyStatistics === undefined &&
|
|
189
189
|
message.clientSequenceNumber % 500 === 1) {
|
|
190
|
-
assert(this.opProcessingTimes.
|
|
190
|
+
assert(this.opProcessingTimes.outboundPushEventTime === undefined,
|
|
191
191
|
0x2cc /* "OpTimeSittingInboundQueue should be undefined" */);
|
|
192
|
-
assert(this.opPerfData.
|
|
193
|
-
0x2cd /* "
|
|
194
|
-
this.opProcessingTimes.
|
|
192
|
+
assert(this.opPerfData.durationNetwork === undefined,
|
|
193
|
+
0x2cd /* "durationNetwork should be undefined" */);
|
|
194
|
+
this.opProcessingTimes.submitOpEventTime = Date.now();
|
|
195
195
|
this.clientSequenceNumberForLatencyStatistics = message.clientSequenceNumber;
|
|
196
196
|
}
|
|
197
197
|
}
|
|
@@ -223,16 +223,16 @@ class OpPerfTelemetry {
|
|
|
223
223
|
|
|
224
224
|
if (this.clientId === message.clientId &&
|
|
225
225
|
this.clientSequenceNumberForLatencyStatistics === message.clientSequenceNumber) {
|
|
226
|
-
assert(this.opProcessingTimes.
|
|
226
|
+
assert(this.opProcessingTimes.submitOpEventTime !== undefined,
|
|
227
227
|
0x120 /* "Undefined latency statistics (op send time)" */);
|
|
228
228
|
const currentTime = Date.now();
|
|
229
229
|
|
|
230
|
-
if (this.opProcessingTimes.
|
|
230
|
+
if (this.opProcessingTimes.inboundPushEventTime !== undefined) {
|
|
231
231
|
this.opPerfData.durationInboundToProcessing = currentTime
|
|
232
|
-
- this.opProcessingTimes.
|
|
232
|
+
- this.opProcessingTimes.inboundPushEventTime;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
const duration = currentTime - this.opProcessingTimes.
|
|
235
|
+
const duration = currentTime - this.opProcessingTimes.submitOpEventTime;
|
|
236
236
|
|
|
237
237
|
// One of the core expectations for Fluid service is to be fast.
|
|
238
238
|
// When it's not the case, we want to learn about it and be able to investigate, so
|
|
@@ -258,6 +258,27 @@ class OpPerfTelemetry {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
|
+
export interface IPerfSignalReport {
|
|
262
|
+
/**
|
|
263
|
+
* Identifier for the signal being submitted in order to
|
|
264
|
+
* allow collection of data around the roundtrip of signal messages.
|
|
265
|
+
*/
|
|
266
|
+
signalSequenceNumber: number;
|
|
267
|
+
/**
|
|
268
|
+
* Number of signals that were expected but not received.
|
|
269
|
+
*/
|
|
270
|
+
signalsLost: number;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Timestamp before submitting the signal we will trace.
|
|
274
|
+
*/
|
|
275
|
+
signalTimestamp: number;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Expected Signal Sequence to be received.
|
|
279
|
+
*/
|
|
280
|
+
trackingSignalSequenceNumber: number | undefined;
|
|
281
|
+
}
|
|
261
282
|
|
|
262
283
|
export function ReportOpPerfTelemetry(
|
|
263
284
|
clientId: string | undefined,
|
package/src/containerRuntime.ts
CHANGED
|
@@ -49,6 +49,7 @@ import { DriverHeader, IDocumentStorageService, ISummaryContext } from "@fluidfr
|
|
|
49
49
|
import { readAndParse } from "@fluidframework/driver-utils";
|
|
50
50
|
import {
|
|
51
51
|
DataCorruptionError,
|
|
52
|
+
DataProcessingError,
|
|
52
53
|
GenericError,
|
|
53
54
|
UsageError,
|
|
54
55
|
extractSafePropertiesFromMessage,
|
|
@@ -108,7 +109,11 @@ import { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
|
108
109
|
import { Summarizer } from "./summarizer";
|
|
109
110
|
import { SummaryManager } from "./summaryManager";
|
|
110
111
|
import { DeltaScheduler } from "./deltaScheduler";
|
|
111
|
-
import {
|
|
112
|
+
import {
|
|
113
|
+
ReportOpPerfTelemetry,
|
|
114
|
+
latencyThreshold,
|
|
115
|
+
IPerfSignalReport,
|
|
116
|
+
} from "./connectionTelemetry";
|
|
112
117
|
import { IPendingLocalState, PendingStateManager } from "./pendingStateManager";
|
|
113
118
|
import { pkgVersion } from "./packageVersion";
|
|
114
119
|
import { BlobManager, IBlobManagerLoadInfo } from "./blobManager";
|
|
@@ -221,7 +226,7 @@ export interface ISummaryConfigurationHeuristics extends ISummaryBaseConfigurati
|
|
|
221
226
|
/**
|
|
222
227
|
* Defines the maximum allowed time in between summarizations.
|
|
223
228
|
*/
|
|
224
|
-
|
|
229
|
+
idleTime: number;
|
|
225
230
|
/**
|
|
226
231
|
* Defines the maximum allowed time, since the last received Ack, before running the summary
|
|
227
232
|
* with reason maxTime.
|
|
@@ -231,9 +236,9 @@ export interface ISummaryConfigurationHeuristics extends ISummaryBaseConfigurati
|
|
|
231
236
|
* Defines the maximum number of Ops, since the last received Ack, that can be allowed
|
|
232
237
|
* before running the summary with reason maxOps.
|
|
233
238
|
*/
|
|
234
|
-
|
|
239
|
+
maxOps: number;
|
|
235
240
|
/**
|
|
236
|
-
*
|
|
241
|
+
* Defines the minimum number of Ops, since the last received Ack, that can be allowed
|
|
237
242
|
* before running the last summary.
|
|
238
243
|
*/
|
|
239
244
|
minOpsForLastSummaryAttempt: number;
|
|
@@ -334,7 +339,7 @@ export interface ISummaryRuntimeOptions {
|
|
|
334
339
|
* @deprecated - use `summaryConfigOverrides.maxOpsSinceLastSummary` instead.
|
|
335
340
|
* Defaults to 7000 ops
|
|
336
341
|
*/
|
|
337
|
-
|
|
342
|
+
maxOpsSinceLastSummary?: number;
|
|
338
343
|
|
|
339
344
|
/**
|
|
340
345
|
* @deprecated - use `summaryConfigOverrides.summarizerClientElection` instead.
|
|
@@ -606,7 +611,7 @@ class ScheduleManagerCore {
|
|
|
606
611
|
|
|
607
612
|
private resumeQueue(startBatch: number, messageEndBatch: ISequencedDocumentMessage) {
|
|
608
613
|
const endBatch = messageEndBatch.sequenceNumber;
|
|
609
|
-
const duration = performance.now() - this.timePaused;
|
|
614
|
+
const duration = this.localPaused ? (performance.now() - this.timePaused) : undefined;
|
|
610
615
|
|
|
611
616
|
this.batchCount++;
|
|
612
617
|
if (this.batchCount % 1000 === 1) {
|
|
@@ -629,7 +634,7 @@ class ScheduleManagerCore {
|
|
|
629
634
|
this.localPaused = false;
|
|
630
635
|
|
|
631
636
|
// Random round number - we want to know when batch waiting paused op processing.
|
|
632
|
-
if (duration > latencyThreshold) {
|
|
637
|
+
if (duration !== undefined && duration > latencyThreshold) {
|
|
633
638
|
this.logger.sendErrorEvent({
|
|
634
639
|
eventName: "MaxBatchWaitTimeExceeded",
|
|
635
640
|
duration,
|
|
@@ -891,7 +896,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
891
896
|
|
|
892
897
|
// Verify summary runtime sequence number matches protocol sequence number.
|
|
893
898
|
const runtimeSequenceNumber = metadata?.message?.sequenceNumber;
|
|
894
|
-
|
|
899
|
+
// When we load with pending state, we reuse an old snapshot so we don't expect these numbers to match
|
|
900
|
+
if (!pendingRuntimeState && runtimeSequenceNumber !== undefined) {
|
|
895
901
|
const protocolSequenceNumber = context.deltaManager.initialSequenceNumber;
|
|
896
902
|
// Unless bypass is explicitly set, then take action when sequence numbers mismatch.
|
|
897
903
|
if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
|
|
@@ -1044,6 +1050,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1044
1050
|
private dirtyContainer: boolean;
|
|
1045
1051
|
private emitDirtyDocumentEvent = true;
|
|
1046
1052
|
|
|
1053
|
+
private readonly defaultTelemetrySignalSampleCount = 100;
|
|
1054
|
+
private _perfSignalData: IPerfSignalReport = {
|
|
1055
|
+
signalsLost: 0,
|
|
1056
|
+
signalSequenceNumber: 0,
|
|
1057
|
+
signalTimestamp: 0,
|
|
1058
|
+
trackingSignalSequenceNumber: undefined,
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1047
1061
|
/**
|
|
1048
1062
|
* Summarizer is responsible for coordinating when to send generate and send summaries.
|
|
1049
1063
|
* It is the main entry point for summary work.
|
|
@@ -1201,18 +1215,18 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1201
1215
|
const pendingRuntimeState = context.pendingLocalState as IPendingRuntimeState | undefined;
|
|
1202
1216
|
const baseSnapshot: ISnapshotTree | undefined = pendingRuntimeState?.baseSnapshot ?? context.baseSnapshot;
|
|
1203
1217
|
|
|
1204
|
-
this.garbageCollector = GarbageCollector.create(
|
|
1205
|
-
this,
|
|
1206
|
-
this.runtimeOptions.gcOptions,
|
|
1207
|
-
(nodePath: string) => this.getGCNodePackagePath(nodePath),
|
|
1208
|
-
() => this.messageAtLastSummary?.timestamp,
|
|
1218
|
+
this.garbageCollector = GarbageCollector.create({
|
|
1219
|
+
runtime: this,
|
|
1220
|
+
gcOptions: this.runtimeOptions.gcOptions,
|
|
1209
1221
|
baseSnapshot,
|
|
1210
|
-
|
|
1211
|
-
this.mc.logger,
|
|
1222
|
+
baseLogger: this.mc.logger,
|
|
1212
1223
|
existing,
|
|
1213
1224
|
metadata,
|
|
1214
|
-
this.context.clientDetails.type === summarizerClientType,
|
|
1215
|
-
|
|
1225
|
+
isSummarizerClient: this.context.clientDetails.type === summarizerClientType,
|
|
1226
|
+
getNodePackagePath: (nodePath: string) => this.getGCNodePackagePath(nodePath),
|
|
1227
|
+
getLastSummaryTimestampMs: () => this.messageAtLastSummary?.timestamp,
|
|
1228
|
+
readAndParseBlob: async <T>(id: string) => readAndParse<T>(this.storage, id),
|
|
1229
|
+
});
|
|
1216
1230
|
|
|
1217
1231
|
const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
|
|
1218
1232
|
this.summarizerNode = createRootSummarizerNodeWithGC(
|
|
@@ -1557,11 +1571,17 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1557
1571
|
}
|
|
1558
1572
|
}
|
|
1559
1573
|
|
|
1574
|
+
private internalId(maybeAlias: string): string {
|
|
1575
|
+
return this.dataStores.aliases().get(maybeAlias) ?? maybeAlias;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1560
1578
|
private async getDataStoreFromRequest(id: string, request: IRequest): Promise<IFluidRouter> {
|
|
1561
1579
|
const wait = typeof request.headers?.[RuntimeHeaders.wait] === "boolean"
|
|
1562
1580
|
? request.headers?.[RuntimeHeaders.wait]
|
|
1563
1581
|
: true;
|
|
1564
|
-
|
|
1582
|
+
|
|
1583
|
+
const internalId = this.internalId(id);
|
|
1584
|
+
const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
|
|
1565
1585
|
|
|
1566
1586
|
/**
|
|
1567
1587
|
* If GC should run and this an external app request with "externalRequest" header, we need to return
|
|
@@ -1743,15 +1763,24 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1743
1763
|
const reconnection = changeOfState && connected;
|
|
1744
1764
|
this._connected = connected;
|
|
1745
1765
|
|
|
1766
|
+
if (!connected) {
|
|
1767
|
+
this._perfSignalData.signalsLost = 0;
|
|
1768
|
+
this._perfSignalData.signalTimestamp = 0;
|
|
1769
|
+
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1746
1772
|
if (reconnection) {
|
|
1747
1773
|
this.consecutiveReconnects++;
|
|
1748
1774
|
|
|
1749
1775
|
if (!this.shouldContinueReconnecting()) {
|
|
1750
|
-
this.closeFn(
|
|
1776
|
+
this.closeFn(
|
|
1751
1777
|
// pre-0.58 error message: MaxReconnectsWithNoProgress
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1778
|
+
DataProcessingError.create(
|
|
1779
|
+
"Runtime detected too many reconnects with no progress syncing local ops",
|
|
1780
|
+
"setConnectionState",
|
|
1781
|
+
undefined,
|
|
1782
|
+
{
|
|
1783
|
+
dataLoss: 1,
|
|
1755
1784
|
attempts: this.consecutiveReconnects,
|
|
1756
1785
|
pendingMessages: this.pendingStateManager.pendingMessagesCount,
|
|
1757
1786
|
}));
|
|
@@ -1853,6 +1882,22 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1853
1882
|
this.dataStores.processAliasMessage(message, localOpMetadata, local);
|
|
1854
1883
|
}
|
|
1855
1884
|
|
|
1885
|
+
/**
|
|
1886
|
+
* Emits the Signal event and update the perf signal data.
|
|
1887
|
+
* @param clientSignalSequenceNumber - is the client signal sequence number to be uploaded.
|
|
1888
|
+
*/
|
|
1889
|
+
private sendSignalTelemetryEvent(clientSignalSequenceNumber: number) {
|
|
1890
|
+
const duration = Date.now() - this._perfSignalData.signalTimestamp;
|
|
1891
|
+
this.logger.sendPerformanceEvent({
|
|
1892
|
+
eventName: "SignalLatency",
|
|
1893
|
+
duration,
|
|
1894
|
+
signalsLost: this._perfSignalData.signalsLost,
|
|
1895
|
+
});
|
|
1896
|
+
|
|
1897
|
+
this._perfSignalData.signalsLost = 0;
|
|
1898
|
+
this._perfSignalData.signalTimestamp = 0;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1856
1901
|
public processSignal(message: ISignalMessage, local: boolean) {
|
|
1857
1902
|
const envelope = message.content as ISignalEnvelope;
|
|
1858
1903
|
const transformed: IInboundSignalMessage = {
|
|
@@ -1861,6 +1906,26 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1861
1906
|
type: envelope.contents.type,
|
|
1862
1907
|
};
|
|
1863
1908
|
|
|
1909
|
+
// Only collect signal telemetry for messages sent by the current client.
|
|
1910
|
+
if (message.clientId === this.clientId && this.connected) {
|
|
1911
|
+
// Check to see if the signal was lost.
|
|
1912
|
+
if (this._perfSignalData.trackingSignalSequenceNumber !== undefined &&
|
|
1913
|
+
envelope.clientSignalSequenceNumber > this._perfSignalData.trackingSignalSequenceNumber) {
|
|
1914
|
+
this._perfSignalData.signalsLost++;
|
|
1915
|
+
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
1916
|
+
this.logger.sendErrorEvent({
|
|
1917
|
+
eventName: "SignalLost",
|
|
1918
|
+
type: envelope.contents.type,
|
|
1919
|
+
signalsLost: this._perfSignalData.signalsLost,
|
|
1920
|
+
trackingSequenceNumber: this._perfSignalData.trackingSignalSequenceNumber,
|
|
1921
|
+
clientSignalSequenceNumber: envelope.clientSignalSequenceNumber,
|
|
1922
|
+
});
|
|
1923
|
+
} else if (envelope.clientSignalSequenceNumber === this._perfSignalData.trackingSignalSequenceNumber) {
|
|
1924
|
+
this.sendSignalTelemetryEvent(envelope.clientSignalSequenceNumber);
|
|
1925
|
+
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1864
1929
|
if (envelope.address === undefined) {
|
|
1865
1930
|
// No address indicates a container signal message.
|
|
1866
1931
|
this.emit("signal", transformed, local);
|
|
@@ -1871,7 +1936,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1871
1936
|
}
|
|
1872
1937
|
|
|
1873
1938
|
public async getRootDataStore(id: string, wait = true): Promise<IFluidRouter> {
|
|
1874
|
-
const
|
|
1939
|
+
const internalId = this.internalId(id);
|
|
1940
|
+
const context = await this.dataStores.getDataStore(internalId, wait);
|
|
1875
1941
|
assert(await context.isRoot(), 0x12b /* "did not get root data store" */);
|
|
1876
1942
|
return context.realize();
|
|
1877
1943
|
}
|
|
@@ -2001,7 +2067,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2001
2067
|
return fluidDataStore;
|
|
2002
2068
|
}
|
|
2003
2069
|
|
|
2070
|
+
/**
|
|
2071
|
+
* @deprecated - will be removed in an upcoming release. See #9660.
|
|
2072
|
+
*/
|
|
2004
2073
|
public async createRootDataStore(pkg: string | string[], rootDataStoreId: string): Promise<IFluidRouter> {
|
|
2074
|
+
if (rootDataStoreId.includes("/")) {
|
|
2075
|
+
throw new UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
|
|
2076
|
+
}
|
|
2005
2077
|
return this._aliasingEnabled === true ?
|
|
2006
2078
|
this.createAndAliasDataStore(pkg, rootDataStoreId) :
|
|
2007
2079
|
this.createRootDataStoreLegacy(pkg, rootDataStoreId);
|
|
@@ -2046,6 +2118,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2046
2118
|
public createDetachedRootDataStore(
|
|
2047
2119
|
pkg: Readonly<string[]>,
|
|
2048
2120
|
rootDataStoreId: string): IFluidDataStoreContextDetached {
|
|
2121
|
+
if (rootDataStoreId.includes("/")) {
|
|
2122
|
+
throw new UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
|
|
2123
|
+
}
|
|
2049
2124
|
return this.dataStores.createDetachedDataStoreCore(pkg, true, rootDataStoreId);
|
|
2050
2125
|
}
|
|
2051
2126
|
|
|
@@ -2143,6 +2218,24 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2143
2218
|
return true;
|
|
2144
2219
|
}
|
|
2145
2220
|
|
|
2221
|
+
private createNewSignalEnvelope(address: string | undefined, type: string, content: any): ISignalEnvelope {
|
|
2222
|
+
const newSequenceNumber = ++this._perfSignalData.signalSequenceNumber;
|
|
2223
|
+
const newEnvelope: ISignalEnvelope = {
|
|
2224
|
+
address,
|
|
2225
|
+
clientSignalSequenceNumber: newSequenceNumber,
|
|
2226
|
+
contents: { type, content },
|
|
2227
|
+
};
|
|
2228
|
+
|
|
2229
|
+
// We should not track any signals in case we already have a tracking number.
|
|
2230
|
+
if (newSequenceNumber % this.defaultTelemetrySignalSampleCount === 1 &&
|
|
2231
|
+
this._perfSignalData.trackingSignalSequenceNumber === undefined) {
|
|
2232
|
+
this._perfSignalData.signalTimestamp = Date.now();
|
|
2233
|
+
this._perfSignalData.trackingSignalSequenceNumber = newSequenceNumber;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
return newEnvelope;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2146
2239
|
/**
|
|
2147
2240
|
* Submits the signal to be sent to other clients.
|
|
2148
2241
|
* @param type - Type of the signal.
|
|
@@ -2150,13 +2243,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2150
2243
|
*/
|
|
2151
2244
|
public submitSignal(type: string, content: any) {
|
|
2152
2245
|
this.verifyNotClosed();
|
|
2153
|
-
const envelope
|
|
2246
|
+
const envelope = this.createNewSignalEnvelope(undefined /* address */, type, content);
|
|
2154
2247
|
return this.context.submitSignalFn(envelope);
|
|
2155
2248
|
}
|
|
2156
2249
|
|
|
2157
2250
|
public submitDataStoreSignal(address: string, type: string, content: any) {
|
|
2158
|
-
const envelope
|
|
2159
|
-
|
|
2251
|
+
const envelope = this.createNewSignalEnvelope(address, type, content);
|
|
2252
|
+
return this.context.submitSignalFn(envelope);
|
|
2160
2253
|
}
|
|
2161
2254
|
|
|
2162
2255
|
public setAttachState(attachState: AttachState.Attaching | AttachState.Attached): void {
|
package/src/dataStore.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
|
-
import { unreachableCase } from "@fluidframework/common-utils";
|
|
7
|
+
import { assert, unreachableCase } from "@fluidframework/common-utils";
|
|
8
8
|
import { AttachState } from "@fluidframework/container-definitions";
|
|
9
|
+
import { UsageError } from "@fluidframework/container-utils";
|
|
9
10
|
import { IRequest, IResponse } from "@fluidframework/core-interfaces";
|
|
10
11
|
import { AliasResult, IDataStore, IFluidDataStoreChannel } from "@fluidframework/runtime-definitions";
|
|
11
12
|
import { TelemetryDataTag } from "@fluidframework/telemetry-utils";
|
|
@@ -53,16 +54,27 @@ enum AliasState {
|
|
|
53
54
|
class DataStore implements IDataStore {
|
|
54
55
|
private aliasState: AliasState = AliasState.None;
|
|
55
56
|
private alias: string | undefined;
|
|
57
|
+
private aliasResult: Promise<AliasResult> | undefined;
|
|
56
58
|
|
|
57
59
|
async trySetAlias(alias: string): Promise<AliasResult> {
|
|
60
|
+
if (alias.includes("/")) {
|
|
61
|
+
throw new UsageError(`The alias cannot contain slashes: '${alias}'`);
|
|
62
|
+
}
|
|
63
|
+
|
|
58
64
|
switch (this.aliasState) {
|
|
59
|
-
// If we're already aliasing,
|
|
65
|
+
// If we're already aliasing, check if it's for the same value and return
|
|
66
|
+
// the stored promise, otherwise return 'AlreadyAliased'
|
|
60
67
|
case AliasState.Aliasing:
|
|
61
|
-
|
|
68
|
+
assert(this.aliasResult !== undefined,
|
|
69
|
+
0x316 /* There should be a cached promise of in-progress aliasing */);
|
|
70
|
+
await this.aliasResult;
|
|
71
|
+
return this.alias === alias ? "Success" : "AlreadyAliased";
|
|
72
|
+
|
|
62
73
|
// If this datastore is already aliased, return true only if this
|
|
63
74
|
// is a repeated call for the same alias
|
|
64
75
|
case AliasState.Aliased:
|
|
65
76
|
return this.alias === alias ? "Success" : "AlreadyAliased";
|
|
77
|
+
|
|
66
78
|
// There is no current or past alias operation for this datastore,
|
|
67
79
|
// it is safe to continue execution
|
|
68
80
|
case AliasState.None: break;
|
|
@@ -70,6 +82,11 @@ class DataStore implements IDataStore {
|
|
|
70
82
|
}
|
|
71
83
|
|
|
72
84
|
this.aliasState = AliasState.Aliasing;
|
|
85
|
+
this.aliasResult = this.trySetAliasInternal(alias);
|
|
86
|
+
return this.aliasResult;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async trySetAliasInternal(alias: string): Promise<AliasResult> {
|
|
73
90
|
const message: IDataStoreAliasMessage = {
|
|
74
91
|
internalId: this.internalId,
|
|
75
92
|
alias,
|
|
@@ -85,7 +102,7 @@ class DataStore implements IDataStore {
|
|
|
85
102
|
|
|
86
103
|
if (this.runtime.attachState === AttachState.Detached) {
|
|
87
104
|
const localResult = this.datastores.processAliasMessageCore(message);
|
|
88
|
-
// Explicitly
|
|
105
|
+
// Explicitly lock-out future attempts of aliasing,
|
|
89
106
|
// regardless of result
|
|
90
107
|
this.aliasState = AliasState.Aliased;
|
|
91
108
|
return localResult ? "Success" : "Conflict";
|
package/src/dataStoreRegistry.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
|
+
import { UsageError } from "@fluidframework/container-utils";
|
|
5
6
|
import {
|
|
6
7
|
FluidDataStoreRegistryEntry,
|
|
7
8
|
IFluidDataStoreRegistry,
|
|
@@ -14,7 +15,13 @@ export class FluidDataStoreRegistry implements IFluidDataStoreRegistry {
|
|
|
14
15
|
public get IFluidDataStoreRegistry() { return this; }
|
|
15
16
|
|
|
16
17
|
constructor(namedEntries: NamedFluidDataStoreRegistryEntries) {
|
|
17
|
-
this.map = new Map(
|
|
18
|
+
this.map = new Map();
|
|
19
|
+
for (const entry of namedEntries) {
|
|
20
|
+
if (this.map.has(entry[0])) {
|
|
21
|
+
throw new UsageError("Duplicate entry names exist");
|
|
22
|
+
}
|
|
23
|
+
this.map.set(entry[0], entry[1]);
|
|
24
|
+
}
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
public async get(name: string): Promise<FluidDataStoreRegistryEntry | undefined> {
|
package/src/dataStores.ts
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
responseToException,
|
|
35
35
|
SummaryTreeBuilder,
|
|
36
36
|
} from "@fluidframework/runtime-utils";
|
|
37
|
-
import { ChildLogger, TelemetryDataTag } from "@fluidframework/telemetry-utils";
|
|
37
|
+
import { ChildLogger, LoggingError, TelemetryDataTag } from "@fluidframework/telemetry-utils";
|
|
38
38
|
import { AttachState } from "@fluidframework/container-definitions";
|
|
39
39
|
import { BlobCacheStorageService, buildSnapshotTree } from "@fluidframework/driver-utils";
|
|
40
40
|
import { assert, Lazy, LazyPromise } from "@fluidframework/common-utils";
|
|
@@ -145,7 +145,7 @@ export class DataStores implements IDisposable {
|
|
|
145
145
|
});
|
|
146
146
|
} else {
|
|
147
147
|
if (typeof value !== "object") {
|
|
148
|
-
throw new
|
|
148
|
+
throw new LoggingError("Snapshot should be there to load from!!");
|
|
149
149
|
}
|
|
150
150
|
const snapshotTree = value;
|
|
151
151
|
dataStoreContext = new LocalFluidDataStoreContext({
|
|
@@ -329,6 +329,8 @@ export class DataStores implements IDisposable {
|
|
|
329
329
|
pkg: Readonly<string[]>,
|
|
330
330
|
isRoot: boolean,
|
|
331
331
|
id = uuid()): IFluidDataStoreContextDetached {
|
|
332
|
+
assert(!id.includes("/"), 0x30c /* Id cannot contain slashes */);
|
|
333
|
+
|
|
332
334
|
const context = new LocalDetachedFluidDataStoreContext({
|
|
333
335
|
id,
|
|
334
336
|
pkg,
|
|
@@ -350,6 +352,7 @@ export class DataStores implements IDisposable {
|
|
|
350
352
|
}
|
|
351
353
|
|
|
352
354
|
public _createFluidDataStoreContext(pkg: string[], id: string, isRoot: boolean, props?: any) {
|
|
355
|
+
assert(!id.includes("/"), 0x30d /* Id cannot contain slashes */);
|
|
353
356
|
const context = new LocalFluidDataStoreContext({
|
|
354
357
|
id,
|
|
355
358
|
pkg,
|
|
@@ -418,9 +421,7 @@ export class DataStores implements IDisposable {
|
|
|
418
421
|
}
|
|
419
422
|
|
|
420
423
|
public async getDataStore(id: string, wait: boolean): Promise<FluidDataStoreContext> {
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
const context = await this.contexts.getBoundOrRemoted(internalId, wait);
|
|
424
|
+
const context = await this.contexts.getBoundOrRemoted(id, wait);
|
|
424
425
|
if (context === undefined) {
|
|
425
426
|
// The requested data store does not exits. Throw a 404 response exception.
|
|
426
427
|
const request = { url: id };
|