@fluidframework/container-runtime 0.51.0 → 0.52.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 +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/containerRuntime.d.ts +23 -11
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +173 -93
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +7 -7
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +1 -1
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.js +2 -1
- package/dist/dataStores.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/summarizerTypes.d.ts +13 -2
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js +3 -0
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryFormat.js +2 -1
- package/dist/summaryFormat.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/containerRuntime.d.ts +23 -11
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +177 -97
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +7 -7
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +1 -1
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.js +2 -1
- package/lib/dataStores.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/summarizerTypes.d.ts +13 -2
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js +3 -0
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryFormat.js +2 -1
- package/lib/summaryFormat.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/containerRuntime.ts +220 -110
- package/src/dataStoreContext.ts +7 -7
- package/src/packageVersion.ts +1 -1
- package/src/summarizerTypes.ts +16 -3
- package/src/summaryManager.ts +8 -3
package/lib/containerRuntime.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
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";
|
|
9
|
+
import { DataCorruptionError, GenericError, extractSafePropertiesFromMessage } from "@fluidframework/container-utils";
|
|
10
10
|
import { BlobTreeEntry, TreeTreeEntry, } from "@fluidframework/protocol-base";
|
|
11
|
-
import { MessageType, } from "@fluidframework/protocol-definitions";
|
|
11
|
+
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
12
12
|
import { FlushMode, channelsTreeName, } from "@fluidframework/runtime-definitions";
|
|
13
13
|
import { addBlobToSummary, addTreeToSummary, convertToSummaryTree, createRootSummarizerNodeWithGC, FluidSerializer, RequestParser, create404Response, exceptionToResponse, responseToException, seqFromTree, } from "@fluidframework/runtime-utils";
|
|
14
14
|
import { v4 as uuid } from "uuid";
|
|
@@ -17,7 +17,7 @@ import { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
|
17
17
|
import { Summarizer } from "./summarizer";
|
|
18
18
|
import { formRequestSummarizerFn, SummaryManager } from "./summaryManager";
|
|
19
19
|
import { DeltaScheduler } from "./deltaScheduler";
|
|
20
|
-
import { ReportOpPerfTelemetry } from "./connectionTelemetry";
|
|
20
|
+
import { ReportOpPerfTelemetry, latencyThreshold } from "./connectionTelemetry";
|
|
21
21
|
import { PendingStateManager } from "./pendingStateManager";
|
|
22
22
|
import { pkgVersion } from "./packageVersion";
|
|
23
23
|
import { BlobManager } from "./blobManager";
|
|
@@ -92,13 +92,16 @@ export function unpackRuntimeMessage(message) {
|
|
|
92
92
|
}
|
|
93
93
|
return message;
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
/**
|
|
96
|
+
* This class controls pausing and resuming of inbound queue to ensure that we never
|
|
97
|
+
* start processing ops in a batch IF we do not have all ops in the batch.
|
|
98
|
+
*/
|
|
99
|
+
class ScheduleManagerCore {
|
|
100
|
+
constructor(deltaManager, logger) {
|
|
97
101
|
this.deltaManager = deltaManager;
|
|
98
|
-
this.emitter = emitter;
|
|
99
102
|
this.logger = logger;
|
|
100
103
|
this.localPaused = false;
|
|
101
|
-
this.
|
|
104
|
+
this.timePaused = 0;
|
|
102
105
|
// Listen for delta manager sends and add batch metadata to messages
|
|
103
106
|
this.deltaManager.on("prepareSend", (messages) => {
|
|
104
107
|
if (messages.length === 0) {
|
|
@@ -106,7 +109,7 @@ export class ScheduleManager {
|
|
|
106
109
|
}
|
|
107
110
|
// First message will have the batch flag set to true if doing a batched send
|
|
108
111
|
const firstMessageMetadata = messages[0].metadata;
|
|
109
|
-
if (!firstMessageMetadata ||
|
|
112
|
+
if (!(firstMessageMetadata === null || firstMessageMetadata === void 0 ? void 0 : firstMessageMetadata.batch)) {
|
|
110
113
|
return;
|
|
111
114
|
}
|
|
112
115
|
// If the batch contains only a single op, clear the batch flag.
|
|
@@ -121,29 +124,143 @@ export class ScheduleManager {
|
|
|
121
124
|
// Listen for updates and peek at the inbound
|
|
122
125
|
this.deltaManager.inbound.on("push", (message) => {
|
|
123
126
|
this.trackPending(message);
|
|
124
|
-
this.updatePauseState(message.sequenceNumber);
|
|
125
127
|
});
|
|
128
|
+
// Start with baseline - empty inbound queue.
|
|
129
|
+
assert(!this.localPaused, 0x293 /* "initial state" */);
|
|
126
130
|
const allPending = this.deltaManager.inbound.toArray();
|
|
127
131
|
for (const pending of allPending) {
|
|
128
132
|
this.trackPending(pending);
|
|
129
133
|
}
|
|
130
|
-
// Based on track pending update the pause state
|
|
131
|
-
this.updatePauseState(this.deltaManager.lastSequenceNumber);
|
|
132
134
|
}
|
|
133
|
-
|
|
135
|
+
/**
|
|
136
|
+
* The only public function in this class - called when we processed an op,
|
|
137
|
+
* to make decision if op processing should be paused or not afer that.
|
|
138
|
+
*/
|
|
139
|
+
afterOpProcessing(sequenceNumber) {
|
|
140
|
+
assert(!this.localPaused, 0x294 /* "can't have op processing paused if we are processing an op" */);
|
|
141
|
+
// If the inbound queue is ever empty, nothing to do!
|
|
142
|
+
if (this.deltaManager.inbound.length === 0) {
|
|
143
|
+
assert(this.pauseSequenceNumber === undefined, 0x295 /* "there should be no pending batch if we have no ops" */);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// The queue is
|
|
147
|
+
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
148
|
+
// - here (processing ops until reaching start of incomplete batch)
|
|
149
|
+
// - in trackPending(), when queue was empty and start of batch showed up.
|
|
150
|
+
// 2. resumed when batch end comes in (in trackPending())
|
|
151
|
+
// do we have incomplete batch to worry about?
|
|
152
|
+
if (this.pauseSequenceNumber !== undefined) {
|
|
153
|
+
assert(sequenceNumber < this.pauseSequenceNumber, 0x296 /* "we should never start processing incomplete batch!" */);
|
|
154
|
+
// If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
|
|
155
|
+
if (sequenceNumber + 1 === this.pauseSequenceNumber) {
|
|
156
|
+
this.pauseQueue();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
pauseQueue() {
|
|
161
|
+
assert(!this.localPaused, 0x297 /* "always called from resumed state" */);
|
|
162
|
+
this.localPaused = true;
|
|
163
|
+
this.timePaused = performance.now();
|
|
164
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
165
|
+
this.deltaManager.inbound.pause();
|
|
166
|
+
}
|
|
167
|
+
resumeQueue(startBatch, endBatch) {
|
|
168
|
+
// Return early if no change in value
|
|
169
|
+
if (!this.localPaused) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this.localPaused = false;
|
|
173
|
+
const duration = performance.now() - this.timePaused;
|
|
174
|
+
// Random round number - we want to know when batch waiting paused op processing.
|
|
175
|
+
if (duration > latencyThreshold) {
|
|
176
|
+
this.logger.sendErrorEvent({
|
|
177
|
+
eventName: "MaxBatchWaitTimeExceeded",
|
|
178
|
+
duration,
|
|
179
|
+
sequenceNumber: endBatch,
|
|
180
|
+
length: endBatch - startBatch,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
this.deltaManager.inbound.resume();
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Called for each incoming op (i.e. inbound "push" notification)
|
|
187
|
+
*/
|
|
188
|
+
trackPending(message) {
|
|
189
|
+
assert(this.deltaManager.inbound.length !== 0, 0x298 /* "we have something in the queue that generates this event" */);
|
|
190
|
+
assert((this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined), 0x299 /* "non-synchronized state" */);
|
|
191
|
+
const metadata = message.metadata;
|
|
192
|
+
const batchMetadata = metadata === null || metadata === void 0 ? void 0 : metadata.batch;
|
|
193
|
+
// Protocol messages are never part of a runtime batch of messages
|
|
194
|
+
if (!isRuntimeMessage(message)) {
|
|
195
|
+
// Protocol messages should never show up in the middle of the batch!
|
|
196
|
+
assert(this.currentBatchClientId === undefined, 0x29a /* "System message in the middle of batch!" */);
|
|
197
|
+
assert(batchMetadata === undefined, 0x29b /* "system op in a batch?" */);
|
|
198
|
+
assert(!this.localPaused, 0x29c /* "we should be processing ops when there is no active batch" */);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (this.currentBatchClientId === undefined && batchMetadata === undefined) {
|
|
202
|
+
assert(!this.localPaused, 0x29d /* "we should be processing ops when there is no active batch" */);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
|
|
206
|
+
// If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
|
|
207
|
+
// the previous one
|
|
208
|
+
if (this.currentBatchClientId !== undefined || batchMetadata === false) {
|
|
209
|
+
if (this.currentBatchClientId !== message.clientId) {
|
|
210
|
+
// "Batch not closed, yet message from another client!"
|
|
211
|
+
throw new DataCorruptionError("OpBatchIncomplete", Object.assign({ batchClientId: this.currentBatchClientId }, extractSafePropertiesFromMessage(message)));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// The queue is
|
|
215
|
+
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
216
|
+
// - in afterOpProcessing() - processing ops until reaching start of incomplete batch
|
|
217
|
+
// - here (batchMetadata == false below), when queue was empty and start of batch showed up.
|
|
218
|
+
// 2. resumed when batch end comes in (batchMetadata === true case below)
|
|
219
|
+
if (batchMetadata) {
|
|
220
|
+
assert(this.currentBatchClientId === undefined, 0x29e /* "there can't be active batch" */);
|
|
221
|
+
assert(!this.localPaused, 0x29f /* "we should be processing ops when there is no active batch" */);
|
|
222
|
+
this.pauseSequenceNumber = message.sequenceNumber;
|
|
223
|
+
this.currentBatchClientId = message.clientId;
|
|
224
|
+
// Start of the batch
|
|
225
|
+
// Only pause processing if queue has no other ops!
|
|
226
|
+
// If there are any other ops in the queue, processing will be stopped when they are processed!
|
|
227
|
+
if (this.deltaManager.inbound.length === 1) {
|
|
228
|
+
this.pauseQueue();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else if (batchMetadata === false) {
|
|
232
|
+
assert(this.pauseSequenceNumber !== undefined, 0x2a0 /* "batch presence was validated above" */);
|
|
233
|
+
// Batch is complete, we can process it!
|
|
234
|
+
this.resumeQueue(this.pauseSequenceNumber, message.sequenceNumber);
|
|
235
|
+
this.pauseSequenceNumber = undefined;
|
|
236
|
+
this.currentBatchClientId = undefined;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// Continuation of current batch. Do nothing
|
|
240
|
+
assert(this.currentBatchClientId !== undefined, 0x2a1 /* "logic error" */);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* This class has the following responsibilities:
|
|
246
|
+
* 1. It tracks batches as we process ops and raises "batchBegin" and "batchEnd" events.
|
|
247
|
+
* As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)
|
|
248
|
+
* 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch
|
|
249
|
+
* unless all ops of the batch are in.
|
|
250
|
+
*/
|
|
251
|
+
export class ScheduleManager {
|
|
252
|
+
constructor(deltaManager, emitter, logger) {
|
|
253
|
+
this.deltaManager = deltaManager;
|
|
254
|
+
this.emitter = emitter;
|
|
255
|
+
this.logger = logger;
|
|
256
|
+
this.hitError = false;
|
|
257
|
+
this.deltaScheduler = new DeltaScheduler(this.deltaManager, ChildLogger.create(this.logger, "DeltaScheduler"));
|
|
258
|
+
this.scheduler = new ScheduleManagerCore(deltaManager, logger);
|
|
259
|
+
}
|
|
260
|
+
beforeOpProcessing(message) {
|
|
134
261
|
var _a;
|
|
135
262
|
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
|
-
}
|
|
263
|
+
assert(this.batchClientId === undefined, 0x2a2 /* "Batch is interrupted by other client op. Should be caught by trackPending()" */);
|
|
147
264
|
// This could be the beginning of a new batch or an individual message.
|
|
148
265
|
this.emitter.emit("batchBegin", message);
|
|
149
266
|
this.deltaScheduler.batchBegin();
|
|
@@ -156,79 +273,32 @@ export class ScheduleManager {
|
|
|
156
273
|
}
|
|
157
274
|
}
|
|
158
275
|
}
|
|
159
|
-
|
|
276
|
+
afterOpProcessing(error, message) {
|
|
160
277
|
var _a;
|
|
278
|
+
// If this is no longer true, we need to revisit what we do where we set this.hitError.
|
|
279
|
+
assert(!this.hitError, 0x2a3 /* "container should be closed on any error" */);
|
|
280
|
+
// Let the scheduler know how far we progressed, to decide if op processing
|
|
281
|
+
// should be paused or not.
|
|
282
|
+
this.scheduler.afterOpProcessing(message.sequenceNumber);
|
|
161
283
|
if (error) {
|
|
284
|
+
// We assume here that loader will close container and stop processing all future ops.
|
|
285
|
+
// This is implicit dependency. If this flow changes, this code might no longer be correct.
|
|
286
|
+
this.hitError = true;
|
|
162
287
|
this.batchClientId = undefined;
|
|
163
288
|
this.emitter.emit("batchEnd", error, message);
|
|
164
289
|
this.deltaScheduler.batchEnd();
|
|
165
290
|
return;
|
|
166
291
|
}
|
|
167
|
-
this.updatePauseState(message.sequenceNumber);
|
|
168
292
|
const batch = (_a = message === null || message === void 0 ? void 0 : message.metadata) === null || _a === void 0 ? void 0 : _a.batch;
|
|
169
293
|
// If no batchClientId has been set then we're in an individual batch. Else, if we get
|
|
170
294
|
// batch end metadata, this is end of the current batch.
|
|
171
|
-
if (
|
|
295
|
+
if (this.batchClientId === undefined || batch === false) {
|
|
172
296
|
this.batchClientId = undefined;
|
|
173
297
|
this.emitter.emit("batchEnd", undefined, message);
|
|
174
298
|
this.deltaScheduler.batchEnd();
|
|
175
299
|
return;
|
|
176
300
|
}
|
|
177
301
|
}
|
|
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
302
|
}
|
|
233
303
|
/**
|
|
234
304
|
* Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
|
|
@@ -299,9 +369,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
299
369
|
}
|
|
300
370
|
else {
|
|
301
371
|
// If we're not the summarizer, and we don't have a summaryManager, we expect that
|
|
302
|
-
//
|
|
372
|
+
// disableSummaries is turned on. We are throwing instead of returning a failure here,
|
|
303
373
|
// because it is a misuse of the API rather than an expected failure.
|
|
304
|
-
throw new Error(`Can't summarize,
|
|
374
|
+
throw new Error(`Can't summarize, disableSummaries: ${this.summariesDisabled()}`);
|
|
305
375
|
}
|
|
306
376
|
};
|
|
307
377
|
this.enqueueSummarize = (...args) => {
|
|
@@ -315,7 +385,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
315
385
|
// If we're not the summarizer, and we don't have a summaryManager, we expect that
|
|
316
386
|
// generateSummaries is turned off. We are throwing instead of returning a failure here,
|
|
317
387
|
// because it is a misuse of the API rather than an expected failure.
|
|
318
|
-
throw new Error(`Can't summarize,
|
|
388
|
+
throw new Error(`Can't summarize, disableSummaries: ${this.summariesDisabled()}`);
|
|
319
389
|
}
|
|
320
390
|
};
|
|
321
391
|
this.baseSummaryMessage = metadata === null || metadata === void 0 ? void 0 : metadata.message;
|
|
@@ -364,7 +434,11 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
364
434
|
});
|
|
365
435
|
this.summaryCollection = new SummaryCollection(this.deltaManager, this.logger);
|
|
366
436
|
// Only create a SummaryManager if summaries are enabled and we are not the summarizer client
|
|
437
|
+
// Map the deprecated generateSummaries flag to disableSummaries.
|
|
367
438
|
if (this.runtimeOptions.summaryOptions.generateSummaries === false) {
|
|
439
|
+
this.runtimeOptions.summaryOptions.disableSummaries = true;
|
|
440
|
+
}
|
|
441
|
+
if (this.summariesDisabled()) {
|
|
368
442
|
this._logger.sendTelemetryEvent({ eventName: "SummariesDisabled" });
|
|
369
443
|
}
|
|
370
444
|
else {
|
|
@@ -454,7 +528,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
454
528
|
runtimeVersion: pkgVersion,
|
|
455
529
|
},
|
|
456
530
|
});
|
|
457
|
-
const { summaryOptions = {
|
|
531
|
+
const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", } = runtimeOptions;
|
|
458
532
|
// We pack at data store level only. If isolated channels are disabled,
|
|
459
533
|
// then there are no .channel layers, we pack at level 1, otherwise we pack at level 2
|
|
460
534
|
const packingLevel = summaryOptions.disableIsolatedChannels ? 1 : 2;
|
|
@@ -505,7 +579,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
505
579
|
const protocolSequenceNumber = context.deltaManager.initialSequenceNumber;
|
|
506
580
|
// Unless bypass is explicitly set, then take action when sequence numbers mismatch.
|
|
507
581
|
if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
|
|
508
|
-
|
|
582
|
+
// "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
|
|
583
|
+
const error = new DataCorruptionError("SummaryMetadataMismatch", { runtimeSequenceNumber, protocolSequenceNumber });
|
|
509
584
|
if (loadSequenceNumberVerification === "log") {
|
|
510
585
|
logger.sendErrorEvent({ eventName: "SequenceNumberMismatch" }, error);
|
|
511
586
|
}
|
|
@@ -842,11 +917,10 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
842
917
|
// We do not need to make deep copy, as each layer will just replace message.content itself,
|
|
843
918
|
// but would not modify contents details
|
|
844
919
|
let message = Object.assign({}, messageArg);
|
|
845
|
-
let error;
|
|
846
920
|
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
847
921
|
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
848
922
|
// messages once a batch has been fully processed.
|
|
849
|
-
this.scheduleManager.
|
|
923
|
+
this.scheduleManager.beforeOpProcessing(message);
|
|
850
924
|
try {
|
|
851
925
|
message = unpackRuntimeMessage(message);
|
|
852
926
|
// Chunk processing must come first given that we will transform the message to the unchunked version
|
|
@@ -874,14 +948,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
874
948
|
default:
|
|
875
949
|
}
|
|
876
950
|
this.emit("op", message);
|
|
951
|
+
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
877
952
|
}
|
|
878
953
|
catch (e) {
|
|
879
|
-
|
|
954
|
+
this.scheduleManager.afterOpProcessing(e, message);
|
|
880
955
|
throw e;
|
|
881
956
|
}
|
|
882
|
-
finally {
|
|
883
|
-
this.scheduleManager.endOperation(error, message);
|
|
884
|
-
}
|
|
885
957
|
}
|
|
886
958
|
processSignal(message, local) {
|
|
887
959
|
const envelope = message.content;
|
|
@@ -968,7 +1040,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
968
1040
|
}
|
|
969
1041
|
async createRootDataStore(pkg, rootDataStoreId) {
|
|
970
1042
|
const fluidDataStore = await this._createDataStore(pkg, true /* isRoot */, rootDataStoreId);
|
|
971
|
-
fluidDataStore.
|
|
1043
|
+
fluidDataStore.bindToContext();
|
|
972
1044
|
return fluidDataStore;
|
|
973
1045
|
}
|
|
974
1046
|
createDetachedRootDataStore(pkg, rootDataStoreId) {
|
|
@@ -980,7 +1052,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
980
1052
|
async _createDataStoreWithProps(pkg, props, id = uuid(), isRoot = false) {
|
|
981
1053
|
const fluidDataStore = await this.dataStores._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
|
|
982
1054
|
if (isRoot) {
|
|
983
|
-
fluidDataStore.
|
|
1055
|
+
fluidDataStore.bindToContext();
|
|
984
1056
|
}
|
|
985
1057
|
return fluidDataStore;
|
|
986
1058
|
}
|
|
@@ -1093,7 +1165,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1093
1165
|
await this.collectGarbage({ logger: summaryLogger, runSweep, fullGC });
|
|
1094
1166
|
}
|
|
1095
1167
|
const summarizeResult = await this.summarizerNode.summarize(fullTree, trackState);
|
|
1096
|
-
assert(summarizeResult.summary.type ===
|
|
1168
|
+
assert(summarizeResult.summary.type === SummaryType.Tree, 0x12f /* "Container Runtime's summarize should always return a tree" */);
|
|
1097
1169
|
return summarizeResult;
|
|
1098
1170
|
}
|
|
1099
1171
|
/**
|
|
@@ -1209,8 +1281,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1209
1281
|
// Because handles are unchanged dataStores in the current logic,
|
|
1210
1282
|
// summarized dataStore count is total dataStore count minus handle count
|
|
1211
1283
|
const dataStoreTree = this.disableIsolatedChannels ? summaryTree : summaryTree.tree[channelsTreeName];
|
|
1212
|
-
assert(dataStoreTree.type ===
|
|
1213
|
-
const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type ===
|
|
1284
|
+
assert(dataStoreTree.type === SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
|
|
1285
|
+
const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === SummaryType.Handle).length;
|
|
1214
1286
|
const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount }, partialStats);
|
|
1215
1287
|
const generateSummaryData = {
|
|
1216
1288
|
referenceSequenceNumber: summaryRefSeqNum,
|
|
@@ -1487,6 +1559,14 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1487
1559
|
getPendingLocalState() {
|
|
1488
1560
|
return this.pendingStateManager.getLocalState();
|
|
1489
1561
|
}
|
|
1562
|
+
/**
|
|
1563
|
+
* @returns true if summaries are explicitly disabled for this ContainerRuntime, false otherwise
|
|
1564
|
+
*/
|
|
1565
|
+
summariesDisabled() {
|
|
1566
|
+
var _a;
|
|
1567
|
+
return this.runtimeOptions.summaryOptions.disableSummaries === true ||
|
|
1568
|
+
((_a = this.runtimeOptions.summaryOptions.summaryConfigOverrides) === null || _a === void 0 ? void 0 : _a.disableSummaries) === true;
|
|
1569
|
+
}
|
|
1490
1570
|
}
|
|
1491
1571
|
/**
|
|
1492
1572
|
* Wait for a specific sequence number. Promise should resolve when we reach that number,
|