@fluidframework/container-runtime 0.51.3 → 0.53.0-46105
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 +4 -0
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +6 -2
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerHandleContext.d.ts +0 -1
- package/dist/containerHandleContext.d.ts.map +1 -1
- package/dist/containerHandleContext.js +0 -1
- package/dist/containerHandleContext.js.map +1 -1
- package/dist/containerRuntime.d.ts +40 -13
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +255 -133
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +10 -7
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +16 -13
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +8 -8
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +20 -36
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +61 -14
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +275 -20
- package/dist/garbageCollection.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.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/summarizer.d.ts +1 -3
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +0 -12
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerTypes.d.ts +14 -3
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js +3 -0
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryFormat.d.ts +9 -1
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js +2 -1
- package/dist/summaryFormat.js.map +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +1 -3
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts +4 -0
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +5 -1
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerHandleContext.d.ts +0 -1
- package/lib/containerHandleContext.d.ts.map +1 -1
- package/lib/containerHandleContext.js +0 -1
- package/lib/containerHandleContext.js.map +1 -1
- package/lib/containerRuntime.d.ts +40 -13
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +259 -138
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +10 -7
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +16 -13
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +8 -8
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +23 -39
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +61 -14
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +276 -21
- package/lib/garbageCollection.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.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/summarizer.d.ts +1 -3
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +0 -12
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerTypes.d.ts +14 -3
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js +3 -0
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryFormat.d.ts +9 -1
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js +2 -1
- package/lib/summaryFormat.js.map +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +1 -3
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js.map +1 -1
- package/package.json +16 -16
- package/src/connectionTelemetry.ts +6 -1
- package/src/containerHandleContext.ts +0 -1
- package/src/containerRuntime.ts +327 -160
- package/src/dataStoreContext.ts +21 -20
- package/src/dataStores.ts +32 -50
- package/src/garbageCollection.ts +390 -18
- package/src/index.ts +20 -2
- package/src/packageVersion.ts +1 -1
- package/src/summarizer.ts +0 -15
- package/src/summarizerTypes.ts +16 -4
- package/src/summaryFormat.ts +10 -1
- package/src/summaryGenerator.ts +2 -3
- package/src/summaryManager.ts +8 -3
package/src/containerRuntime.ts
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
7
|
import { ITelemetryGenericEvent, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
8
8
|
import {
|
|
9
|
+
FluidObject,
|
|
10
|
+
IFluidConfiguration,
|
|
11
|
+
IFluidHandle,
|
|
12
|
+
IFluidHandleContext,
|
|
9
13
|
IFluidObject,
|
|
10
14
|
IFluidRouter,
|
|
11
|
-
IFluidHandleContext,
|
|
12
|
-
IFluidSerializer,
|
|
13
15
|
IRequest,
|
|
14
16
|
IResponse,
|
|
15
|
-
IFluidHandle,
|
|
16
|
-
IFluidConfiguration,
|
|
17
17
|
} from "@fluidframework/core-interfaces";
|
|
18
18
|
import {
|
|
19
19
|
IAudience,
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
Trace,
|
|
37
37
|
TypedEventEmitter,
|
|
38
38
|
unreachableCase,
|
|
39
|
+
performance,
|
|
39
40
|
} from "@fluidframework/common-utils";
|
|
40
41
|
import {
|
|
41
42
|
ChildLogger,
|
|
@@ -46,11 +47,7 @@ import {
|
|
|
46
47
|
} from "@fluidframework/telemetry-utils";
|
|
47
48
|
import { IDocumentStorageService, ISummaryContext } from "@fluidframework/driver-definitions";
|
|
48
49
|
import { readAndParse, BlobAggregationStorage } from "@fluidframework/driver-utils";
|
|
49
|
-
import { DataCorruptionError, GenericError } from "@fluidframework/container-utils";
|
|
50
|
-
import {
|
|
51
|
-
BlobTreeEntry,
|
|
52
|
-
TreeTreeEntry,
|
|
53
|
-
} from "@fluidframework/protocol-base";
|
|
50
|
+
import { DataCorruptionError, GenericError, extractSafePropertiesFromMessage } from "@fluidframework/container-utils";
|
|
54
51
|
import {
|
|
55
52
|
IClientDetails,
|
|
56
53
|
IDocumentMessage,
|
|
@@ -88,13 +85,13 @@ import {
|
|
|
88
85
|
addTreeToSummary,
|
|
89
86
|
convertToSummaryTree,
|
|
90
87
|
createRootSummarizerNodeWithGC,
|
|
91
|
-
FluidSerializer,
|
|
92
88
|
IRootSummarizerNodeWithGC,
|
|
93
89
|
RequestParser,
|
|
94
90
|
create404Response,
|
|
95
91
|
exceptionToResponse,
|
|
96
92
|
responseToException,
|
|
97
93
|
seqFromTree,
|
|
94
|
+
convertSummaryTreeToITree,
|
|
98
95
|
} from "@fluidframework/runtime-utils";
|
|
99
96
|
import { v4 as uuid } from "uuid";
|
|
100
97
|
import { ContainerFluidHandleContext } from "./containerHandleContext";
|
|
@@ -102,7 +99,7 @@ import { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
|
102
99
|
import { Summarizer } from "./summarizer";
|
|
103
100
|
import { formRequestSummarizerFn, ISummarizerRequestOptions, SummaryManager } from "./summaryManager";
|
|
104
101
|
import { DeltaScheduler } from "./deltaScheduler";
|
|
105
|
-
import { ReportOpPerfTelemetry } from "./connectionTelemetry";
|
|
102
|
+
import { ReportOpPerfTelemetry, latencyThreshold } from "./connectionTelemetry";
|
|
106
103
|
import { IPendingLocalState, PendingStateManager } from "./pendingStateManager";
|
|
107
104
|
import { pkgVersion } from "./packageVersion";
|
|
108
105
|
import { BlobManager, IBlobManagerLoadInfo } from "./blobManager";
|
|
@@ -113,6 +110,7 @@ import {
|
|
|
113
110
|
electedSummarizerBlobName,
|
|
114
111
|
extractSummaryMetadataMessage,
|
|
115
112
|
IContainerRuntimeMetadata,
|
|
113
|
+
ICreateContainerMetadata,
|
|
116
114
|
ISummaryMetadataMessage,
|
|
117
115
|
metadataBlobName,
|
|
118
116
|
wrapSummaryInChannelsTree,
|
|
@@ -135,6 +133,7 @@ import { formExponentialFn, Throttler } from "./throttler";
|
|
|
135
133
|
import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
|
|
136
134
|
import {
|
|
137
135
|
GarbageCollector,
|
|
136
|
+
gcTreeKey,
|
|
138
137
|
IGarbageCollectionRuntime,
|
|
139
138
|
IGarbageCollector,
|
|
140
139
|
IGCStats,
|
|
@@ -223,6 +222,12 @@ export interface IGCRuntimeOptions {
|
|
|
223
222
|
|
|
224
223
|
export interface ISummaryRuntimeOptions {
|
|
225
224
|
/**
|
|
225
|
+
* Flag that disables summaries if it is set to true.
|
|
226
|
+
*/
|
|
227
|
+
disableSummaries?: boolean;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @deprecated - To disable summaries, please set disableSummaries===true.
|
|
226
231
|
* Flag that will generate summaries if connected to a service that supports them.
|
|
227
232
|
* This defaults to true and must be explicitly set to false to disable.
|
|
228
233
|
*/
|
|
@@ -269,9 +274,9 @@ export interface IContainerRuntimeOptions {
|
|
|
269
274
|
loadSequenceNumberVerification?: "close" | "log" | "bypass";
|
|
270
275
|
}
|
|
271
276
|
|
|
272
|
-
|
|
277
|
+
type IRuntimeMessageMetadata = undefined | {
|
|
273
278
|
batch?: boolean;
|
|
274
|
-
}
|
|
279
|
+
};
|
|
275
280
|
|
|
276
281
|
// Local storage key to set the default flush mode to TurnBased
|
|
277
282
|
const turnBasedFlushModeKey = "FluidFlushModeTurnBased";
|
|
@@ -311,23 +316,20 @@ export function unpackRuntimeMessage(message: ISequencedDocumentMessage) {
|
|
|
311
316
|
return message;
|
|
312
317
|
}
|
|
313
318
|
|
|
314
|
-
|
|
315
|
-
|
|
319
|
+
/**
|
|
320
|
+
* This class controls pausing and resuming of inbound queue to ensure that we never
|
|
321
|
+
* start processing ops in a batch IF we do not have all ops in the batch.
|
|
322
|
+
*/
|
|
323
|
+
class ScheduleManagerCore {
|
|
316
324
|
private pauseSequenceNumber: number | undefined;
|
|
317
|
-
private
|
|
325
|
+
private currentBatchClientId: string | undefined;
|
|
318
326
|
private localPaused = false;
|
|
319
|
-
private
|
|
327
|
+
private timePaused = 0;
|
|
320
328
|
|
|
321
329
|
constructor(
|
|
322
330
|
private readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
|
|
323
|
-
private readonly emitter: EventEmitter,
|
|
324
331
|
private readonly logger: ITelemetryLogger,
|
|
325
332
|
) {
|
|
326
|
-
this.deltaScheduler = new DeltaScheduler(
|
|
327
|
-
this.deltaManager,
|
|
328
|
-
ChildLogger.create(this.logger, "DeltaScheduler"),
|
|
329
|
-
);
|
|
330
|
-
|
|
331
333
|
// Listen for delta manager sends and add batch metadata to messages
|
|
332
334
|
this.deltaManager.on("prepareSend", (messages: IDocumentMessage[]) => {
|
|
333
335
|
if (messages.length === 0) {
|
|
@@ -336,7 +338,7 @@ export class ScheduleManager {
|
|
|
336
338
|
|
|
337
339
|
// First message will have the batch flag set to true if doing a batched send
|
|
338
340
|
const firstMessageMetadata = messages[0].metadata as IRuntimeMessageMetadata;
|
|
339
|
-
if (!firstMessageMetadata
|
|
341
|
+
if (!firstMessageMetadata?.batch) {
|
|
340
342
|
return;
|
|
341
343
|
}
|
|
342
344
|
|
|
@@ -356,32 +358,178 @@ export class ScheduleManager {
|
|
|
356
358
|
"push",
|
|
357
359
|
(message: ISequencedDocumentMessage) => {
|
|
358
360
|
this.trackPending(message);
|
|
359
|
-
this.updatePauseState(message.sequenceNumber);
|
|
360
361
|
});
|
|
361
362
|
|
|
363
|
+
// Start with baseline - empty inbound queue.
|
|
364
|
+
assert(!this.localPaused, 0x293 /* "initial state" */);
|
|
365
|
+
|
|
362
366
|
const allPending = this.deltaManager.inbound.toArray();
|
|
363
367
|
for (const pending of allPending) {
|
|
364
368
|
this.trackPending(pending);
|
|
365
369
|
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* The only public function in this class - called when we processed an op,
|
|
374
|
+
* to make decision if op processing should be paused or not afer that.
|
|
375
|
+
*/
|
|
376
|
+
public afterOpProcessing(sequenceNumber: number) {
|
|
377
|
+
assert(!this.localPaused, 0x294 /* "can't have op processing paused if we are processing an op" */);
|
|
378
|
+
|
|
379
|
+
// If the inbound queue is ever empty, nothing to do!
|
|
380
|
+
if (this.deltaManager.inbound.length === 0) {
|
|
381
|
+
assert(this.pauseSequenceNumber === undefined,
|
|
382
|
+
0x295 /* "there should be no pending batch if we have no ops" */);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
366
385
|
|
|
367
|
-
//
|
|
368
|
-
|
|
386
|
+
// The queue is
|
|
387
|
+
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
388
|
+
// - here (processing ops until reaching start of incomplete batch)
|
|
389
|
+
// - in trackPending(), when queue was empty and start of batch showed up.
|
|
390
|
+
// 2. resumed when batch end comes in (in trackPending())
|
|
391
|
+
|
|
392
|
+
// do we have incomplete batch to worry about?
|
|
393
|
+
if (this.pauseSequenceNumber !== undefined) {
|
|
394
|
+
assert(sequenceNumber < this.pauseSequenceNumber,
|
|
395
|
+
0x296 /* "we should never start processing incomplete batch!" */);
|
|
396
|
+
// If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
|
|
397
|
+
if (sequenceNumber + 1 === this.pauseSequenceNumber) {
|
|
398
|
+
this.pauseQueue();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
369
401
|
}
|
|
370
402
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
403
|
+
private pauseQueue() {
|
|
404
|
+
assert(!this.localPaused, 0x297 /* "always called from resumed state" */);
|
|
405
|
+
this.localPaused = true;
|
|
406
|
+
this.timePaused = performance.now();
|
|
407
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
408
|
+
this.deltaManager.inbound.pause();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private resumeQueue(startBatch: number, endBatch: number) {
|
|
412
|
+
// Return early if no change in value
|
|
413
|
+
if (!this.localPaused) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
this.localPaused = false;
|
|
418
|
+
const duration = performance.now() - this.timePaused;
|
|
419
|
+
// Random round number - we want to know when batch waiting paused op processing.
|
|
420
|
+
if (duration > latencyThreshold) {
|
|
421
|
+
this.logger.sendErrorEvent({
|
|
422
|
+
eventName: "MaxBatchWaitTimeExceeded",
|
|
423
|
+
duration,
|
|
424
|
+
sequenceNumber: endBatch,
|
|
425
|
+
length: endBatch - startBatch,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
this.deltaManager.inbound.resume();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Called for each incoming op (i.e. inbound "push" notification)
|
|
433
|
+
*/
|
|
434
|
+
private trackPending(message: ISequencedDocumentMessage) {
|
|
435
|
+
assert(this.deltaManager.inbound.length !== 0,
|
|
436
|
+
0x298 /* "we have something in the queue that generates this event" */);
|
|
437
|
+
|
|
438
|
+
assert((this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined),
|
|
439
|
+
0x299 /* "non-synchronized state" */);
|
|
440
|
+
|
|
441
|
+
const metadata = message.metadata as IRuntimeMessageMetadata;
|
|
442
|
+
const batchMetadata = metadata?.batch;
|
|
443
|
+
|
|
444
|
+
// Protocol messages are never part of a runtime batch of messages
|
|
445
|
+
if (!isRuntimeMessage(message)) {
|
|
446
|
+
// Protocol messages should never show up in the middle of the batch!
|
|
447
|
+
assert(this.currentBatchClientId === undefined, 0x29a /* "System message in the middle of batch!" */);
|
|
448
|
+
assert(batchMetadata === undefined, 0x29b /* "system op in a batch?" */);
|
|
449
|
+
assert(!this.localPaused, 0x29c /* "we should be processing ops when there is no active batch" */);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (this.currentBatchClientId === undefined && batchMetadata === undefined) {
|
|
454
|
+
assert(!this.localPaused, 0x29d /* "we should be processing ops when there is no active batch" */);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
|
|
459
|
+
// If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
|
|
460
|
+
// the previous one
|
|
461
|
+
if (this.currentBatchClientId !== undefined || batchMetadata === false) {
|
|
462
|
+
if (this.currentBatchClientId !== message.clientId) {
|
|
463
|
+
// "Batch not closed, yet message from another client!"
|
|
464
|
+
throw new DataCorruptionError(
|
|
465
|
+
"OpBatchIncomplete",
|
|
466
|
+
{
|
|
467
|
+
batchClientId: this.currentBatchClientId,
|
|
468
|
+
...extractSafePropertiesFromMessage(message),
|
|
469
|
+
});
|
|
384
470
|
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// The queue is
|
|
474
|
+
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
475
|
+
// - in afterOpProcessing() - processing ops until reaching start of incomplete batch
|
|
476
|
+
// - here (batchMetadata == false below), when queue was empty and start of batch showed up.
|
|
477
|
+
// 2. resumed when batch end comes in (batchMetadata === true case below)
|
|
478
|
+
|
|
479
|
+
if (batchMetadata) {
|
|
480
|
+
assert(this.currentBatchClientId === undefined, 0x29e /* "there can't be active batch" */);
|
|
481
|
+
assert(!this.localPaused, 0x29f /* "we should be processing ops when there is no active batch" */);
|
|
482
|
+
this.pauseSequenceNumber = message.sequenceNumber;
|
|
483
|
+
this.currentBatchClientId = message.clientId;
|
|
484
|
+
// Start of the batch
|
|
485
|
+
// Only pause processing if queue has no other ops!
|
|
486
|
+
// If there are any other ops in the queue, processing will be stopped when they are processed!
|
|
487
|
+
if (this.deltaManager.inbound.length === 1) {
|
|
488
|
+
this.pauseQueue();
|
|
489
|
+
}
|
|
490
|
+
} else if (batchMetadata === false) {
|
|
491
|
+
assert(this.pauseSequenceNumber !== undefined, 0x2a0 /* "batch presence was validated above" */);
|
|
492
|
+
// Batch is complete, we can process it!
|
|
493
|
+
this.resumeQueue(this.pauseSequenceNumber, message.sequenceNumber);
|
|
494
|
+
this.pauseSequenceNumber = undefined;
|
|
495
|
+
this.currentBatchClientId = undefined;
|
|
496
|
+
} else {
|
|
497
|
+
// Continuation of current batch. Do nothing
|
|
498
|
+
assert(this.currentBatchClientId !== undefined, 0x2a1 /* "logic error" */);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* This class has the following responsibilities:
|
|
505
|
+
* 1. It tracks batches as we process ops and raises "batchBegin" and "batchEnd" events.
|
|
506
|
+
* As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)
|
|
507
|
+
* 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch
|
|
508
|
+
* unless all ops of the batch are in.
|
|
509
|
+
*/
|
|
510
|
+
export class ScheduleManager {
|
|
511
|
+
private readonly deltaScheduler: DeltaScheduler;
|
|
512
|
+
private batchClientId: string | undefined;
|
|
513
|
+
private hitError = false;
|
|
514
|
+
|
|
515
|
+
private readonly scheduler: ScheduleManagerCore;
|
|
516
|
+
|
|
517
|
+
constructor(
|
|
518
|
+
private readonly deltaManager: IDeltaManager<ISequencedDocumentMessage, IDocumentMessage>,
|
|
519
|
+
private readonly emitter: EventEmitter,
|
|
520
|
+
private readonly logger: ITelemetryLogger,
|
|
521
|
+
) {
|
|
522
|
+
this.deltaScheduler = new DeltaScheduler(
|
|
523
|
+
this.deltaManager,
|
|
524
|
+
ChildLogger.create(this.logger, "DeltaScheduler"),
|
|
525
|
+
);
|
|
526
|
+
this.scheduler = new ScheduleManagerCore(deltaManager, logger);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
public beforeOpProcessing(message: ISequencedDocumentMessage) {
|
|
530
|
+
if (this.batchClientId !== message.clientId) {
|
|
531
|
+
assert(this.batchClientId === undefined,
|
|
532
|
+
0x2a2 /* "Batch is interrupted by other client op. Should be caught by trackPending()" */);
|
|
385
533
|
|
|
386
534
|
// This could be the beginning of a new batch or an individual message.
|
|
387
535
|
this.emitter.emit("batchBegin", message);
|
|
@@ -396,85 +544,34 @@ export class ScheduleManager {
|
|
|
396
544
|
}
|
|
397
545
|
}
|
|
398
546
|
|
|
399
|
-
public
|
|
547
|
+
public afterOpProcessing(error: any | undefined, message: ISequencedDocumentMessage) {
|
|
548
|
+
// If this is no longer true, we need to revisit what we do where we set this.hitError.
|
|
549
|
+
assert(!this.hitError, 0x2a3 /* "container should be closed on any error" */);
|
|
550
|
+
|
|
551
|
+
// Let the scheduler know how far we progressed, to decide if op processing
|
|
552
|
+
// should be paused or not.
|
|
553
|
+
this.scheduler.afterOpProcessing(message.sequenceNumber);
|
|
554
|
+
|
|
400
555
|
if (error) {
|
|
556
|
+
// We assume here that loader will close container and stop processing all future ops.
|
|
557
|
+
// This is implicit dependency. If this flow changes, this code might no longer be correct.
|
|
558
|
+
this.hitError = true;
|
|
401
559
|
this.batchClientId = undefined;
|
|
402
560
|
this.emitter.emit("batchEnd", error, message);
|
|
403
561
|
this.deltaScheduler.batchEnd();
|
|
404
562
|
return;
|
|
405
563
|
}
|
|
406
564
|
|
|
407
|
-
this.updatePauseState(message.sequenceNumber);
|
|
408
|
-
|
|
409
565
|
const batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;
|
|
410
566
|
// If no batchClientId has been set then we're in an individual batch. Else, if we get
|
|
411
567
|
// batch end metadata, this is end of the current batch.
|
|
412
|
-
if (
|
|
568
|
+
if (this.batchClientId === undefined || batch === false) {
|
|
413
569
|
this.batchClientId = undefined;
|
|
414
570
|
this.emitter.emit("batchEnd", undefined, message);
|
|
415
571
|
this.deltaScheduler.batchEnd();
|
|
416
572
|
return;
|
|
417
573
|
}
|
|
418
574
|
}
|
|
419
|
-
|
|
420
|
-
public setPaused(localPaused: boolean) {
|
|
421
|
-
// Return early if no change in value
|
|
422
|
-
if (this.localPaused === localPaused) {
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
this.localPaused = localPaused;
|
|
427
|
-
if (localPaused) {
|
|
428
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
429
|
-
this.deltaManager.inbound.pause();
|
|
430
|
-
} else {
|
|
431
|
-
this.deltaManager.inbound.resume();
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
private updatePauseState(sequenceNumber: number) {
|
|
436
|
-
// If the inbound queue is ever empty we pause it and wait for new events
|
|
437
|
-
if (this.deltaManager.inbound.length === 0) {
|
|
438
|
-
this.setPaused(true);
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// If no message has caused the pause flag to be set, or the next message up is not the one we need to pause at
|
|
443
|
-
// then we simply continue processing
|
|
444
|
-
if (!this.pauseSequenceNumber || sequenceNumber + 1 < this.pauseSequenceNumber) {
|
|
445
|
-
this.setPaused(false);
|
|
446
|
-
} else {
|
|
447
|
-
// Otherwise the next message requires us to pause
|
|
448
|
-
this.setPaused(true);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
private trackPending(message: ISequencedDocumentMessage) {
|
|
453
|
-
const metadata = message.metadata as IRuntimeMessageMetadata | undefined;
|
|
454
|
-
|
|
455
|
-
// Protocol messages are never part of a runtime batch of messages
|
|
456
|
-
if (!isRuntimeMessage(message)) {
|
|
457
|
-
this.pauseSequenceNumber = undefined;
|
|
458
|
-
this.pauseClientId = undefined;
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const batchMetadata = metadata ? metadata.batch : undefined;
|
|
463
|
-
|
|
464
|
-
// If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
|
|
465
|
-
if (this.pauseClientId === message.clientId) {
|
|
466
|
-
if (batchMetadata !== undefined) {
|
|
467
|
-
// If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
|
|
468
|
-
// the previous one
|
|
469
|
-
this.pauseSequenceNumber = batchMetadata ? message.sequenceNumber : undefined;
|
|
470
|
-
this.pauseClientId = batchMetadata ? this.pauseClientId : undefined;
|
|
471
|
-
}
|
|
472
|
-
} else {
|
|
473
|
-
// We check the batch flag for the new clientID - if true we pause otherwise we reset the tracking data
|
|
474
|
-
this.pauseSequenceNumber = batchMetadata ? message.sequenceNumber : undefined;
|
|
475
|
-
this.pauseClientId = batchMetadata ? message.clientId : undefined;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
575
|
}
|
|
479
576
|
|
|
480
577
|
/**
|
|
@@ -484,6 +581,19 @@ export class ScheduleManager {
|
|
|
484
581
|
*/
|
|
485
582
|
export const agentSchedulerId = "_scheduler";
|
|
486
583
|
|
|
584
|
+
// safely check navigator and get the hardware spec value
|
|
585
|
+
export function getDeviceSpec() {
|
|
586
|
+
try {
|
|
587
|
+
if (typeof navigator === "object" && navigator !== null) {
|
|
588
|
+
return {
|
|
589
|
+
deviceMemory: (navigator as any).deviceMemory,
|
|
590
|
+
hardwareConcurrency: navigator.hardwareConcurrency,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
} catch {
|
|
594
|
+
}
|
|
595
|
+
return {};
|
|
596
|
+
}
|
|
487
597
|
/**
|
|
488
598
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
489
599
|
* It will define the store level mappings.
|
|
@@ -519,7 +629,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
519
629
|
registryEntries: NamedFluidDataStoreRegistryEntries,
|
|
520
630
|
requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise<IResponse>,
|
|
521
631
|
runtimeOptions: IContainerRuntimeOptions = {},
|
|
522
|
-
containerScope:
|
|
632
|
+
containerScope: FluidObject = context.scope,
|
|
523
633
|
existing?: boolean,
|
|
524
634
|
): Promise<ContainerRuntime> {
|
|
525
635
|
// If taggedLogger exists, use it. Otherwise, wrap the vanilla logger:
|
|
@@ -531,7 +641,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
531
641
|
});
|
|
532
642
|
|
|
533
643
|
const {
|
|
534
|
-
summaryOptions = {
|
|
644
|
+
summaryOptions = {},
|
|
535
645
|
gcOptions = {},
|
|
536
646
|
loadSequenceNumberVerification = "close",
|
|
537
647
|
} = runtimeOptions;
|
|
@@ -597,8 +707,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
597
707
|
const protocolSequenceNumber = context.deltaManager.initialSequenceNumber;
|
|
598
708
|
// Unless bypass is explicitly set, then take action when sequence numbers mismatch.
|
|
599
709
|
if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
|
|
710
|
+
// "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
|
|
600
711
|
const error = new DataCorruptionError(
|
|
601
|
-
"
|
|
712
|
+
"SummaryMetadataMismatch",
|
|
602
713
|
{ runtimeSequenceNumber, protocolSequenceNumber },
|
|
603
714
|
);
|
|
604
715
|
|
|
@@ -692,7 +803,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
692
803
|
return this._flushMode;
|
|
693
804
|
}
|
|
694
805
|
|
|
695
|
-
public get scope(): IFluidObject {
|
|
806
|
+
public get scope(): IFluidObject & FluidObject {
|
|
696
807
|
return this.containerScope;
|
|
697
808
|
}
|
|
698
809
|
|
|
@@ -704,9 +815,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
704
815
|
return this.context.attachState;
|
|
705
816
|
}
|
|
706
817
|
|
|
707
|
-
// Back compat: 0.28, can be removed in 0.29
|
|
708
|
-
public readonly IFluidSerializer: IFluidSerializer;
|
|
709
|
-
|
|
710
818
|
public readonly IFluidHandleContext: IFluidHandleContext;
|
|
711
819
|
|
|
712
820
|
// internal logger for ContainerRuntime. Use this.logger for stores, summaries, etc.
|
|
@@ -773,6 +881,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
773
881
|
|
|
774
882
|
private readonly dataStores: DataStores;
|
|
775
883
|
|
|
884
|
+
/**
|
|
885
|
+
* True, if GC data should be written at root of the summary tree.
|
|
886
|
+
* False, if data stores should write GC blobs in their summary tree.
|
|
887
|
+
*/
|
|
888
|
+
public get writeGCDataAtRoot(): boolean {
|
|
889
|
+
return this.garbageCollector.writeDataAtRoot;
|
|
890
|
+
}
|
|
891
|
+
|
|
776
892
|
/**
|
|
777
893
|
* True if generating summaries with isolated channels is
|
|
778
894
|
* explicitly disabled. This only affects how summaries are written,
|
|
@@ -791,6 +907,9 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
791
907
|
return this._summarizer;
|
|
792
908
|
}
|
|
793
909
|
|
|
910
|
+
private readonly createContainerMetadata: ICreateContainerMetadata;
|
|
911
|
+
private summaryCount: number | undefined;
|
|
912
|
+
|
|
794
913
|
private constructor(
|
|
795
914
|
private readonly context: IContainerContext,
|
|
796
915
|
private readonly registry: IFluidDataStoreRegistry,
|
|
@@ -798,7 +917,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
798
917
|
electedSummarizerData: ISerializedElection | undefined,
|
|
799
918
|
chunks: [string, string[]][],
|
|
800
919
|
private readonly runtimeOptions: Readonly<Required<IContainerRuntimeOptions>>,
|
|
801
|
-
private readonly containerScope:
|
|
920
|
+
private readonly containerScope: FluidObject,
|
|
802
921
|
public readonly logger: ITelemetryLogger,
|
|
803
922
|
existing: boolean,
|
|
804
923
|
blobManagerSnapshot: IBlobManagerLoadInfo,
|
|
@@ -806,9 +925,23 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
806
925
|
private _storage?: IDocumentStorageService,
|
|
807
926
|
) {
|
|
808
927
|
super();
|
|
809
|
-
|
|
810
928
|
this.baseSummaryMessage = metadata?.message;
|
|
811
929
|
|
|
930
|
+
// If this is an existing container, we get values from metadata.
|
|
931
|
+
// otherwise, we initialize them.
|
|
932
|
+
if (existing) {
|
|
933
|
+
this.createContainerMetadata = {
|
|
934
|
+
createContainerRuntimeVersion: metadata?.createContainerRuntimeVersion,
|
|
935
|
+
createContainerTimestamp: metadata?.createContainerTimestamp,
|
|
936
|
+
};
|
|
937
|
+
this.summaryCount = metadata?.summaryCount;
|
|
938
|
+
} else {
|
|
939
|
+
this.createContainerMetadata = {
|
|
940
|
+
createContainerRuntimeVersion: pkgVersion,
|
|
941
|
+
createContainerTimestamp: Date.now(),
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
812
945
|
// Default to false (enabled).
|
|
813
946
|
this.disableIsolatedChannels = this.runtimeOptions.summaryOptions.disableIsolatedChannels ?? false;
|
|
814
947
|
|
|
@@ -816,14 +949,26 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
816
949
|
this.chunkMap = new Map<string, string[]>(chunks);
|
|
817
950
|
|
|
818
951
|
this.IFluidHandleContext = new ContainerFluidHandleContext("", this);
|
|
819
|
-
this.IFluidSerializer = new FluidSerializer(this.IFluidHandleContext);
|
|
820
952
|
|
|
821
953
|
this._logger = ChildLogger.create(this.logger, "ContainerRuntime");
|
|
822
954
|
|
|
955
|
+
/**
|
|
956
|
+
* Function that return the current server timestamp. This is used by the garbage collector to set the
|
|
957
|
+
* time when a node becomes unreferenced.
|
|
958
|
+
* For now, we use the timestamp of the last op for gcTimestamp. However, there can be cases where
|
|
959
|
+
* we don't have an op (on demand summaries for instance). In those cases, we will use the timestamp
|
|
960
|
+
* of this client's connection - https://github.com/microsoft/FluidFramework/issues/8375.
|
|
961
|
+
*/
|
|
962
|
+
const getCurrentTimestamp = () => {
|
|
963
|
+
return this.deltaManager.lastMessage?.timestamp ?? Date.now();
|
|
964
|
+
};
|
|
823
965
|
this.garbageCollector = GarbageCollector.create(
|
|
824
966
|
this,
|
|
825
967
|
this.runtimeOptions.gcOptions,
|
|
826
968
|
(unusedRoutes: string[]) => this.dataStores.deleteUnusedRoutes(unusedRoutes),
|
|
969
|
+
getCurrentTimestamp,
|
|
970
|
+
context.baseSnapshot,
|
|
971
|
+
async <T>(id: string) => readAndParse<T>(this.storage, id),
|
|
827
972
|
this._logger,
|
|
828
973
|
existing,
|
|
829
974
|
metadata,
|
|
@@ -847,8 +992,6 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
847
992
|
throwOnFailure: true,
|
|
848
993
|
// If GC should not run, let the summarizer node know so that it does not track GC state.
|
|
849
994
|
gcDisabled: !this.garbageCollector.shouldRunGC,
|
|
850
|
-
// The max duration for which objects can be unreferenced before they are eligible for deletion.
|
|
851
|
-
maxUnreferencedDurationMs: this.runtimeOptions.gcOptions.maxUnreferencedDurationMs,
|
|
852
995
|
},
|
|
853
996
|
);
|
|
854
997
|
|
|
@@ -873,7 +1016,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
873
1016
|
getInitialGCSummaryDetailsFn,
|
|
874
1017
|
),
|
|
875
1018
|
(id: string) => this.summarizerNode.deleteChild(id),
|
|
876
|
-
this._logger
|
|
1019
|
+
this._logger,
|
|
1020
|
+
async () => this.garbageCollector.getDataStoreBaseGCDetails(),
|
|
1021
|
+
(id: string) => this.garbageCollector.nodeChanged(id),
|
|
1022
|
+
);
|
|
877
1023
|
|
|
878
1024
|
this.blobManager = new BlobManager(
|
|
879
1025
|
this.IFluidHandleContext,
|
|
@@ -901,16 +1047,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
901
1047
|
this.clearPartialChunks(clientId);
|
|
902
1048
|
});
|
|
903
1049
|
|
|
904
|
-
this.context.quorum.on("addProposal", (proposal) => {
|
|
905
|
-
if (proposal.key === "code" || proposal.key === "code2") {
|
|
906
|
-
this.emit("codeDetailsProposed", proposal.value, proposal);
|
|
907
|
-
}
|
|
908
|
-
});
|
|
909
|
-
|
|
910
1050
|
this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
|
|
911
1051
|
|
|
912
1052
|
// Only create a SummaryManager if summaries are enabled and we are not the summarizer client
|
|
1053
|
+
// Map the deprecated generateSummaries flag to disableSummaries.
|
|
913
1054
|
if (this.runtimeOptions.summaryOptions.generateSummaries === false) {
|
|
1055
|
+
this.runtimeOptions.summaryOptions.disableSummaries = true;
|
|
1056
|
+
}
|
|
1057
|
+
if (this.summariesDisabled()) {
|
|
914
1058
|
this._logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
|
|
915
1059
|
} else {
|
|
916
1060
|
const maxOpsSinceLastSummary = this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary ?? 7000;
|
|
@@ -999,7 +1143,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
999
1143
|
this.deltaManager.on("readonly", (readonly: boolean) => {
|
|
1000
1144
|
// we accumulate ops while being in read-only state.
|
|
1001
1145
|
// once user gets write permissions and we have active connection, flush all pending ops.
|
|
1002
|
-
|
|
1146
|
+
// eslint-disable-next-line max-len
|
|
1147
|
+
assert(readonly === this.deltaManager.readOnlyInfo.readonly, 0x124 /* "inconsistent readonly property/event state" */);
|
|
1003
1148
|
|
|
1004
1149
|
// We need to be very careful with when we (re)send pending ops, to ensure that we only send ops
|
|
1005
1150
|
// when we either never send an op, or attempted to send it but we know for sure it was not
|
|
@@ -1022,6 +1167,23 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1022
1167
|
this.deltaManager.on("op", this.onOp);
|
|
1023
1168
|
}
|
|
1024
1169
|
|
|
1170
|
+
// logging hardware telemetry
|
|
1171
|
+
logger.sendTelemetryEvent({
|
|
1172
|
+
eventName:"DeviceSpec",
|
|
1173
|
+
...getDeviceSpec(),
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
// logging container load stats
|
|
1177
|
+
this.logger.sendTelemetryEvent({
|
|
1178
|
+
eventName: "ContainerLoadStats",
|
|
1179
|
+
...this.createContainerMetadata,
|
|
1180
|
+
...this.dataStores.containerLoadStats,
|
|
1181
|
+
summaryCount: this.summaryCount,
|
|
1182
|
+
summaryFormatVersion: metadata?.summaryFormatVersion,
|
|
1183
|
+
disableIsolatedChannels: metadata?.disableIsolatedChannels,
|
|
1184
|
+
gcVersion: metadata?.gcFeature,
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1025
1187
|
ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
|
|
1026
1188
|
}
|
|
1027
1189
|
|
|
@@ -1146,6 +1308,8 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1146
1308
|
|
|
1147
1309
|
private formMetadata(): IContainerRuntimeMetadata {
|
|
1148
1310
|
return {
|
|
1311
|
+
...this.createContainerMetadata,
|
|
1312
|
+
summaryCount: this.summaryCount,
|
|
1149
1313
|
summaryFormatVersion: 1,
|
|
1150
1314
|
disableIsolatedChannels: this.disableIsolatedChannels || undefined,
|
|
1151
1315
|
gcFeature: this.garbageCollector.gcSummaryFeatureVersion,
|
|
@@ -1182,26 +1346,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1182
1346
|
* @deprecated - Use summarize to get summary of the container runtime.
|
|
1183
1347
|
*/
|
|
1184
1348
|
public async snapshot(): Promise<ITree> {
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
root.entries = root.entries.concat(entries);
|
|
1194
|
-
} else {
|
|
1195
|
-
root.entries.push(new TreeTreeEntry(channelsTreeName, { entries }));
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
root.entries.push(new BlobTreeEntry(metadataBlobName, JSON.stringify(this.formMetadata())));
|
|
1199
|
-
|
|
1200
|
-
if (this.chunkMap.size > 0) {
|
|
1201
|
-
root.entries.push(new BlobTreeEntry(chunksBlobName, JSON.stringify([...this.chunkMap])));
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
return root;
|
|
1349
|
+
const summaryResult = await this.summarize({
|
|
1350
|
+
summaryLogger: this.logger,
|
|
1351
|
+
fullTree: true,
|
|
1352
|
+
trackState: false,
|
|
1353
|
+
runGC: this.garbageCollector.shouldRunGC,
|
|
1354
|
+
fullGC: true,
|
|
1355
|
+
});
|
|
1356
|
+
return convertSummaryTreeToITree(summaryResult.summary);
|
|
1205
1357
|
}
|
|
1206
1358
|
|
|
1207
1359
|
private addContainerBlobsToSummary(summaryTree: ISummaryTreeWithStats) {
|
|
@@ -1223,6 +1375,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1223
1375
|
const blobsTree = convertToSummaryTree(snapshot, false);
|
|
1224
1376
|
addTreeToSummary(summaryTree, blobsTreeName, blobsTree);
|
|
1225
1377
|
}
|
|
1378
|
+
|
|
1379
|
+
if (this.writeGCDataAtRoot) {
|
|
1380
|
+
const gcSummary = this.garbageCollector.summarize();
|
|
1381
|
+
if (gcSummary !== undefined) {
|
|
1382
|
+
addTreeToSummary(summaryTree, gcTreeKey, gcSummary);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1226
1385
|
}
|
|
1227
1386
|
|
|
1228
1387
|
private replayPendingStates() {
|
|
@@ -1326,12 +1485,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1326
1485
|
// but would not modify contents details
|
|
1327
1486
|
let message = { ...messageArg };
|
|
1328
1487
|
|
|
1329
|
-
let error: any | undefined;
|
|
1330
|
-
|
|
1331
1488
|
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
1332
1489
|
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
1333
1490
|
// messages once a batch has been fully processed.
|
|
1334
|
-
this.scheduleManager.
|
|
1491
|
+
this.scheduleManager.beforeOpProcessing(message);
|
|
1335
1492
|
|
|
1336
1493
|
try {
|
|
1337
1494
|
message = unpackRuntimeMessage(message);
|
|
@@ -1365,11 +1522,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1365
1522
|
}
|
|
1366
1523
|
|
|
1367
1524
|
this.emit("op", message);
|
|
1525
|
+
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
1368
1526
|
} catch (e) {
|
|
1369
|
-
|
|
1527
|
+
this.scheduleManager.afterOpProcessing(e, message);
|
|
1370
1528
|
throw e;
|
|
1371
|
-
} finally {
|
|
1372
|
-
this.scheduleManager.endOperation(error, message);
|
|
1373
1529
|
}
|
|
1374
1530
|
}
|
|
1375
1531
|
|
|
@@ -1675,21 +1831,17 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1675
1831
|
* Implementation of IGarbageCollectionRuntime::updateUsedRoutes.
|
|
1676
1832
|
* After GC has run, called to notify this container's nodes of routes that are used in it.
|
|
1677
1833
|
* @param usedRoutes - The routes that are used in all nodes in this Container.
|
|
1834
|
+
* @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
|
|
1835
|
+
* unreferenced as part of this GC run, this should be used to update the time when it happens.
|
|
1678
1836
|
* @returns the statistics of the used state of the data stores.
|
|
1679
1837
|
*/
|
|
1680
|
-
public updateUsedRoutes(usedRoutes: string[]): IUsedStateStats {
|
|
1838
|
+
public updateUsedRoutes(usedRoutes: string[], gcTimestamp?: number): IUsedStateStats {
|
|
1681
1839
|
// Update our summarizer node's used routes. Updating used routes in summarizer node before
|
|
1682
1840
|
// summarizing is required and asserted by the the summarizer node. We are the root and are
|
|
1683
1841
|
// always referenced, so the used routes is only self-route (empty string).
|
|
1684
1842
|
this.summarizerNode.updateUsedRoutes([""]);
|
|
1685
1843
|
|
|
1686
|
-
return this.dataStores.updateUsedRoutes(
|
|
1687
|
-
usedRoutes,
|
|
1688
|
-
// For now, we use the timestamp of the last op for gcTimestamp. However, there can be cases where
|
|
1689
|
-
// we don't have an op (on demand summaries for instance). In those cases, we will use the timestamp
|
|
1690
|
-
// of this client's connection - https://github.com/microsoft/FluidFramework/issues/7152.
|
|
1691
|
-
this.deltaManager.lastMessage?.timestamp,
|
|
1692
|
-
);
|
|
1844
|
+
return this.dataStores.updateUsedRoutes(usedRoutes, gcTimestamp);
|
|
1693
1845
|
}
|
|
1694
1846
|
|
|
1695
1847
|
/**
|
|
@@ -1781,6 +1933,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1781
1933
|
return { stage: "base", referenceSequenceNumber: summaryRefSeqNum, error: continueResult.error };
|
|
1782
1934
|
}
|
|
1783
1935
|
|
|
1936
|
+
// increment summary count
|
|
1937
|
+
if (this.summaryCount !== undefined) {
|
|
1938
|
+
this.summaryCount++;
|
|
1939
|
+
} else {
|
|
1940
|
+
this.summaryCount = 1;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1784
1943
|
const trace = Trace.start();
|
|
1785
1944
|
let summarizeResult: ISummaryTreeWithStats;
|
|
1786
1945
|
try {
|
|
@@ -2199,6 +2358,14 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2199
2358
|
return this.pendingStateManager.getLocalState();
|
|
2200
2359
|
}
|
|
2201
2360
|
|
|
2361
|
+
/**
|
|
2362
|
+
* @returns true if summaries are explicitly disabled for this ContainerRuntime, false otherwise
|
|
2363
|
+
*/
|
|
2364
|
+
public summariesDisabled(): boolean {
|
|
2365
|
+
return this.runtimeOptions.summaryOptions.disableSummaries === true ||
|
|
2366
|
+
this.runtimeOptions.summaryOptions.summaryConfigOverrides?.disableSummaries === true;
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2202
2369
|
public readonly summarizeOnDemand: ISummarizer["summarizeOnDemand"] = (...args) => {
|
|
2203
2370
|
if (this.clientDetails.type === summarizerClientType) {
|
|
2204
2371
|
return this.summarizer.summarizeOnDemand(...args);
|
|
@@ -2206,10 +2373,10 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2206
2373
|
return this.summaryManager.summarizeOnDemand(...args);
|
|
2207
2374
|
} else {
|
|
2208
2375
|
// If we're not the summarizer, and we don't have a summaryManager, we expect that
|
|
2209
|
-
//
|
|
2376
|
+
// disableSummaries is turned on. We are throwing instead of returning a failure here,
|
|
2210
2377
|
// because it is a misuse of the API rather than an expected failure.
|
|
2211
2378
|
throw new Error(
|
|
2212
|
-
`Can't summarize,
|
|
2379
|
+
`Can't summarize, disableSummaries: ${this.summariesDisabled()}`,
|
|
2213
2380
|
);
|
|
2214
2381
|
}
|
|
2215
2382
|
};
|
|
@@ -2224,7 +2391,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2224
2391
|
// generateSummaries is turned off. We are throwing instead of returning a failure here,
|
|
2225
2392
|
// because it is a misuse of the API rather than an expected failure.
|
|
2226
2393
|
throw new Error(
|
|
2227
|
-
`Can't summarize,
|
|
2394
|
+
`Can't summarize, disableSummaries: ${this.summariesDisabled()}`,
|
|
2228
2395
|
);
|
|
2229
2396
|
}
|
|
2230
2397
|
};
|