@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/lib/containerRuntime.js
CHANGED
|
@@ -3,21 +3,20 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
import { AttachState, } from "@fluidframework/container-definitions";
|
|
6
|
-
import { assert, Trace, TypedEventEmitter, unreachableCase, } from "@fluidframework/common-utils";
|
|
6
|
+
import { assert, Trace, TypedEventEmitter, unreachableCase, performance, } from "@fluidframework/common-utils";
|
|
7
7
|
import { ChildLogger, raiseConnectedEvent, PerformanceEvent, normalizeError, TaggedLoggerAdapter, } from "@fluidframework/telemetry-utils";
|
|
8
8
|
import { readAndParse, BlobAggregationStorage } from "@fluidframework/driver-utils";
|
|
9
|
-
import { DataCorruptionError, GenericError } from "@fluidframework/container-utils";
|
|
10
|
-
import {
|
|
11
|
-
import { MessageType, } from "@fluidframework/protocol-definitions";
|
|
9
|
+
import { DataCorruptionError, GenericError, extractSafePropertiesFromMessage } from "@fluidframework/container-utils";
|
|
10
|
+
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
12
11
|
import { FlushMode, channelsTreeName, } from "@fluidframework/runtime-definitions";
|
|
13
|
-
import { addBlobToSummary, addTreeToSummary, convertToSummaryTree, createRootSummarizerNodeWithGC,
|
|
12
|
+
import { addBlobToSummary, addTreeToSummary, convertToSummaryTree, createRootSummarizerNodeWithGC, RequestParser, create404Response, exceptionToResponse, responseToException, seqFromTree, convertSummaryTreeToITree, } from "@fluidframework/runtime-utils";
|
|
14
13
|
import { v4 as uuid } from "uuid";
|
|
15
14
|
import { ContainerFluidHandleContext } from "./containerHandleContext";
|
|
16
15
|
import { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
17
16
|
import { Summarizer } from "./summarizer";
|
|
18
17
|
import { formRequestSummarizerFn, SummaryManager } from "./summaryManager";
|
|
19
18
|
import { DeltaScheduler } from "./deltaScheduler";
|
|
20
|
-
import { ReportOpPerfTelemetry } from "./connectionTelemetry";
|
|
19
|
+
import { ReportOpPerfTelemetry, latencyThreshold } from "./connectionTelemetry";
|
|
21
20
|
import { PendingStateManager } from "./pendingStateManager";
|
|
22
21
|
import { pkgVersion } from "./packageVersion";
|
|
23
22
|
import { BlobManager } from "./blobManager";
|
|
@@ -29,7 +28,7 @@ import { OrderedClientCollection, OrderedClientElection } from "./orderedClientE
|
|
|
29
28
|
import { SummarizerClientElection, summarizerClientType } from "./summarizerClientElection";
|
|
30
29
|
import { formExponentialFn, Throttler } from "./throttler";
|
|
31
30
|
import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
|
|
32
|
-
import { GarbageCollector, } from "./garbageCollection";
|
|
31
|
+
import { GarbageCollector, gcTreeKey, } from "./garbageCollection";
|
|
33
32
|
export var ContainerMessageType;
|
|
34
33
|
(function (ContainerMessageType) {
|
|
35
34
|
// An op to be delivered to store
|
|
@@ -92,13 +91,16 @@ export function unpackRuntimeMessage(message) {
|
|
|
92
91
|
}
|
|
93
92
|
return message;
|
|
94
93
|
}
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
/**
|
|
95
|
+
* This class controls pausing and resuming of inbound queue to ensure that we never
|
|
96
|
+
* start processing ops in a batch IF we do not have all ops in the batch.
|
|
97
|
+
*/
|
|
98
|
+
class ScheduleManagerCore {
|
|
99
|
+
constructor(deltaManager, logger) {
|
|
97
100
|
this.deltaManager = deltaManager;
|
|
98
|
-
this.emitter = emitter;
|
|
99
101
|
this.logger = logger;
|
|
100
102
|
this.localPaused = false;
|
|
101
|
-
this.
|
|
103
|
+
this.timePaused = 0;
|
|
102
104
|
// Listen for delta manager sends and add batch metadata to messages
|
|
103
105
|
this.deltaManager.on("prepareSend", (messages) => {
|
|
104
106
|
if (messages.length === 0) {
|
|
@@ -106,7 +108,7 @@ export class ScheduleManager {
|
|
|
106
108
|
}
|
|
107
109
|
// First message will have the batch flag set to true if doing a batched send
|
|
108
110
|
const firstMessageMetadata = messages[0].metadata;
|
|
109
|
-
if (!firstMessageMetadata ||
|
|
111
|
+
if (!(firstMessageMetadata === null || firstMessageMetadata === void 0 ? void 0 : firstMessageMetadata.batch)) {
|
|
110
112
|
return;
|
|
111
113
|
}
|
|
112
114
|
// If the batch contains only a single op, clear the batch flag.
|
|
@@ -121,29 +123,143 @@ export class ScheduleManager {
|
|
|
121
123
|
// Listen for updates and peek at the inbound
|
|
122
124
|
this.deltaManager.inbound.on("push", (message) => {
|
|
123
125
|
this.trackPending(message);
|
|
124
|
-
this.updatePauseState(message.sequenceNumber);
|
|
125
126
|
});
|
|
127
|
+
// Start with baseline - empty inbound queue.
|
|
128
|
+
assert(!this.localPaused, 0x293 /* "initial state" */);
|
|
126
129
|
const allPending = this.deltaManager.inbound.toArray();
|
|
127
130
|
for (const pending of allPending) {
|
|
128
131
|
this.trackPending(pending);
|
|
129
132
|
}
|
|
130
|
-
// Based on track pending update the pause state
|
|
131
|
-
this.updatePauseState(this.deltaManager.lastSequenceNumber);
|
|
132
133
|
}
|
|
133
|
-
|
|
134
|
+
/**
|
|
135
|
+
* The only public function in this class - called when we processed an op,
|
|
136
|
+
* to make decision if op processing should be paused or not afer that.
|
|
137
|
+
*/
|
|
138
|
+
afterOpProcessing(sequenceNumber) {
|
|
139
|
+
assert(!this.localPaused, 0x294 /* "can't have op processing paused if we are processing an op" */);
|
|
140
|
+
// If the inbound queue is ever empty, nothing to do!
|
|
141
|
+
if (this.deltaManager.inbound.length === 0) {
|
|
142
|
+
assert(this.pauseSequenceNumber === undefined, 0x295 /* "there should be no pending batch if we have no ops" */);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// The queue is
|
|
146
|
+
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
147
|
+
// - here (processing ops until reaching start of incomplete batch)
|
|
148
|
+
// - in trackPending(), when queue was empty and start of batch showed up.
|
|
149
|
+
// 2. resumed when batch end comes in (in trackPending())
|
|
150
|
+
// do we have incomplete batch to worry about?
|
|
151
|
+
if (this.pauseSequenceNumber !== undefined) {
|
|
152
|
+
assert(sequenceNumber < this.pauseSequenceNumber, 0x296 /* "we should never start processing incomplete batch!" */);
|
|
153
|
+
// If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
|
|
154
|
+
if (sequenceNumber + 1 === this.pauseSequenceNumber) {
|
|
155
|
+
this.pauseQueue();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
pauseQueue() {
|
|
160
|
+
assert(!this.localPaused, 0x297 /* "always called from resumed state" */);
|
|
161
|
+
this.localPaused = true;
|
|
162
|
+
this.timePaused = performance.now();
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
164
|
+
this.deltaManager.inbound.pause();
|
|
165
|
+
}
|
|
166
|
+
resumeQueue(startBatch, endBatch) {
|
|
167
|
+
// Return early if no change in value
|
|
168
|
+
if (!this.localPaused) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.localPaused = false;
|
|
172
|
+
const duration = performance.now() - this.timePaused;
|
|
173
|
+
// Random round number - we want to know when batch waiting paused op processing.
|
|
174
|
+
if (duration > latencyThreshold) {
|
|
175
|
+
this.logger.sendErrorEvent({
|
|
176
|
+
eventName: "MaxBatchWaitTimeExceeded",
|
|
177
|
+
duration,
|
|
178
|
+
sequenceNumber: endBatch,
|
|
179
|
+
length: endBatch - startBatch,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
this.deltaManager.inbound.resume();
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Called for each incoming op (i.e. inbound "push" notification)
|
|
186
|
+
*/
|
|
187
|
+
trackPending(message) {
|
|
188
|
+
assert(this.deltaManager.inbound.length !== 0, 0x298 /* "we have something in the queue that generates this event" */);
|
|
189
|
+
assert((this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined), 0x299 /* "non-synchronized state" */);
|
|
190
|
+
const metadata = message.metadata;
|
|
191
|
+
const batchMetadata = metadata === null || metadata === void 0 ? void 0 : metadata.batch;
|
|
192
|
+
// Protocol messages are never part of a runtime batch of messages
|
|
193
|
+
if (!isRuntimeMessage(message)) {
|
|
194
|
+
// Protocol messages should never show up in the middle of the batch!
|
|
195
|
+
assert(this.currentBatchClientId === undefined, 0x29a /* "System message in the middle of batch!" */);
|
|
196
|
+
assert(batchMetadata === undefined, 0x29b /* "system op in a batch?" */);
|
|
197
|
+
assert(!this.localPaused, 0x29c /* "we should be processing ops when there is no active batch" */);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (this.currentBatchClientId === undefined && batchMetadata === undefined) {
|
|
201
|
+
assert(!this.localPaused, 0x29d /* "we should be processing ops when there is no active batch" */);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
|
|
205
|
+
// If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
|
|
206
|
+
// the previous one
|
|
207
|
+
if (this.currentBatchClientId !== undefined || batchMetadata === false) {
|
|
208
|
+
if (this.currentBatchClientId !== message.clientId) {
|
|
209
|
+
// "Batch not closed, yet message from another client!"
|
|
210
|
+
throw new DataCorruptionError("OpBatchIncomplete", Object.assign({ batchClientId: this.currentBatchClientId }, extractSafePropertiesFromMessage(message)));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// The queue is
|
|
214
|
+
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
215
|
+
// - in afterOpProcessing() - processing ops until reaching start of incomplete batch
|
|
216
|
+
// - here (batchMetadata == false below), when queue was empty and start of batch showed up.
|
|
217
|
+
// 2. resumed when batch end comes in (batchMetadata === true case below)
|
|
218
|
+
if (batchMetadata) {
|
|
219
|
+
assert(this.currentBatchClientId === undefined, 0x29e /* "there can't be active batch" */);
|
|
220
|
+
assert(!this.localPaused, 0x29f /* "we should be processing ops when there is no active batch" */);
|
|
221
|
+
this.pauseSequenceNumber = message.sequenceNumber;
|
|
222
|
+
this.currentBatchClientId = message.clientId;
|
|
223
|
+
// Start of the batch
|
|
224
|
+
// Only pause processing if queue has no other ops!
|
|
225
|
+
// If there are any other ops in the queue, processing will be stopped when they are processed!
|
|
226
|
+
if (this.deltaManager.inbound.length === 1) {
|
|
227
|
+
this.pauseQueue();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else if (batchMetadata === false) {
|
|
231
|
+
assert(this.pauseSequenceNumber !== undefined, 0x2a0 /* "batch presence was validated above" */);
|
|
232
|
+
// Batch is complete, we can process it!
|
|
233
|
+
this.resumeQueue(this.pauseSequenceNumber, message.sequenceNumber);
|
|
234
|
+
this.pauseSequenceNumber = undefined;
|
|
235
|
+
this.currentBatchClientId = undefined;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
// Continuation of current batch. Do nothing
|
|
239
|
+
assert(this.currentBatchClientId !== undefined, 0x2a1 /* "logic error" */);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* This class has the following responsibilities:
|
|
245
|
+
* 1. It tracks batches as we process ops and raises "batchBegin" and "batchEnd" events.
|
|
246
|
+
* As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)
|
|
247
|
+
* 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch
|
|
248
|
+
* unless all ops of the batch are in.
|
|
249
|
+
*/
|
|
250
|
+
export class ScheduleManager {
|
|
251
|
+
constructor(deltaManager, emitter, logger) {
|
|
252
|
+
this.deltaManager = deltaManager;
|
|
253
|
+
this.emitter = emitter;
|
|
254
|
+
this.logger = logger;
|
|
255
|
+
this.hitError = false;
|
|
256
|
+
this.deltaScheduler = new DeltaScheduler(this.deltaManager, ChildLogger.create(this.logger, "DeltaScheduler"));
|
|
257
|
+
this.scheduler = new ScheduleManagerCore(deltaManager, logger);
|
|
258
|
+
}
|
|
259
|
+
beforeOpProcessing(message) {
|
|
134
260
|
var _a;
|
|
135
261
|
if (this.batchClientId !== message.clientId) {
|
|
136
|
-
|
|
137
|
-
// consider the previous batch over.
|
|
138
|
-
if (this.batchClientId) {
|
|
139
|
-
this.emitter.emit("batchEnd", "Did not receive real batchEnd message", undefined);
|
|
140
|
-
this.deltaScheduler.batchEnd();
|
|
141
|
-
this.logger.sendTelemetryEvent({
|
|
142
|
-
eventName: "BatchEndNotReceived",
|
|
143
|
-
clientId: this.batchClientId,
|
|
144
|
-
sequenceNumber: message.sequenceNumber,
|
|
145
|
-
});
|
|
146
|
-
}
|
|
262
|
+
assert(this.batchClientId === undefined, 0x2a2 /* "Batch is interrupted by other client op. Should be caught by trackPending()" */);
|
|
147
263
|
// This could be the beginning of a new batch or an individual message.
|
|
148
264
|
this.emitter.emit("batchBegin", message);
|
|
149
265
|
this.deltaScheduler.batchBegin();
|
|
@@ -156,79 +272,32 @@ export class ScheduleManager {
|
|
|
156
272
|
}
|
|
157
273
|
}
|
|
158
274
|
}
|
|
159
|
-
|
|
275
|
+
afterOpProcessing(error, message) {
|
|
160
276
|
var _a;
|
|
277
|
+
// If this is no longer true, we need to revisit what we do where we set this.hitError.
|
|
278
|
+
assert(!this.hitError, 0x2a3 /* "container should be closed on any error" */);
|
|
279
|
+
// Let the scheduler know how far we progressed, to decide if op processing
|
|
280
|
+
// should be paused or not.
|
|
281
|
+
this.scheduler.afterOpProcessing(message.sequenceNumber);
|
|
161
282
|
if (error) {
|
|
283
|
+
// We assume here that loader will close container and stop processing all future ops.
|
|
284
|
+
// This is implicit dependency. If this flow changes, this code might no longer be correct.
|
|
285
|
+
this.hitError = true;
|
|
162
286
|
this.batchClientId = undefined;
|
|
163
287
|
this.emitter.emit("batchEnd", error, message);
|
|
164
288
|
this.deltaScheduler.batchEnd();
|
|
165
289
|
return;
|
|
166
290
|
}
|
|
167
|
-
this.updatePauseState(message.sequenceNumber);
|
|
168
291
|
const batch = (_a = message === null || message === void 0 ? void 0 : message.metadata) === null || _a === void 0 ? void 0 : _a.batch;
|
|
169
292
|
// If no batchClientId has been set then we're in an individual batch. Else, if we get
|
|
170
293
|
// batch end metadata, this is end of the current batch.
|
|
171
|
-
if (
|
|
294
|
+
if (this.batchClientId === undefined || batch === false) {
|
|
172
295
|
this.batchClientId = undefined;
|
|
173
296
|
this.emitter.emit("batchEnd", undefined, message);
|
|
174
297
|
this.deltaScheduler.batchEnd();
|
|
175
298
|
return;
|
|
176
299
|
}
|
|
177
300
|
}
|
|
178
|
-
setPaused(localPaused) {
|
|
179
|
-
// Return early if no change in value
|
|
180
|
-
if (this.localPaused === localPaused) {
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
this.localPaused = localPaused;
|
|
184
|
-
if (localPaused) {
|
|
185
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
186
|
-
this.deltaManager.inbound.pause();
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
this.deltaManager.inbound.resume();
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
updatePauseState(sequenceNumber) {
|
|
193
|
-
// If the inbound queue is ever empty we pause it and wait for new events
|
|
194
|
-
if (this.deltaManager.inbound.length === 0) {
|
|
195
|
-
this.setPaused(true);
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
// 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
|
|
199
|
-
// then we simply continue processing
|
|
200
|
-
if (!this.pauseSequenceNumber || sequenceNumber + 1 < this.pauseSequenceNumber) {
|
|
201
|
-
this.setPaused(false);
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
// Otherwise the next message requires us to pause
|
|
205
|
-
this.setPaused(true);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
trackPending(message) {
|
|
209
|
-
const metadata = message.metadata;
|
|
210
|
-
// Protocol messages are never part of a runtime batch of messages
|
|
211
|
-
if (!isRuntimeMessage(message)) {
|
|
212
|
-
this.pauseSequenceNumber = undefined;
|
|
213
|
-
this.pauseClientId = undefined;
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
const batchMetadata = metadata ? metadata.batch : undefined;
|
|
217
|
-
// If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
|
|
218
|
-
if (this.pauseClientId === message.clientId) {
|
|
219
|
-
if (batchMetadata !== undefined) {
|
|
220
|
-
// If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
|
|
221
|
-
// the previous one
|
|
222
|
-
this.pauseSequenceNumber = batchMetadata ? message.sequenceNumber : undefined;
|
|
223
|
-
this.pauseClientId = batchMetadata ? this.pauseClientId : undefined;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
// We check the batch flag for the new clientID - if true we pause otherwise we reset the tracking data
|
|
228
|
-
this.pauseSequenceNumber = batchMetadata ? message.sequenceNumber : undefined;
|
|
229
|
-
this.pauseClientId = batchMetadata ? message.clientId : undefined;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
301
|
}
|
|
233
302
|
/**
|
|
234
303
|
* Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
|
|
@@ -236,6 +305,20 @@ export class ScheduleManager {
|
|
|
236
305
|
* ContainerRuntime's perspective.
|
|
237
306
|
*/
|
|
238
307
|
export const agentSchedulerId = "_scheduler";
|
|
308
|
+
// safely check navigator and get the hardware spec value
|
|
309
|
+
export function getDeviceSpec() {
|
|
310
|
+
try {
|
|
311
|
+
if (typeof navigator === "object" && navigator !== null) {
|
|
312
|
+
return {
|
|
313
|
+
deviceMemory: navigator.deviceMemory,
|
|
314
|
+
hardwareConcurrency: navigator.hardwareConcurrency,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch (_a) {
|
|
319
|
+
}
|
|
320
|
+
return {};
|
|
321
|
+
}
|
|
239
322
|
/**
|
|
240
323
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
241
324
|
* It will define the store level mappings.
|
|
@@ -299,9 +382,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
299
382
|
}
|
|
300
383
|
else {
|
|
301
384
|
// If we're not the summarizer, and we don't have a summaryManager, we expect that
|
|
302
|
-
//
|
|
385
|
+
// disableSummaries is turned on. We are throwing instead of returning a failure here,
|
|
303
386
|
// because it is a misuse of the API rather than an expected failure.
|
|
304
|
-
throw new Error(`Can't summarize,
|
|
387
|
+
throw new Error(`Can't summarize, disableSummaries: ${this.summariesDisabled()}`);
|
|
305
388
|
}
|
|
306
389
|
};
|
|
307
390
|
this.enqueueSummarize = (...args) => {
|
|
@@ -315,18 +398,43 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
315
398
|
// If we're not the summarizer, and we don't have a summaryManager, we expect that
|
|
316
399
|
// generateSummaries is turned off. We are throwing instead of returning a failure here,
|
|
317
400
|
// because it is a misuse of the API rather than an expected failure.
|
|
318
|
-
throw new Error(`Can't summarize,
|
|
401
|
+
throw new Error(`Can't summarize, disableSummaries: ${this.summariesDisabled()}`);
|
|
319
402
|
}
|
|
320
403
|
};
|
|
321
404
|
this.baseSummaryMessage = metadata === null || metadata === void 0 ? void 0 : metadata.message;
|
|
405
|
+
// If this is an existing container, we get values from metadata.
|
|
406
|
+
// otherwise, we initialize them.
|
|
407
|
+
if (existing) {
|
|
408
|
+
this.createContainerMetadata = {
|
|
409
|
+
createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
|
|
410
|
+
createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
|
|
411
|
+
};
|
|
412
|
+
this.summaryCount = metadata === null || metadata === void 0 ? void 0 : metadata.summaryCount;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
this.createContainerMetadata = {
|
|
416
|
+
createContainerRuntimeVersion: pkgVersion,
|
|
417
|
+
createContainerTimestamp: Date.now(),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
322
420
|
// Default to false (enabled).
|
|
323
421
|
this.disableIsolatedChannels = (_a = this.runtimeOptions.summaryOptions.disableIsolatedChannels) !== null && _a !== void 0 ? _a : false;
|
|
324
422
|
this._connected = this.context.connected;
|
|
325
423
|
this.chunkMap = new Map(chunks);
|
|
326
424
|
this.IFluidHandleContext = new ContainerFluidHandleContext("", this);
|
|
327
|
-
this.IFluidSerializer = new FluidSerializer(this.IFluidHandleContext);
|
|
328
425
|
this._logger = ChildLogger.create(this.logger, "ContainerRuntime");
|
|
329
|
-
|
|
426
|
+
/**
|
|
427
|
+
* Function that return the current server timestamp. This is used by the garbage collector to set the
|
|
428
|
+
* time when a node becomes unreferenced.
|
|
429
|
+
* For now, we use the timestamp of the last op for gcTimestamp. However, there can be cases where
|
|
430
|
+
* we don't have an op (on demand summaries for instance). In those cases, we will use the timestamp
|
|
431
|
+
* of this client's connection - https://github.com/microsoft/FluidFramework/issues/8375.
|
|
432
|
+
*/
|
|
433
|
+
const getCurrentTimestamp = () => {
|
|
434
|
+
var _a, _b;
|
|
435
|
+
return (_b = (_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.timestamp) !== null && _b !== void 0 ? _b : Date.now();
|
|
436
|
+
};
|
|
437
|
+
this.garbageCollector = GarbageCollector.create(this, this.runtimeOptions.gcOptions, (unusedRoutes) => this.dataStores.deleteUnusedRoutes(unusedRoutes), getCurrentTimestamp, context.baseSnapshot, async (id) => readAndParse(this.storage, id), this._logger, existing, metadata);
|
|
330
438
|
const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
|
|
331
439
|
this.summarizerNode = createRootSummarizerNodeWithGC(ChildLogger.create(this.logger, "SummarizerNode"),
|
|
332
440
|
// Summarize function to call when summarize is called. Summarizer node always tracks summary state.
|
|
@@ -343,13 +451,11 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
343
451
|
throwOnFailure: true,
|
|
344
452
|
// If GC should not run, let the summarizer node know so that it does not track GC state.
|
|
345
453
|
gcDisabled: !this.garbageCollector.shouldRunGC,
|
|
346
|
-
// The max duration for which objects can be unreferenced before they are eligible for deletion.
|
|
347
|
-
maxUnreferencedDurationMs: this.runtimeOptions.gcOptions.maxUnreferencedDurationMs,
|
|
348
454
|
});
|
|
349
455
|
if (this.context.baseSnapshot) {
|
|
350
456
|
this.summarizerNode.loadBaseSummaryWithoutDifferential(this.context.baseSnapshot);
|
|
351
457
|
}
|
|
352
|
-
this.dataStores = new DataStores(getSummaryForDatastores(context.baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getInitialGCSummaryDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getInitialGCSummaryDetailsFn), (id) => this.summarizerNode.deleteChild(id), this._logger);
|
|
458
|
+
this.dataStores = new DataStores(getSummaryForDatastores(context.baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getInitialGCSummaryDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getInitialGCSummaryDetailsFn), (id) => this.summarizerNode.deleteChild(id), this._logger, async () => this.garbageCollector.getDataStoreBaseGCDetails(), (id) => this.garbageCollector.nodeChanged(id));
|
|
353
459
|
this.blobManager = new BlobManager(this.IFluidHandleContext, blobManagerSnapshot, () => this.storage, (blobId) => this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId }), this, this.logger);
|
|
354
460
|
this.scheduleManager = new ScheduleManager(context.deltaManager, this, ChildLogger.create(this.logger, "ScheduleManager"));
|
|
355
461
|
this.deltaSender = this.deltaManager;
|
|
@@ -357,14 +463,13 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
357
463
|
this.context.quorum.on("removeMember", (clientId) => {
|
|
358
464
|
this.clearPartialChunks(clientId);
|
|
359
465
|
});
|
|
360
|
-
this.context.quorum.on("addProposal", (proposal) => {
|
|
361
|
-
if (proposal.key === "code" || proposal.key === "code2") {
|
|
362
|
-
this.emit("codeDetailsProposed", proposal.value, proposal);
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
466
|
this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
|
|
366
467
|
// Only create a SummaryManager if summaries are enabled and we are not the summarizer client
|
|
468
|
+
// Map the deprecated generateSummaries flag to disableSummaries.
|
|
367
469
|
if (this.runtimeOptions.summaryOptions.generateSummaries === false) {
|
|
470
|
+
this.runtimeOptions.summaryOptions.disableSummaries = true;
|
|
471
|
+
}
|
|
472
|
+
if (this.summariesDisabled()) {
|
|
368
473
|
this._logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
|
|
369
474
|
}
|
|
370
475
|
else {
|
|
@@ -414,7 +519,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
414
519
|
this.deltaManager.on("readonly", (readonly) => {
|
|
415
520
|
// we accumulate ops while being in read-only state.
|
|
416
521
|
// once user gets write permissions and we have active connection, flush all pending ops.
|
|
417
|
-
|
|
522
|
+
// eslint-disable-next-line max-len
|
|
523
|
+
assert(readonly === this.deltaManager.readOnlyInfo.readonly, 0x124 /* "inconsistent readonly property/event state" */);
|
|
418
524
|
// We need to be very careful with when we (re)send pending ops, to ensure that we only send ops
|
|
419
525
|
// when we either never send an op, or attempted to send it but we know for sure it was not
|
|
420
526
|
// sequenced by server and will never be sequenced (i.e. was lost)
|
|
@@ -433,6 +539,10 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
433
539
|
if (context.pendingLocalState !== undefined) {
|
|
434
540
|
this.deltaManager.on("op", this.onOp);
|
|
435
541
|
}
|
|
542
|
+
// logging hardware telemetry
|
|
543
|
+
logger.sendTelemetryEvent(Object.assign({ eventName: "DeviceSpec" }, getDeviceSpec()));
|
|
544
|
+
// logging container load stats
|
|
545
|
+
this.logger.sendTelemetryEvent(Object.assign(Object.assign(Object.assign({ eventName: "ContainerLoadStats" }, this.createContainerMetadata), this.dataStores.containerLoadStats), { summaryCount: this.summaryCount, summaryFormatVersion: metadata === null || metadata === void 0 ? void 0 : metadata.summaryFormatVersion, disableIsolatedChannels: metadata === null || metadata === void 0 ? void 0 : metadata.disableIsolatedChannels, gcVersion: metadata === null || metadata === void 0 ? void 0 : metadata.gcFeature }));
|
|
436
546
|
ReportOpPerfTelemetry(this.context.clientId, this.deltaManager, this.logger);
|
|
437
547
|
}
|
|
438
548
|
get IContainerRuntime() { return this; }
|
|
@@ -454,7 +564,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
454
564
|
runtimeVersion: pkgVersion,
|
|
455
565
|
},
|
|
456
566
|
});
|
|
457
|
-
const { summaryOptions = {
|
|
567
|
+
const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", } = runtimeOptions;
|
|
458
568
|
// We pack at data store level only. If isolated channels are disabled,
|
|
459
569
|
// then there are no .channel layers, we pack at level 1, otherwise we pack at level 2
|
|
460
570
|
const packingLevel = summaryOptions.disableIsolatedChannels ? 1 : 2;
|
|
@@ -505,7 +615,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
505
615
|
const protocolSequenceNumber = context.deltaManager.initialSequenceNumber;
|
|
506
616
|
// Unless bypass is explicitly set, then take action when sequence numbers mismatch.
|
|
507
617
|
if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
|
|
508
|
-
|
|
618
|
+
// "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
|
|
619
|
+
const error = new DataCorruptionError("SummaryMetadataMismatch", { runtimeSequenceNumber, protocolSequenceNumber });
|
|
509
620
|
if (loadSequenceNumberVerification === "log") {
|
|
510
621
|
logger.sendErrorEvent({ eventName: "SequenceNumberMismatch" }, error);
|
|
511
622
|
}
|
|
@@ -584,6 +695,13 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
584
695
|
return Object.assign(Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_b = (_a = this.context) === null || _a === void 0 ? void 0 : _a.serviceConfiguration) === null || _b === void 0 ? void 0 : _b.summary), (_c = this.runtimeOptions.summaryOptions) === null || _c === void 0 ? void 0 : _c.summaryConfigOverrides);
|
|
585
696
|
}
|
|
586
697
|
get disposed() { return this._disposed; }
|
|
698
|
+
/**
|
|
699
|
+
* True, if GC data should be written at root of the summary tree.
|
|
700
|
+
* False, if data stores should write GC blobs in their summary tree.
|
|
701
|
+
*/
|
|
702
|
+
get writeGCDataAtRoot() {
|
|
703
|
+
return this.garbageCollector.writeDataAtRoot;
|
|
704
|
+
}
|
|
587
705
|
static get defaultFlushMode() {
|
|
588
706
|
return getLocalStorageFeatureGate(turnBasedFlushModeKey) ? FlushMode.TurnBased : FlushMode.Immediate;
|
|
589
707
|
}
|
|
@@ -704,14 +822,10 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
704
822
|
}
|
|
705
823
|
formMetadata() {
|
|
706
824
|
var _a;
|
|
707
|
-
return {
|
|
708
|
-
summaryFormatVersion: 1,
|
|
709
|
-
disableIsolatedChannels: this.disableIsolatedChannels || undefined,
|
|
710
|
-
gcFeature: this.garbageCollector.gcSummaryFeatureVersion,
|
|
825
|
+
return Object.assign(Object.assign({}, this.createContainerMetadata), { summaryCount: this.summaryCount, summaryFormatVersion: 1, disableIsolatedChannels: this.disableIsolatedChannels || undefined, gcFeature: this.garbageCollector.gcSummaryFeatureVersion,
|
|
711
826
|
// The last message processed at the time of summary. If there are no messages, nothing has changed from
|
|
712
827
|
// the base summary we loaded from. So, use the message from its metadata blob.
|
|
713
|
-
message: (_a = extractSummaryMetadataMessage(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.baseSummaryMessage
|
|
714
|
-
};
|
|
828
|
+
message: (_a = extractSummaryMetadataMessage(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.baseSummaryMessage });
|
|
715
829
|
}
|
|
716
830
|
/**
|
|
717
831
|
* Retrieves the runtime for a data store if it's referenced as per the initially summary that it is loaded with.
|
|
@@ -738,22 +852,14 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
738
852
|
* @deprecated - Use summarize to get summary of the container runtime.
|
|
739
853
|
*/
|
|
740
854
|
async snapshot() {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
else {
|
|
750
|
-
root.entries.push(new TreeTreeEntry(channelsTreeName, { entries }));
|
|
751
|
-
}
|
|
752
|
-
root.entries.push(new BlobTreeEntry(metadataBlobName, JSON.stringify(this.formMetadata())));
|
|
753
|
-
if (this.chunkMap.size > 0) {
|
|
754
|
-
root.entries.push(new BlobTreeEntry(chunksBlobName, JSON.stringify([...this.chunkMap])));
|
|
755
|
-
}
|
|
756
|
-
return root;
|
|
855
|
+
const summaryResult = await this.summarize({
|
|
856
|
+
summaryLogger: this.logger,
|
|
857
|
+
fullTree: true,
|
|
858
|
+
trackState: false,
|
|
859
|
+
runGC: this.garbageCollector.shouldRunGC,
|
|
860
|
+
fullGC: true,
|
|
861
|
+
});
|
|
862
|
+
return convertSummaryTreeToITree(summaryResult.summary);
|
|
757
863
|
}
|
|
758
864
|
addContainerBlobsToSummary(summaryTree) {
|
|
759
865
|
var _a;
|
|
@@ -773,6 +879,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
773
879
|
const blobsTree = convertToSummaryTree(snapshot, false);
|
|
774
880
|
addTreeToSummary(summaryTree, blobsTreeName, blobsTree);
|
|
775
881
|
}
|
|
882
|
+
if (this.writeGCDataAtRoot) {
|
|
883
|
+
const gcSummary = this.garbageCollector.summarize();
|
|
884
|
+
if (gcSummary !== undefined) {
|
|
885
|
+
addTreeToSummary(summaryTree, gcTreeKey, gcSummary);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
776
888
|
}
|
|
777
889
|
replayPendingStates() {
|
|
778
890
|
// We need to be able to send ops to replay states
|
|
@@ -842,11 +954,10 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
842
954
|
// We do not need to make deep copy, as each layer will just replace message.content itself,
|
|
843
955
|
// but would not modify contents details
|
|
844
956
|
let message = Object.assign({}, messageArg);
|
|
845
|
-
let error;
|
|
846
957
|
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
847
958
|
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
848
959
|
// messages once a batch has been fully processed.
|
|
849
|
-
this.scheduleManager.
|
|
960
|
+
this.scheduleManager.beforeOpProcessing(message);
|
|
850
961
|
try {
|
|
851
962
|
message = unpackRuntimeMessage(message);
|
|
852
963
|
// Chunk processing must come first given that we will transform the message to the unchunked version
|
|
@@ -874,14 +985,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
874
985
|
default:
|
|
875
986
|
}
|
|
876
987
|
this.emit("op", message);
|
|
988
|
+
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
877
989
|
}
|
|
878
990
|
catch (e) {
|
|
879
|
-
|
|
991
|
+
this.scheduleManager.afterOpProcessing(e, message);
|
|
880
992
|
throw e;
|
|
881
993
|
}
|
|
882
|
-
finally {
|
|
883
|
-
this.scheduleManager.endOperation(error, message);
|
|
884
|
-
}
|
|
885
994
|
}
|
|
886
995
|
processSignal(message, local) {
|
|
887
996
|
const envelope = message.content;
|
|
@@ -1093,7 +1202,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1093
1202
|
await this.collectGarbage({ logger: summaryLogger, runSweep, fullGC });
|
|
1094
1203
|
}
|
|
1095
1204
|
const summarizeResult = await this.summarizerNode.summarize(fullTree, trackState);
|
|
1096
|
-
assert(summarizeResult.summary.type ===
|
|
1205
|
+
assert(summarizeResult.summary.type === SummaryType.Tree, 0x12f /* "Container Runtime's summarize should always return a tree" */);
|
|
1097
1206
|
return summarizeResult;
|
|
1098
1207
|
}
|
|
1099
1208
|
/**
|
|
@@ -1108,19 +1217,16 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1108
1217
|
* Implementation of IGarbageCollectionRuntime::updateUsedRoutes.
|
|
1109
1218
|
* After GC has run, called to notify this container's nodes of routes that are used in it.
|
|
1110
1219
|
* @param usedRoutes - The routes that are used in all nodes in this Container.
|
|
1220
|
+
* @param gcTimestamp - The time when GC was run that generated these used routes. If any node node becomes
|
|
1221
|
+
* unreferenced as part of this GC run, this should be used to update the time when it happens.
|
|
1111
1222
|
* @returns the statistics of the used state of the data stores.
|
|
1112
1223
|
*/
|
|
1113
|
-
updateUsedRoutes(usedRoutes) {
|
|
1114
|
-
var _a;
|
|
1224
|
+
updateUsedRoutes(usedRoutes, gcTimestamp) {
|
|
1115
1225
|
// Update our summarizer node's used routes. Updating used routes in summarizer node before
|
|
1116
1226
|
// summarizing is required and asserted by the the summarizer node. We are the root and are
|
|
1117
1227
|
// always referenced, so the used routes is only self-route (empty string).
|
|
1118
1228
|
this.summarizerNode.updateUsedRoutes([""]);
|
|
1119
|
-
return this.dataStores.updateUsedRoutes(usedRoutes,
|
|
1120
|
-
// For now, we use the timestamp of the last op for gcTimestamp. However, there can be cases where
|
|
1121
|
-
// we don't have an op (on demand summaries for instance). In those cases, we will use the timestamp
|
|
1122
|
-
// of this client's connection - https://github.com/microsoft/FluidFramework/issues/7152.
|
|
1123
|
-
this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.timestamp);
|
|
1229
|
+
return this.dataStores.updateUsedRoutes(usedRoutes, gcTimestamp);
|
|
1124
1230
|
}
|
|
1125
1231
|
/**
|
|
1126
1232
|
* Runs garbage collection and udpates the reference / used state of the nodes in the container.
|
|
@@ -1188,6 +1294,13 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1188
1294
|
if (!continueResult.continue) {
|
|
1189
1295
|
return { stage: "base", referenceSequenceNumber: summaryRefSeqNum, error: continueResult.error };
|
|
1190
1296
|
}
|
|
1297
|
+
// increment summary count
|
|
1298
|
+
if (this.summaryCount !== undefined) {
|
|
1299
|
+
this.summaryCount++;
|
|
1300
|
+
}
|
|
1301
|
+
else {
|
|
1302
|
+
this.summaryCount = 1;
|
|
1303
|
+
}
|
|
1191
1304
|
const trace = Trace.start();
|
|
1192
1305
|
let summarizeResult;
|
|
1193
1306
|
try {
|
|
@@ -1209,8 +1322,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1209
1322
|
// Because handles are unchanged dataStores in the current logic,
|
|
1210
1323
|
// summarized dataStore count is total dataStore count minus handle count
|
|
1211
1324
|
const dataStoreTree = this.disableIsolatedChannels ? summaryTree : summaryTree.tree[channelsTreeName];
|
|
1212
|
-
assert(dataStoreTree.type ===
|
|
1213
|
-
const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type ===
|
|
1325
|
+
assert(dataStoreTree.type === SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
|
|
1326
|
+
const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === SummaryType.Handle).length;
|
|
1214
1327
|
const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount }, partialStats);
|
|
1215
1328
|
const generateSummaryData = {
|
|
1216
1329
|
referenceSequenceNumber: summaryRefSeqNum,
|
|
@@ -1487,6 +1600,14 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1487
1600
|
getPendingLocalState() {
|
|
1488
1601
|
return this.pendingStateManager.getLocalState();
|
|
1489
1602
|
}
|
|
1603
|
+
/**
|
|
1604
|
+
* @returns true if summaries are explicitly disabled for this ContainerRuntime, false otherwise
|
|
1605
|
+
*/
|
|
1606
|
+
summariesDisabled() {
|
|
1607
|
+
var _a;
|
|
1608
|
+
return this.runtimeOptions.summaryOptions.disableSummaries === true ||
|
|
1609
|
+
((_a = this.runtimeOptions.summaryOptions.summaryConfigOverrides) === null || _a === void 0 ? void 0 : _a.disableSummaries) === true;
|
|
1610
|
+
}
|
|
1490
1611
|
}
|
|
1491
1612
|
/**
|
|
1492
1613
|
* Wait for a specific sequence number. Promise should resolve when we reach that number,
|