@fluidframework/container-runtime 2.0.0-internal.1.0.0.84253 → 2.0.0-internal.1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mocharc.js +12 -0
- package/dist/blobManager.d.ts +7 -1
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +18 -1
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +2 -66
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +37 -295
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +3 -5
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +13 -23
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +1 -6
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +37 -6
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +61 -65
- package/dist/garbageCollection.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -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/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +15 -2
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/scheduleManager.d.ts +28 -0
- package/dist/scheduleManager.d.ts.map +1 -0
- package/dist/scheduleManager.js +235 -0
- package/dist/scheduleManager.js.map +1 -0
- package/dist/summaryCollection.js +1 -1
- package/dist/summaryCollection.js.map +1 -1
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +20 -5
- package/dist/summaryManager.js.map +1 -1
- package/lib/blobManager.d.ts +7 -1
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +19 -2
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +2 -66
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +38 -295
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +3 -5
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +13 -23
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +1 -6
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +37 -6
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +47 -52
- package/lib/garbageCollection.js.map +1 -1
- package/lib/index.d.ts +2 -1
- 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/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +15 -2
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/scheduleManager.d.ts +28 -0
- package/lib/scheduleManager.d.ts.map +1 -0
- package/lib/scheduleManager.js +231 -0
- package/lib/scheduleManager.js.map +1 -0
- package/lib/summaryCollection.js +1 -1
- package/lib/summaryCollection.js.map +1 -1
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +20 -5
- package/lib/summaryManager.js.map +1 -1
- package/package.json +19 -15
- package/src/blobManager.ts +23 -1
- package/src/containerRuntime.ts +42 -392
- package/src/dataStoreContext.ts +10 -25
- package/src/dataStores.ts +0 -6
- package/src/garbageCollection.ts +64 -69
- package/src/index.ts +1 -2
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +18 -2
- package/src/scheduleManager.ts +294 -0
- package/src/summaryCollection.ts +1 -1
- package/src/summaryManager.ts +20 -5
package/lib/containerRuntime.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AttachState, LoaderHeader, } from "@fluidframework/container-definitions";
|
|
2
|
-
import { assert, Trace, TypedEventEmitter, unreachableCase,
|
|
2
|
+
import { assert, Trace, TypedEventEmitter, unreachableCase, } from "@fluidframework/common-utils";
|
|
3
3
|
import { ChildLogger, raiseConnectedEvent, PerformanceEvent, TaggedLoggerAdapter, loggerToMonitoringContext, } from "@fluidframework/telemetry-utils";
|
|
4
4
|
import { DriverHeader, FetchSource, } from "@fluidframework/driver-definitions";
|
|
5
5
|
import { readAndParse, isUnpackedRuntimeMessage } from "@fluidframework/driver-utils";
|
|
6
|
-
import { DataCorruptionError, DataProcessingError, GenericError, UsageError,
|
|
6
|
+
import { DataCorruptionError, DataProcessingError, GenericError, UsageError, } from "@fluidframework/container-utils";
|
|
7
7
|
import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
|
|
8
8
|
import { FlushMode, channelsTreeName, } from "@fluidframework/runtime-definitions";
|
|
9
9
|
import { addBlobToSummary, addSummarizeResultToSummary, addTreeToSummary, createRootSummarizerNodeWithGC, RequestParser, create404Response, exceptionToResponse, requestFluidObject, responseToException, seqFromTree, calculateStats, TelemetryContext, } from "@fluidframework/runtime-utils";
|
|
@@ -13,8 +13,7 @@ import { ContainerFluidHandleContext } from "./containerHandleContext";
|
|
|
13
13
|
import { FluidDataStoreRegistry } from "./dataStoreRegistry";
|
|
14
14
|
import { Summarizer } from "./summarizer";
|
|
15
15
|
import { SummaryManager } from "./summaryManager";
|
|
16
|
-
import {
|
|
17
|
-
import { ReportOpPerfTelemetry, latencyThreshold, } from "./connectionTelemetry";
|
|
16
|
+
import { ReportOpPerfTelemetry, } from "./connectionTelemetry";
|
|
18
17
|
import { PendingStateManager } from "./pendingStateManager";
|
|
19
18
|
import { pkgVersion } from "./packageVersion";
|
|
20
19
|
import { BlobManager } from "./blobManager";
|
|
@@ -29,6 +28,7 @@ import { GarbageCollector, GCNodeType, gcTreeKey, } from "./garbageCollection";
|
|
|
29
28
|
import { channelToDataStore, isDataStoreAliasMessage, } from "./dataStore";
|
|
30
29
|
import { BindBatchTracker } from "./batchTracker";
|
|
31
30
|
import { SerializedSnapshotStorage } from "./serializedSnapshotStorage";
|
|
31
|
+
import { ScheduleManager } from "./scheduleManager";
|
|
32
32
|
export var ContainerMessageType;
|
|
33
33
|
(function (ContainerMessageType) {
|
|
34
34
|
// An op to be delivered to store
|
|
@@ -75,10 +75,6 @@ export var RuntimeHeaders;
|
|
|
75
75
|
RuntimeHeaders["viaHandle"] = "viaHandle";
|
|
76
76
|
})(RuntimeHeaders || (RuntimeHeaders = {}));
|
|
77
77
|
const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
|
|
78
|
-
// Feature gate for the max op size. If the value is negative, chunking is enabled
|
|
79
|
-
// and all ops over 16k would be chunked. If the value is positive, all ops with
|
|
80
|
-
// a size strictly larger will be rejected and the container closed with an error.
|
|
81
|
-
const maxOpSizeInBytesKey = "Fluid.ContainerRuntime.MaxOpSizeInBytes";
|
|
82
78
|
// By default, we should reject any op larger than 768KB,
|
|
83
79
|
// in order to account for some extra overhead from serialization
|
|
84
80
|
// to not reach the 1MB limits in socket.io and Kafka.
|
|
@@ -122,229 +118,6 @@ export function unpackRuntimeMessage(message) {
|
|
|
122
118
|
}
|
|
123
119
|
return message;
|
|
124
120
|
}
|
|
125
|
-
/**
|
|
126
|
-
* This class controls pausing and resuming of inbound queue to ensure that we never
|
|
127
|
-
* start processing ops in a batch IF we do not have all ops in the batch.
|
|
128
|
-
*/
|
|
129
|
-
class ScheduleManagerCore {
|
|
130
|
-
constructor(deltaManager, logger) {
|
|
131
|
-
this.deltaManager = deltaManager;
|
|
132
|
-
this.logger = logger;
|
|
133
|
-
this.localPaused = false;
|
|
134
|
-
this.timePaused = 0;
|
|
135
|
-
this.batchCount = 0;
|
|
136
|
-
// Listen for delta manager sends and add batch metadata to messages
|
|
137
|
-
this.deltaManager.on("prepareSend", (messages) => {
|
|
138
|
-
if (messages.length === 0) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
// First message will have the batch flag set to true if doing a batched send
|
|
142
|
-
const firstMessageMetadata = messages[0].metadata;
|
|
143
|
-
if (!(firstMessageMetadata === null || firstMessageMetadata === void 0 ? void 0 : firstMessageMetadata.batch)) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
// If the batch contains only a single op, clear the batch flag.
|
|
147
|
-
if (messages.length === 1) {
|
|
148
|
-
delete firstMessageMetadata.batch;
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
// Set the batch flag to false on the last message to indicate the end of the send batch
|
|
152
|
-
const lastMessage = messages[messages.length - 1];
|
|
153
|
-
lastMessage.metadata = Object.assign(Object.assign({}, lastMessage.metadata), { batch: false });
|
|
154
|
-
});
|
|
155
|
-
// Listen for updates and peek at the inbound
|
|
156
|
-
this.deltaManager.inbound.on("push", (message) => {
|
|
157
|
-
this.trackPending(message);
|
|
158
|
-
});
|
|
159
|
-
// Start with baseline - empty inbound queue.
|
|
160
|
-
assert(!this.localPaused, 0x293 /* "initial state" */);
|
|
161
|
-
const allPending = this.deltaManager.inbound.toArray();
|
|
162
|
-
for (const pending of allPending) {
|
|
163
|
-
this.trackPending(pending);
|
|
164
|
-
}
|
|
165
|
-
// We are intentionally directly listening to the "op" to inspect system ops as well.
|
|
166
|
-
// If we do not observe system ops, we are likely to hit 0x296 assert when system ops
|
|
167
|
-
// precedes start of incomplete batch.
|
|
168
|
-
this.deltaManager.on("op", (message) => this.afterOpProcessing(message.sequenceNumber));
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* The only public function in this class - called when we processed an op,
|
|
172
|
-
* to make decision if op processing should be paused or not afer that.
|
|
173
|
-
*/
|
|
174
|
-
afterOpProcessing(sequenceNumber) {
|
|
175
|
-
assert(!this.localPaused, 0x294 /* "can't have op processing paused if we are processing an op" */);
|
|
176
|
-
// If the inbound queue is ever empty, nothing to do!
|
|
177
|
-
if (this.deltaManager.inbound.length === 0) {
|
|
178
|
-
assert(this.pauseSequenceNumber === undefined, 0x295 /* "there should be no pending batch if we have no ops" */);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
// The queue is
|
|
182
|
-
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
183
|
-
// - here (processing ops until reaching start of incomplete batch)
|
|
184
|
-
// - in trackPending(), when queue was empty and start of batch showed up.
|
|
185
|
-
// 2. resumed when batch end comes in (in trackPending())
|
|
186
|
-
// do we have incomplete batch to worry about?
|
|
187
|
-
if (this.pauseSequenceNumber !== undefined) {
|
|
188
|
-
assert(sequenceNumber < this.pauseSequenceNumber, 0x296 /* "we should never start processing incomplete batch!" */);
|
|
189
|
-
// If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
|
|
190
|
-
if (sequenceNumber + 1 === this.pauseSequenceNumber) {
|
|
191
|
-
this.pauseQueue();
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
pauseQueue() {
|
|
196
|
-
assert(!this.localPaused, 0x297 /* "always called from resumed state" */);
|
|
197
|
-
this.localPaused = true;
|
|
198
|
-
this.timePaused = performance.now();
|
|
199
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
200
|
-
this.deltaManager.inbound.pause();
|
|
201
|
-
}
|
|
202
|
-
resumeQueue(startBatch, messageEndBatch) {
|
|
203
|
-
const endBatch = messageEndBatch.sequenceNumber;
|
|
204
|
-
const duration = this.localPaused ? (performance.now() - this.timePaused) : undefined;
|
|
205
|
-
this.batchCount++;
|
|
206
|
-
if (this.batchCount % 1000 === 1) {
|
|
207
|
-
this.logger.sendTelemetryEvent({
|
|
208
|
-
eventName: "BatchStats",
|
|
209
|
-
sequenceNumber: endBatch,
|
|
210
|
-
length: endBatch - startBatch + 1,
|
|
211
|
-
msnDistance: endBatch - messageEndBatch.minimumSequenceNumber,
|
|
212
|
-
duration,
|
|
213
|
-
batchCount: this.batchCount,
|
|
214
|
-
interrupted: this.localPaused,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
// Return early if no change in value
|
|
218
|
-
if (!this.localPaused) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
this.localPaused = false;
|
|
222
|
-
// Random round number - we want to know when batch waiting paused op processing.
|
|
223
|
-
if (duration !== undefined && duration > latencyThreshold) {
|
|
224
|
-
this.logger.sendErrorEvent({
|
|
225
|
-
eventName: "MaxBatchWaitTimeExceeded",
|
|
226
|
-
duration,
|
|
227
|
-
sequenceNumber: endBatch,
|
|
228
|
-
length: endBatch - startBatch,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
this.deltaManager.inbound.resume();
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Called for each incoming op (i.e. inbound "push" notification)
|
|
235
|
-
*/
|
|
236
|
-
trackPending(message) {
|
|
237
|
-
assert(this.deltaManager.inbound.length !== 0, 0x298 /* "we have something in the queue that generates this event" */);
|
|
238
|
-
assert((this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined), 0x299 /* "non-synchronized state" */);
|
|
239
|
-
const metadata = message.metadata;
|
|
240
|
-
const batchMetadata = metadata === null || metadata === void 0 ? void 0 : metadata.batch;
|
|
241
|
-
// Protocol messages are never part of a runtime batch of messages
|
|
242
|
-
if (!isUnpackedRuntimeMessage(message)) {
|
|
243
|
-
// Protocol messages should never show up in the middle of the batch!
|
|
244
|
-
assert(this.currentBatchClientId === undefined, 0x29a /* "System message in the middle of batch!" */);
|
|
245
|
-
assert(batchMetadata === undefined, 0x29b /* "system op in a batch?" */);
|
|
246
|
-
assert(!this.localPaused, 0x29c /* "we should be processing ops when there is no active batch" */);
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
if (this.currentBatchClientId === undefined && batchMetadata === undefined) {
|
|
250
|
-
assert(!this.localPaused, 0x29d /* "we should be processing ops when there is no active batch" */);
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
// If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
|
|
254
|
-
// If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
|
|
255
|
-
// the previous one
|
|
256
|
-
if (this.currentBatchClientId !== undefined || batchMetadata === false) {
|
|
257
|
-
if (this.currentBatchClientId !== message.clientId) {
|
|
258
|
-
// "Batch not closed, yet message from another client!"
|
|
259
|
-
throw new DataCorruptionError("OpBatchIncomplete", Object.assign({ runtimeVersion: pkgVersion, batchClientId: this.currentBatchClientId }, extractSafePropertiesFromMessage(message)));
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
// The queue is
|
|
263
|
-
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
264
|
-
// - in afterOpProcessing() - processing ops until reaching start of incomplete batch
|
|
265
|
-
// - here (batchMetadata == false below), when queue was empty and start of batch showed up.
|
|
266
|
-
// 2. resumed when batch end comes in (batchMetadata === true case below)
|
|
267
|
-
if (batchMetadata) {
|
|
268
|
-
assert(this.currentBatchClientId === undefined, 0x29e /* "there can't be active batch" */);
|
|
269
|
-
assert(!this.localPaused, 0x29f /* "we should be processing ops when there is no active batch" */);
|
|
270
|
-
this.pauseSequenceNumber = message.sequenceNumber;
|
|
271
|
-
this.currentBatchClientId = message.clientId;
|
|
272
|
-
// Start of the batch
|
|
273
|
-
// Only pause processing if queue has no other ops!
|
|
274
|
-
// If there are any other ops in the queue, processing will be stopped when they are processed!
|
|
275
|
-
if (this.deltaManager.inbound.length === 1) {
|
|
276
|
-
this.pauseQueue();
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
else if (batchMetadata === false) {
|
|
280
|
-
assert(this.pauseSequenceNumber !== undefined, 0x2a0 /* "batch presence was validated above" */);
|
|
281
|
-
// Batch is complete, we can process it!
|
|
282
|
-
this.resumeQueue(this.pauseSequenceNumber, message);
|
|
283
|
-
this.pauseSequenceNumber = undefined;
|
|
284
|
-
this.currentBatchClientId = undefined;
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
// Continuation of current batch. Do nothing
|
|
288
|
-
assert(this.currentBatchClientId !== undefined, 0x2a1 /* "logic error" */);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* This class has the following responsibilities:
|
|
294
|
-
* 1. It tracks batches as we process ops and raises "batchBegin" and "batchEnd" events.
|
|
295
|
-
* As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)
|
|
296
|
-
* 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch
|
|
297
|
-
* unless all ops of the batch are in.
|
|
298
|
-
*/
|
|
299
|
-
export class ScheduleManager {
|
|
300
|
-
constructor(deltaManager, emitter, logger) {
|
|
301
|
-
this.deltaManager = deltaManager;
|
|
302
|
-
this.emitter = emitter;
|
|
303
|
-
this.logger = logger;
|
|
304
|
-
this.hitError = false;
|
|
305
|
-
this.deltaScheduler = new DeltaScheduler(this.deltaManager, ChildLogger.create(this.logger, "DeltaScheduler"));
|
|
306
|
-
void new ScheduleManagerCore(deltaManager, logger);
|
|
307
|
-
}
|
|
308
|
-
beforeOpProcessing(message) {
|
|
309
|
-
var _a;
|
|
310
|
-
if (this.batchClientId !== message.clientId) {
|
|
311
|
-
assert(this.batchClientId === undefined, 0x2a2 /* "Batch is interrupted by other client op. Should be caught by trackPending()" */);
|
|
312
|
-
// This could be the beginning of a new batch or an individual message.
|
|
313
|
-
this.emitter.emit("batchBegin", message);
|
|
314
|
-
this.deltaScheduler.batchBegin(message);
|
|
315
|
-
const batch = (_a = message === null || message === void 0 ? void 0 : message.metadata) === null || _a === void 0 ? void 0 : _a.batch;
|
|
316
|
-
if (batch) {
|
|
317
|
-
this.batchClientId = message.clientId;
|
|
318
|
-
}
|
|
319
|
-
else {
|
|
320
|
-
this.batchClientId = undefined;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
afterOpProcessing(error, message) {
|
|
325
|
-
var _a;
|
|
326
|
-
// If this is no longer true, we need to revisit what we do where we set this.hitError.
|
|
327
|
-
assert(!this.hitError, 0x2a3 /* "container should be closed on any error" */);
|
|
328
|
-
if (error) {
|
|
329
|
-
// We assume here that loader will close container and stop processing all future ops.
|
|
330
|
-
// This is implicit dependency. If this flow changes, this code might no longer be correct.
|
|
331
|
-
this.hitError = true;
|
|
332
|
-
this.batchClientId = undefined;
|
|
333
|
-
this.emitter.emit("batchEnd", error, message);
|
|
334
|
-
this.deltaScheduler.batchEnd(message);
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
const batch = (_a = message === null || message === void 0 ? void 0 : message.metadata) === null || _a === void 0 ? void 0 : _a.batch;
|
|
338
|
-
// If no batchClientId has been set then we're in an individual batch. Else, if we get
|
|
339
|
-
// batch end metadata, this is end of the current batch.
|
|
340
|
-
if (this.batchClientId === undefined || batch === false) {
|
|
341
|
-
this.batchClientId = undefined;
|
|
342
|
-
this.emitter.emit("batchEnd", undefined, message);
|
|
343
|
-
this.deltaScheduler.batchEnd(message);
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
121
|
/**
|
|
349
122
|
* Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
|
|
350
123
|
* special-case for document dirty state. Ultimately we should have no special-cases from the
|
|
@@ -371,7 +144,7 @@ export function getDeviceSpec() {
|
|
|
371
144
|
*/
|
|
372
145
|
export class ContainerRuntime extends TypedEventEmitter {
|
|
373
146
|
constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
|
|
374
|
-
var _a, _b, _c, _d
|
|
147
|
+
var _a, _b, _c, _d;
|
|
375
148
|
if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
|
|
376
149
|
super();
|
|
377
150
|
this.context = context;
|
|
@@ -382,7 +155,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
382
155
|
this._storage = _storage;
|
|
383
156
|
this.requestHandler = requestHandler;
|
|
384
157
|
this.summaryConfiguration = summaryConfiguration;
|
|
385
|
-
this.defaultMaxConsecutiveReconnects =
|
|
158
|
+
this.defaultMaxConsecutiveReconnects = 7;
|
|
386
159
|
this._orderSequentiallyCalls = 0;
|
|
387
160
|
this.needsFlush = false;
|
|
388
161
|
this.flushTrigger = false;
|
|
@@ -426,8 +199,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
426
199
|
}
|
|
427
200
|
};
|
|
428
201
|
this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
|
|
429
|
-
// Default to false (enabled).
|
|
430
|
-
this.disableIsolatedChannels = (_b = this.runtimeOptions.summaryOptions.disableIsolatedChannels) !== null && _b !== void 0 ? _b : false;
|
|
431
202
|
this._connected = this.context.connected;
|
|
432
203
|
this.chunkMap = new Map(chunks);
|
|
433
204
|
this.handleContext = new ContainerFluidHandleContext("", this);
|
|
@@ -440,12 +211,11 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
440
211
|
this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
|
|
441
212
|
this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
|
|
442
213
|
this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
|
|
443
|
-
this._maxOpSizeInBytes = ((_c = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _c !== void 0 ? _c : defaultMaxOpSizeInBytes);
|
|
444
214
|
this.maxConsecutiveReconnects =
|
|
445
|
-
(
|
|
215
|
+
(_b = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _b !== void 0 ? _b : this.defaultMaxConsecutiveReconnects;
|
|
446
216
|
this._flushMode = runtimeOptions.flushMode;
|
|
447
217
|
const pendingRuntimeState = context.pendingLocalState;
|
|
448
|
-
const baseSnapshot = (
|
|
218
|
+
const baseSnapshot = (_c = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _c !== void 0 ? _c : context.baseSnapshot;
|
|
449
219
|
this.garbageCollector = GarbageCollector.create({
|
|
450
220
|
runtime: this,
|
|
451
221
|
gcOptions: this.runtimeOptions.gcOptions,
|
|
@@ -479,7 +249,11 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
479
249
|
this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
|
|
480
250
|
}
|
|
481
251
|
this.dataStores = new DataStores(getSummaryForDatastores(baseSnapshot, metadata), this, (attachMsg) => this.submit(ContainerMessageType.Attach, attachMsg), (id, createParam) => (summarizeInternal, getGCDataFn, getBaseGCDetailsFn) => this.summarizerNode.createChild(summarizeInternal, id, createParam, undefined, getGCDataFn, getBaseGCDetailsFn), (id) => this.summarizerNode.deleteChild(id), this.mc.logger, async () => this.garbageCollector.getBaseGCDetails(), (path, timestampMs, packagePath) => this.garbageCollector.nodeUpdated(path, "Changed", timestampMs, packagePath), new Map(dataStoreAliasMap), this.garbageCollector.writeDataAtRoot);
|
|
482
|
-
this.blobManager = new BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId, localId) =>
|
|
252
|
+
this.blobManager = new BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId, localId) => {
|
|
253
|
+
if (!this.disposed) {
|
|
254
|
+
this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
|
|
255
|
+
}
|
|
256
|
+
}, (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pendingAttachmentBlobs);
|
|
483
257
|
this.scheduleManager = new ScheduleManager(context.deltaManager, this, ChildLogger.create(this.logger, "ScheduleManager"));
|
|
484
258
|
this.deltaSender = this.deltaManager;
|
|
485
259
|
this.pendingStateManager = new PendingStateManager({
|
|
@@ -572,7 +346,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
572
346
|
};
|
|
573
347
|
// summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
|
|
574
348
|
// the count is reset to 0.
|
|
575
|
-
loadSummaryNumber = (
|
|
349
|
+
loadSummaryNumber = (_d = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _d !== void 0 ? _d : 0;
|
|
576
350
|
}
|
|
577
351
|
else {
|
|
578
352
|
this.createContainerMetadata = {
|
|
@@ -918,7 +692,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
918
692
|
var _a;
|
|
919
693
|
const metadata = Object.assign(Object.assign(Object.assign(Object.assign({}, this.createContainerMetadata), {
|
|
920
694
|
// Increment the summary number for the next summary that will be generated.
|
|
921
|
-
summaryNumber: this.nextSummaryNumber++, summaryFormatVersion: 1
|
|
695
|
+
summaryNumber: this.nextSummaryNumber++, summaryFormatVersion: 1 }), this.garbageCollector.getMetadata()), {
|
|
922
696
|
// The last message processed at the time of summary. If there are no new messages, use the message from the
|
|
923
697
|
// last summary.
|
|
924
698
|
message: (_a = extractSummaryMetadataMessage(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.messageAtLastSummary });
|
|
@@ -1060,13 +834,14 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1060
834
|
this.verifyNotClosed();
|
|
1061
835
|
// There might be no change of state due to Container calling this API after loading runtime.
|
|
1062
836
|
const changeOfState = this._connected !== connected;
|
|
1063
|
-
const reconnection = changeOfState && connected;
|
|
837
|
+
const reconnection = changeOfState && !connected;
|
|
1064
838
|
this._connected = connected;
|
|
1065
839
|
if (!connected) {
|
|
1066
840
|
this._perfSignalData.signalsLost = 0;
|
|
1067
841
|
this._perfSignalData.signalTimestamp = 0;
|
|
1068
842
|
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
1069
843
|
}
|
|
844
|
+
// Fail while disconnected
|
|
1070
845
|
if (reconnection) {
|
|
1071
846
|
this.consecutiveReconnects++;
|
|
1072
847
|
if (!this.shouldContinueReconnecting()) {
|
|
@@ -1407,10 +1182,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1407
1182
|
this.blobManager.setRedirectTable(blobRedirectTable);
|
|
1408
1183
|
}
|
|
1409
1184
|
const summarizeResult = this.dataStores.createSummary(telemetryContext);
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
wrapSummaryInChannelsTree(summarizeResult);
|
|
1413
|
-
}
|
|
1185
|
+
// Wrap data store summaries in .channels subtree.
|
|
1186
|
+
wrapSummaryInChannelsTree(summarizeResult);
|
|
1414
1187
|
this.addContainerStateToSummary(summarizeResult, true /* fullTree */, false /* trackState */, telemetryContext);
|
|
1415
1188
|
return summarizeResult.summary;
|
|
1416
1189
|
}
|
|
@@ -1425,12 +1198,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1425
1198
|
}
|
|
1426
1199
|
async summarizeInternal(fullTree, trackState, telemetryContext) {
|
|
1427
1200
|
const summarizeResult = await this.dataStores.summarize(fullTree, trackState, telemetryContext);
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
wrapSummaryInChannelsTree(summarizeResult);
|
|
1432
|
-
pathPartsForChildren = [channelsTreeName];
|
|
1433
|
-
}
|
|
1201
|
+
// Wrap data store summaries in .channels subtree.
|
|
1202
|
+
wrapSummaryInChannelsTree(summarizeResult);
|
|
1203
|
+
const pathPartsForChildren = [channelsTreeName];
|
|
1434
1204
|
this.addContainerStateToSummary(summarizeResult, fullTree, trackState, telemetryContext);
|
|
1435
1205
|
return Object.assign(Object.assign({}, summarizeResult), { id: "", pathPartsForChildren });
|
|
1436
1206
|
}
|
|
@@ -1683,7 +1453,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1683
1453
|
// Counting dataStores and handles
|
|
1684
1454
|
// Because handles are unchanged dataStores in the current logic,
|
|
1685
1455
|
// summarized dataStore count is total dataStore count minus handle count
|
|
1686
|
-
const dataStoreTree =
|
|
1456
|
+
const dataStoreTree = summaryTree.tree[channelsTreeName];
|
|
1687
1457
|
assert(dataStoreTree.type === SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
|
|
1688
1458
|
const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === SummaryType.Handle).length;
|
|
1689
1459
|
const gcSummaryTreeStats = summaryTree.tree[gcTreeKey]
|
|
@@ -1836,7 +1606,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1836
1606
|
let opMetadataInternal = opMetadata;
|
|
1837
1607
|
if (this.canSendOps()) {
|
|
1838
1608
|
const serializedContent = JSON.stringify(content);
|
|
1839
|
-
const maxOpSize = this.context.deltaManager.maxMessageSize;
|
|
1840
1609
|
// If in TurnBased flush mode we will trigger a flush at the next turn break
|
|
1841
1610
|
if (this.flushMode === FlushMode.TurnBased && !this.needsFlush) {
|
|
1842
1611
|
opMetadataInternal = Object.assign(Object.assign({}, opMetadata), { batch: true });
|
|
@@ -1850,7 +1619,19 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1850
1619
|
});
|
|
1851
1620
|
}
|
|
1852
1621
|
}
|
|
1853
|
-
|
|
1622
|
+
if (!serializedContent || serializedContent.length <= defaultMaxOpSizeInBytes) {
|
|
1623
|
+
clientSequenceNumber = this.submitRuntimeMessage(type, content, this._flushMode === FlushMode.TurnBased /* batch */, opMetadataInternal);
|
|
1624
|
+
}
|
|
1625
|
+
else {
|
|
1626
|
+
// If the content length is larger than the client configured message size
|
|
1627
|
+
// instead of splitting the content, we will fail by explicitly closing the container
|
|
1628
|
+
this.closeFn(new GenericError("OpTooLarge",
|
|
1629
|
+
/* error */ undefined, {
|
|
1630
|
+
length: serializedContent.length,
|
|
1631
|
+
limit: defaultMaxOpSizeInBytes,
|
|
1632
|
+
}));
|
|
1633
|
+
clientSequenceNumber = -1;
|
|
1634
|
+
}
|
|
1854
1635
|
}
|
|
1855
1636
|
// Let the PendingStateManager know that a message was submitted.
|
|
1856
1637
|
this.pendingStateManager.onSubmitMessage(type, clientSequenceNumber, this.deltaManager.lastSequenceNumber, content, localOpMetadata, opMetadataInternal);
|
|
@@ -1858,46 +1639,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1858
1639
|
this.updateDocumentDirtyState(true);
|
|
1859
1640
|
}
|
|
1860
1641
|
}
|
|
1861
|
-
submitMaybeChunkedMessages(type, content, serializedContent, serverMaxOpSize, batch, opMetadataInternal = undefined) {
|
|
1862
|
-
if (this._maxOpSizeInBytes >= 0) {
|
|
1863
|
-
// Chunking disabled
|
|
1864
|
-
if (!serializedContent || serializedContent.length <= this._maxOpSizeInBytes) {
|
|
1865
|
-
return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
|
|
1866
|
-
}
|
|
1867
|
-
// When chunking is disabled, we ignore the server max message size
|
|
1868
|
-
// and if the content length is larger than the client configured message size
|
|
1869
|
-
// instead of splitting the content, we will fail by explicitly close the container
|
|
1870
|
-
this.closeFn(new GenericError("OpTooLarge",
|
|
1871
|
-
/* error */ undefined, {
|
|
1872
|
-
length: serializedContent.length,
|
|
1873
|
-
limit: this._maxOpSizeInBytes,
|
|
1874
|
-
}));
|
|
1875
|
-
return -1;
|
|
1876
|
-
}
|
|
1877
|
-
// Chunking enabled, fallback on the server's max message size
|
|
1878
|
-
// and split the content accordingly
|
|
1879
|
-
if (!serializedContent || serializedContent.length <= serverMaxOpSize) {
|
|
1880
|
-
return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
|
|
1881
|
-
}
|
|
1882
|
-
return this.submitChunkedMessage(type, serializedContent, serverMaxOpSize);
|
|
1883
|
-
}
|
|
1884
|
-
submitChunkedMessage(type, content, maxOpSize) {
|
|
1885
|
-
const contentLength = content.length;
|
|
1886
|
-
const chunkN = Math.floor((contentLength - 1) / maxOpSize) + 1;
|
|
1887
|
-
let offset = 0;
|
|
1888
|
-
let clientSequenceNumber = 0;
|
|
1889
|
-
for (let i = 1; i <= chunkN; i = i + 1) {
|
|
1890
|
-
const chunkedOp = {
|
|
1891
|
-
chunkId: i,
|
|
1892
|
-
contents: content.substr(offset, maxOpSize),
|
|
1893
|
-
originalType: type,
|
|
1894
|
-
totalChunks: chunkN,
|
|
1895
|
-
};
|
|
1896
|
-
offset += maxOpSize;
|
|
1897
|
-
clientSequenceNumber = this.submitRuntimeMessage(ContainerMessageType.ChunkedOp, chunkedOp, false);
|
|
1898
|
-
}
|
|
1899
|
-
return clientSequenceNumber;
|
|
1900
|
-
}
|
|
1901
1642
|
submitSystemMessage(type, contents) {
|
|
1902
1643
|
this.verifyNotClosed();
|
|
1903
1644
|
assert(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
|
|
@@ -2034,6 +1775,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2034
1775
|
if (previousPendingState) {
|
|
2035
1776
|
return {
|
|
2036
1777
|
pending: this.pendingStateManager.getLocalState(),
|
|
1778
|
+
pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
|
|
2037
1779
|
snapshotBlobs: previousPendingState.snapshotBlobs,
|
|
2038
1780
|
baseSnapshot: previousPendingState.baseSnapshot,
|
|
2039
1781
|
savedOps: this.savedOps,
|
|
@@ -2043,6 +1785,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2043
1785
|
assert(!!this.baseSnapshotBlobs, 0x2e7 /* "Must serialize base snapshot blobs before getting runtime state" */);
|
|
2044
1786
|
return {
|
|
2045
1787
|
pending: this.pendingStateManager.getLocalState(),
|
|
1788
|
+
pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
|
|
2046
1789
|
snapshotBlobs: this.baseSnapshotBlobs,
|
|
2047
1790
|
baseSnapshot: this.context.baseSnapshot,
|
|
2048
1791
|
savedOps: this.savedOps,
|