@fluidframework/container-runtime 2.0.0-internal.1.0.0.82693 → 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/batchTracker.js +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +7 -1
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +34 -17
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +3 -104
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +83 -395
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +2 -3
- package/dist/dataStore.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 +1 -1
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +3 -8
- 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/runningSummarizer.d.ts +14 -0
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +25 -0
- package/dist/runningSummarizer.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/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +33 -3
- package/dist/summarizer.js.map +1 -1
- package/dist/summaryCollection.js +1 -1
- package/dist/summaryCollection.js.map +1 -1
- package/dist/summaryGenerator.js +1 -1
- package/dist/summaryGenerator.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/batchTracker.js +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +7 -1
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +35 -18
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +3 -104
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +84 -395
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +2 -3
- package/lib/dataStore.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 +1 -1
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +3 -8
- 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/runningSummarizer.d.ts +14 -0
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +25 -0
- package/lib/runningSummarizer.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/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +35 -5
- package/lib/summarizer.js.map +1 -1
- package/lib/summaryCollection.js +1 -1
- package/lib/summaryCollection.js.map +1 -1
- package/lib/summaryGenerator.js +1 -1
- package/lib/summaryGenerator.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 +32 -19
- package/src/batchTracker.ts +1 -1
- package/src/blobManager.ts +43 -17
- package/src/containerRuntime.ts +113 -547
- package/src/dataStore.ts +1 -4
- package/src/dataStoreContext.ts +10 -25
- package/src/dataStores.ts +13 -19
- 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/runningSummarizer.ts +33 -1
- package/src/scheduleManager.ts +294 -0
- package/src/summarizer.ts +46 -10
- package/src/summaryCollection.ts +1 -1
- package/src/summaryGenerator.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
|
|
@@ -74,12 +74,7 @@ export var RuntimeHeaders;
|
|
|
74
74
|
/** True if the request is coming from an IFluidHandle. */
|
|
75
75
|
RuntimeHeaders["viaHandle"] = "viaHandle";
|
|
76
76
|
})(RuntimeHeaders || (RuntimeHeaders = {}));
|
|
77
|
-
const useDataStoreAliasingKey = "Fluid.ContainerRuntime.UseDataStoreAliasing";
|
|
78
77
|
const maxConsecutiveReconnectsKey = "Fluid.ContainerRuntime.MaxConsecutiveReconnects";
|
|
79
|
-
// Feature gate for the max op size. If the value is negative, chunking is enabled
|
|
80
|
-
// and all ops over 16k would be chunked. If the value is positive, all ops with
|
|
81
|
-
// a size strictly larger will be rejected and the container closed with an error.
|
|
82
|
-
const maxOpSizeInBytesKey = "Fluid.ContainerRuntime.MaxOpSizeInBytes";
|
|
83
78
|
// By default, we should reject any op larger than 768KB,
|
|
84
79
|
// in order to account for some extra overhead from serialization
|
|
85
80
|
// to not reach the 1MB limits in socket.io and Kafka.
|
|
@@ -123,229 +118,6 @@ export function unpackRuntimeMessage(message) {
|
|
|
123
118
|
}
|
|
124
119
|
return message;
|
|
125
120
|
}
|
|
126
|
-
/**
|
|
127
|
-
* This class controls pausing and resuming of inbound queue to ensure that we never
|
|
128
|
-
* start processing ops in a batch IF we do not have all ops in the batch.
|
|
129
|
-
*/
|
|
130
|
-
class ScheduleManagerCore {
|
|
131
|
-
constructor(deltaManager, logger) {
|
|
132
|
-
this.deltaManager = deltaManager;
|
|
133
|
-
this.logger = logger;
|
|
134
|
-
this.localPaused = false;
|
|
135
|
-
this.timePaused = 0;
|
|
136
|
-
this.batchCount = 0;
|
|
137
|
-
// Listen for delta manager sends and add batch metadata to messages
|
|
138
|
-
this.deltaManager.on("prepareSend", (messages) => {
|
|
139
|
-
if (messages.length === 0) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
// First message will have the batch flag set to true if doing a batched send
|
|
143
|
-
const firstMessageMetadata = messages[0].metadata;
|
|
144
|
-
if (!(firstMessageMetadata === null || firstMessageMetadata === void 0 ? void 0 : firstMessageMetadata.batch)) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
// If the batch contains only a single op, clear the batch flag.
|
|
148
|
-
if (messages.length === 1) {
|
|
149
|
-
delete firstMessageMetadata.batch;
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
// Set the batch flag to false on the last message to indicate the end of the send batch
|
|
153
|
-
const lastMessage = messages[messages.length - 1];
|
|
154
|
-
lastMessage.metadata = Object.assign(Object.assign({}, lastMessage.metadata), { batch: false });
|
|
155
|
-
});
|
|
156
|
-
// Listen for updates and peek at the inbound
|
|
157
|
-
this.deltaManager.inbound.on("push", (message) => {
|
|
158
|
-
this.trackPending(message);
|
|
159
|
-
});
|
|
160
|
-
// Start with baseline - empty inbound queue.
|
|
161
|
-
assert(!this.localPaused, 0x293 /* "initial state" */);
|
|
162
|
-
const allPending = this.deltaManager.inbound.toArray();
|
|
163
|
-
for (const pending of allPending) {
|
|
164
|
-
this.trackPending(pending);
|
|
165
|
-
}
|
|
166
|
-
// We are intentionally directly listening to the "op" to inspect system ops as well.
|
|
167
|
-
// If we do not observe system ops, we are likely to hit 0x296 assert when system ops
|
|
168
|
-
// precedes start of incomplete batch.
|
|
169
|
-
this.deltaManager.on("op", (message) => this.afterOpProcessing(message.sequenceNumber));
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* The only public function in this class - called when we processed an op,
|
|
173
|
-
* to make decision if op processing should be paused or not afer that.
|
|
174
|
-
*/
|
|
175
|
-
afterOpProcessing(sequenceNumber) {
|
|
176
|
-
assert(!this.localPaused, 0x294 /* "can't have op processing paused if we are processing an op" */);
|
|
177
|
-
// If the inbound queue is ever empty, nothing to do!
|
|
178
|
-
if (this.deltaManager.inbound.length === 0) {
|
|
179
|
-
assert(this.pauseSequenceNumber === undefined, 0x295 /* "there should be no pending batch if we have no ops" */);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
// The queue is
|
|
183
|
-
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
184
|
-
// - here (processing ops until reaching start of incomplete batch)
|
|
185
|
-
// - in trackPending(), when queue was empty and start of batch showed up.
|
|
186
|
-
// 2. resumed when batch end comes in (in trackPending())
|
|
187
|
-
// do we have incomplete batch to worry about?
|
|
188
|
-
if (this.pauseSequenceNumber !== undefined) {
|
|
189
|
-
assert(sequenceNumber < this.pauseSequenceNumber, 0x296 /* "we should never start processing incomplete batch!" */);
|
|
190
|
-
// If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
|
|
191
|
-
if (sequenceNumber + 1 === this.pauseSequenceNumber) {
|
|
192
|
-
this.pauseQueue();
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
pauseQueue() {
|
|
197
|
-
assert(!this.localPaused, 0x297 /* "always called from resumed state" */);
|
|
198
|
-
this.localPaused = true;
|
|
199
|
-
this.timePaused = performance.now();
|
|
200
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
201
|
-
this.deltaManager.inbound.pause();
|
|
202
|
-
}
|
|
203
|
-
resumeQueue(startBatch, messageEndBatch) {
|
|
204
|
-
const endBatch = messageEndBatch.sequenceNumber;
|
|
205
|
-
const duration = this.localPaused ? (performance.now() - this.timePaused) : undefined;
|
|
206
|
-
this.batchCount++;
|
|
207
|
-
if (this.batchCount % 1000 === 1) {
|
|
208
|
-
this.logger.sendTelemetryEvent({
|
|
209
|
-
eventName: "BatchStats",
|
|
210
|
-
sequenceNumber: endBatch,
|
|
211
|
-
length: endBatch - startBatch + 1,
|
|
212
|
-
msnDistance: endBatch - messageEndBatch.minimumSequenceNumber,
|
|
213
|
-
duration,
|
|
214
|
-
batchCount: this.batchCount,
|
|
215
|
-
interrupted: this.localPaused,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
// Return early if no change in value
|
|
219
|
-
if (!this.localPaused) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
this.localPaused = false;
|
|
223
|
-
// Random round number - we want to know when batch waiting paused op processing.
|
|
224
|
-
if (duration !== undefined && duration > latencyThreshold) {
|
|
225
|
-
this.logger.sendErrorEvent({
|
|
226
|
-
eventName: "MaxBatchWaitTimeExceeded",
|
|
227
|
-
duration,
|
|
228
|
-
sequenceNumber: endBatch,
|
|
229
|
-
length: endBatch - startBatch,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
this.deltaManager.inbound.resume();
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Called for each incoming op (i.e. inbound "push" notification)
|
|
236
|
-
*/
|
|
237
|
-
trackPending(message) {
|
|
238
|
-
assert(this.deltaManager.inbound.length !== 0, 0x298 /* "we have something in the queue that generates this event" */);
|
|
239
|
-
assert((this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined), 0x299 /* "non-synchronized state" */);
|
|
240
|
-
const metadata = message.metadata;
|
|
241
|
-
const batchMetadata = metadata === null || metadata === void 0 ? void 0 : metadata.batch;
|
|
242
|
-
// Protocol messages are never part of a runtime batch of messages
|
|
243
|
-
if (!isUnpackedRuntimeMessage(message)) {
|
|
244
|
-
// Protocol messages should never show up in the middle of the batch!
|
|
245
|
-
assert(this.currentBatchClientId === undefined, 0x29a /* "System message in the middle of batch!" */);
|
|
246
|
-
assert(batchMetadata === undefined, 0x29b /* "system op in a batch?" */);
|
|
247
|
-
assert(!this.localPaused, 0x29c /* "we should be processing ops when there is no active batch" */);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
if (this.currentBatchClientId === undefined && batchMetadata === undefined) {
|
|
251
|
-
assert(!this.localPaused, 0x29d /* "we should be processing ops when there is no active batch" */);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
// If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
|
|
255
|
-
// If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
|
|
256
|
-
// the previous one
|
|
257
|
-
if (this.currentBatchClientId !== undefined || batchMetadata === false) {
|
|
258
|
-
if (this.currentBatchClientId !== message.clientId) {
|
|
259
|
-
// "Batch not closed, yet message from another client!"
|
|
260
|
-
throw new DataCorruptionError("OpBatchIncomplete", Object.assign({ runtimeVersion: pkgVersion, batchClientId: this.currentBatchClientId }, extractSafePropertiesFromMessage(message)));
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
// The queue is
|
|
264
|
-
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
265
|
-
// - in afterOpProcessing() - processing ops until reaching start of incomplete batch
|
|
266
|
-
// - here (batchMetadata == false below), when queue was empty and start of batch showed up.
|
|
267
|
-
// 2. resumed when batch end comes in (batchMetadata === true case below)
|
|
268
|
-
if (batchMetadata) {
|
|
269
|
-
assert(this.currentBatchClientId === undefined, 0x29e /* "there can't be active batch" */);
|
|
270
|
-
assert(!this.localPaused, 0x29f /* "we should be processing ops when there is no active batch" */);
|
|
271
|
-
this.pauseSequenceNumber = message.sequenceNumber;
|
|
272
|
-
this.currentBatchClientId = message.clientId;
|
|
273
|
-
// Start of the batch
|
|
274
|
-
// Only pause processing if queue has no other ops!
|
|
275
|
-
// If there are any other ops in the queue, processing will be stopped when they are processed!
|
|
276
|
-
if (this.deltaManager.inbound.length === 1) {
|
|
277
|
-
this.pauseQueue();
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
else if (batchMetadata === false) {
|
|
281
|
-
assert(this.pauseSequenceNumber !== undefined, 0x2a0 /* "batch presence was validated above" */);
|
|
282
|
-
// Batch is complete, we can process it!
|
|
283
|
-
this.resumeQueue(this.pauseSequenceNumber, message);
|
|
284
|
-
this.pauseSequenceNumber = undefined;
|
|
285
|
-
this.currentBatchClientId = undefined;
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
// Continuation of current batch. Do nothing
|
|
289
|
-
assert(this.currentBatchClientId !== undefined, 0x2a1 /* "logic error" */);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* This class has the following responsibilities:
|
|
295
|
-
* 1. It tracks batches as we process ops and raises "batchBegin" and "batchEnd" events.
|
|
296
|
-
* As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)
|
|
297
|
-
* 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch
|
|
298
|
-
* unless all ops of the batch are in.
|
|
299
|
-
*/
|
|
300
|
-
export class ScheduleManager {
|
|
301
|
-
constructor(deltaManager, emitter, logger) {
|
|
302
|
-
this.deltaManager = deltaManager;
|
|
303
|
-
this.emitter = emitter;
|
|
304
|
-
this.logger = logger;
|
|
305
|
-
this.hitError = false;
|
|
306
|
-
this.deltaScheduler = new DeltaScheduler(this.deltaManager, ChildLogger.create(this.logger, "DeltaScheduler"));
|
|
307
|
-
void new ScheduleManagerCore(deltaManager, logger);
|
|
308
|
-
}
|
|
309
|
-
beforeOpProcessing(message) {
|
|
310
|
-
var _a;
|
|
311
|
-
if (this.batchClientId !== message.clientId) {
|
|
312
|
-
assert(this.batchClientId === undefined, 0x2a2 /* "Batch is interrupted by other client op. Should be caught by trackPending()" */);
|
|
313
|
-
// This could be the beginning of a new batch or an individual message.
|
|
314
|
-
this.emitter.emit("batchBegin", message);
|
|
315
|
-
this.deltaScheduler.batchBegin(message);
|
|
316
|
-
const batch = (_a = message === null || message === void 0 ? void 0 : message.metadata) === null || _a === void 0 ? void 0 : _a.batch;
|
|
317
|
-
if (batch) {
|
|
318
|
-
this.batchClientId = message.clientId;
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
this.batchClientId = undefined;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
afterOpProcessing(error, message) {
|
|
326
|
-
var _a;
|
|
327
|
-
// If this is no longer true, we need to revisit what we do where we set this.hitError.
|
|
328
|
-
assert(!this.hitError, 0x2a3 /* "container should be closed on any error" */);
|
|
329
|
-
if (error) {
|
|
330
|
-
// We assume here that loader will close container and stop processing all future ops.
|
|
331
|
-
// This is implicit dependency. If this flow changes, this code might no longer be correct.
|
|
332
|
-
this.hitError = true;
|
|
333
|
-
this.batchClientId = undefined;
|
|
334
|
-
this.emitter.emit("batchEnd", error, message);
|
|
335
|
-
this.deltaScheduler.batchEnd(message);
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
const batch = (_a = message === null || message === void 0 ? void 0 : message.metadata) === null || _a === void 0 ? void 0 : _a.batch;
|
|
339
|
-
// If no batchClientId has been set then we're in an individual batch. Else, if we get
|
|
340
|
-
// batch end metadata, this is end of the current batch.
|
|
341
|
-
if (this.batchClientId === undefined || batch === false) {
|
|
342
|
-
this.batchClientId = undefined;
|
|
343
|
-
this.emitter.emit("batchEnd", undefined, message);
|
|
344
|
-
this.deltaScheduler.batchEnd(message);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
121
|
/**
|
|
350
122
|
* Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
|
|
351
123
|
* special-case for document dirty state. Ultimately we should have no special-cases from the
|
|
@@ -372,7 +144,7 @@ export function getDeviceSpec() {
|
|
|
372
144
|
*/
|
|
373
145
|
export class ContainerRuntime extends TypedEventEmitter {
|
|
374
146
|
constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
|
|
375
|
-
var _a, _b, _c, _d
|
|
147
|
+
var _a, _b, _c, _d;
|
|
376
148
|
if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
|
|
377
149
|
super();
|
|
378
150
|
this.context = context;
|
|
@@ -383,7 +155,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
383
155
|
this._storage = _storage;
|
|
384
156
|
this.requestHandler = requestHandler;
|
|
385
157
|
this.summaryConfiguration = summaryConfiguration;
|
|
386
|
-
this.defaultMaxConsecutiveReconnects =
|
|
158
|
+
this.defaultMaxConsecutiveReconnects = 7;
|
|
387
159
|
this._orderSequentiallyCalls = 0;
|
|
388
160
|
this.needsFlush = false;
|
|
389
161
|
this.flushTrigger = false;
|
|
@@ -427,8 +199,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
427
199
|
}
|
|
428
200
|
};
|
|
429
201
|
this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
|
|
430
|
-
// Default to false (enabled).
|
|
431
|
-
this.disableIsolatedChannels = (_b = this.runtimeOptions.summaryOptions.disableIsolatedChannels) !== null && _b !== void 0 ? _b : false;
|
|
432
202
|
this._connected = this.context.connected;
|
|
433
203
|
this.chunkMap = new Map(chunks);
|
|
434
204
|
this.handleContext = new ContainerFluidHandleContext("", this);
|
|
@@ -441,15 +211,11 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
441
211
|
this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
|
|
442
212
|
this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
|
|
443
213
|
this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
|
|
444
|
-
this._aliasingEnabled =
|
|
445
|
-
((_c = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _c !== void 0 ? _c : false) ||
|
|
446
|
-
((_d = runtimeOptions.useDataStoreAliasing) !== null && _d !== void 0 ? _d : false);
|
|
447
|
-
this._maxOpSizeInBytes = ((_e = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _e !== void 0 ? _e : defaultMaxOpSizeInBytes);
|
|
448
214
|
this.maxConsecutiveReconnects =
|
|
449
|
-
(
|
|
215
|
+
(_b = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _b !== void 0 ? _b : this.defaultMaxConsecutiveReconnects;
|
|
450
216
|
this._flushMode = runtimeOptions.flushMode;
|
|
451
217
|
const pendingRuntimeState = context.pendingLocalState;
|
|
452
|
-
const baseSnapshot = (
|
|
218
|
+
const baseSnapshot = (_c = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _c !== void 0 ? _c : context.baseSnapshot;
|
|
453
219
|
this.garbageCollector = GarbageCollector.create({
|
|
454
220
|
runtime: this,
|
|
455
221
|
gcOptions: this.runtimeOptions.gcOptions,
|
|
@@ -483,7 +249,11 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
483
249
|
this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
|
|
484
250
|
}
|
|
485
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);
|
|
486
|
-
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);
|
|
487
257
|
this.scheduleManager = new ScheduleManager(context.deltaManager, this, ChildLogger.create(this.logger, "ScheduleManager"));
|
|
488
258
|
this.deltaSender = this.deltaManager;
|
|
489
259
|
this.pendingStateManager = new PendingStateManager({
|
|
@@ -576,7 +346,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
576
346
|
};
|
|
577
347
|
// summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
|
|
578
348
|
// the count is reset to 0.
|
|
579
|
-
loadSummaryNumber = (
|
|
349
|
+
loadSummaryNumber = (_d = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _d !== void 0 ? _d : 0;
|
|
580
350
|
}
|
|
581
351
|
else {
|
|
582
352
|
this.createContainerMetadata = {
|
|
@@ -611,7 +381,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
611
381
|
runtimeVersion: pkgVersion,
|
|
612
382
|
},
|
|
613
383
|
});
|
|
614
|
-
const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close",
|
|
384
|
+
const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
|
|
615
385
|
const pendingRuntimeState = context.pendingLocalState;
|
|
616
386
|
const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
|
|
617
387
|
const storage = !pendingRuntimeState ?
|
|
@@ -664,7 +434,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
664
434
|
summaryOptions,
|
|
665
435
|
gcOptions,
|
|
666
436
|
loadSequenceNumberVerification,
|
|
667
|
-
useDataStoreAliasing,
|
|
668
437
|
flushMode,
|
|
669
438
|
enableOfflineLoad,
|
|
670
439
|
}, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
|
|
@@ -923,7 +692,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
923
692
|
var _a;
|
|
924
693
|
const metadata = Object.assign(Object.assign(Object.assign(Object.assign({}, this.createContainerMetadata), {
|
|
925
694
|
// Increment the summary number for the next summary that will be generated.
|
|
926
|
-
summaryNumber: this.nextSummaryNumber++, summaryFormatVersion: 1
|
|
695
|
+
summaryNumber: this.nextSummaryNumber++, summaryFormatVersion: 1 }), this.garbageCollector.getMetadata()), {
|
|
927
696
|
// The last message processed at the time of summary. If there are no new messages, use the message from the
|
|
928
697
|
// last summary.
|
|
929
698
|
message: (_a = extractSummaryMetadataMessage(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.messageAtLastSummary });
|
|
@@ -1046,8 +815,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1046
815
|
// ensure we don't submit ops referencing a blob that has not been uploaded
|
|
1047
816
|
const connecting = connected && !this._connected && !this.deltaManager.readOnlyInfo.readonly;
|
|
1048
817
|
if (connecting && this.blobManager.hasPendingOfflineUploads) {
|
|
1049
|
-
assert(!this.delayConnectClientId,
|
|
1050
|
-
assert(!!clientId,
|
|
818
|
+
assert(!this.delayConnectClientId, 0x392 /* Connect event delay must be canceled before subsequent connect event */);
|
|
819
|
+
assert(!!clientId, 0x393 /* Must have clientId when connecting */);
|
|
1051
820
|
this.delayConnectClientId = clientId;
|
|
1052
821
|
this.blobManager.onConnected().then(() => {
|
|
1053
822
|
// make sure we didn't reconnect before the promise resolved
|
|
@@ -1061,17 +830,18 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1061
830
|
this.setConnectionStateCore(connected, clientId);
|
|
1062
831
|
}
|
|
1063
832
|
setConnectionStateCore(connected, clientId) {
|
|
1064
|
-
assert(!this.delayConnectClientId,
|
|
833
|
+
assert(!this.delayConnectClientId, 0x394 /* connect event delay must be cleared before propagating connect event */);
|
|
1065
834
|
this.verifyNotClosed();
|
|
1066
835
|
// There might be no change of state due to Container calling this API after loading runtime.
|
|
1067
836
|
const changeOfState = this._connected !== connected;
|
|
1068
|
-
const reconnection = changeOfState && connected;
|
|
837
|
+
const reconnection = changeOfState && !connected;
|
|
1069
838
|
this._connected = connected;
|
|
1070
839
|
if (!connected) {
|
|
1071
840
|
this._perfSignalData.signalsLost = 0;
|
|
1072
841
|
this._perfSignalData.signalTimestamp = 0;
|
|
1073
842
|
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
1074
843
|
}
|
|
844
|
+
// Fail while disconnected
|
|
1075
845
|
if (reconnection) {
|
|
1076
846
|
this.consecutiveReconnects++;
|
|
1077
847
|
if (!this.shouldContinueReconnecting()) {
|
|
@@ -1303,59 +1073,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1303
1073
|
}
|
|
1304
1074
|
async createDataStore(pkg) {
|
|
1305
1075
|
const internalId = uuid();
|
|
1306
|
-
return channelToDataStore(await this._createDataStore(pkg,
|
|
1307
|
-
}
|
|
1308
|
-
/**
|
|
1309
|
-
* Creates a root datastore directly with a user generated id and attaches it to storage.
|
|
1310
|
-
* It is vulnerable to name collisions and should not be used.
|
|
1311
|
-
*
|
|
1312
|
-
* This method will be removed. See #6465.
|
|
1313
|
-
*/
|
|
1314
|
-
async createRootDataStoreLegacy(pkg, rootDataStoreId) {
|
|
1315
|
-
const fluidDataStore = await this._createDataStore(pkg, true /* isRoot */, rootDataStoreId);
|
|
1316
|
-
fluidDataStore.makeVisibleAndAttachGraph();
|
|
1317
|
-
return fluidDataStore;
|
|
1318
|
-
}
|
|
1319
|
-
/**
|
|
1320
|
-
* @deprecated - will be removed in an upcoming release. See #9660.
|
|
1321
|
-
*/
|
|
1322
|
-
async createRootDataStore(pkg, rootDataStoreId) {
|
|
1323
|
-
if (rootDataStoreId.includes("/")) {
|
|
1324
|
-
throw new UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
|
|
1325
|
-
}
|
|
1326
|
-
return this._aliasingEnabled === true ?
|
|
1327
|
-
this.createAndAliasDataStore(pkg, rootDataStoreId) :
|
|
1328
|
-
this.createRootDataStoreLegacy(pkg, rootDataStoreId);
|
|
1329
|
-
}
|
|
1330
|
-
/**
|
|
1331
|
-
* Creates a data store then attempts to alias it.
|
|
1332
|
-
* If aliasing fails, it will raise an exception.
|
|
1333
|
-
*
|
|
1334
|
-
* This method will be removed. See #6465.
|
|
1335
|
-
*
|
|
1336
|
-
* @param pkg - Package name of the data store
|
|
1337
|
-
* @param alias - Alias to be assigned to the data store
|
|
1338
|
-
* @param props - Properties for the data store
|
|
1339
|
-
* @returns - An aliased data store which can can be found / loaded by alias.
|
|
1340
|
-
*/
|
|
1341
|
-
async createAndAliasDataStore(pkg, alias, props) {
|
|
1342
|
-
const internalId = uuid();
|
|
1343
|
-
try {
|
|
1344
|
-
// A similar call may have been initiated by the same client, so we should try to get
|
|
1345
|
-
// a possible existing aliased datastore first.
|
|
1346
|
-
const existingDataStore = await this.getRootDataStoreChannel(alias, /* wait */ false);
|
|
1347
|
-
return channelToDataStore(existingDataStore, internalId, this, this.dataStores, this.mc.logger, true);
|
|
1348
|
-
}
|
|
1349
|
-
catch (err) {
|
|
1350
|
-
const newChannel = await this._createDataStore(pkg, false /* isRoot */, internalId, props);
|
|
1351
|
-
const newDataStore = channelToDataStore(newChannel, internalId, this, this.dataStores, this.mc.logger);
|
|
1352
|
-
const aliasResult = await newDataStore.trySetAlias(alias);
|
|
1353
|
-
if (aliasResult === "Success") {
|
|
1354
|
-
return newDataStore;
|
|
1355
|
-
}
|
|
1356
|
-
const existingDataStore = await this.getRootDataStoreChannel(alias, /* wait */ false);
|
|
1357
|
-
return channelToDataStore(existingDataStore, internalId, this, this.dataStores, this.mc.logger, true);
|
|
1358
|
-
}
|
|
1076
|
+
return channelToDataStore(await this._createDataStore(pkg, internalId), internalId, this, this.dataStores, this.mc.logger);
|
|
1359
1077
|
}
|
|
1360
1078
|
createDetachedRootDataStore(pkg, rootDataStoreId) {
|
|
1361
1079
|
if (rootDataStoreId.includes("/")) {
|
|
@@ -1366,31 +1084,13 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1366
1084
|
createDetachedDataStore(pkg) {
|
|
1367
1085
|
return this.dataStores.createDetachedDataStoreCore(pkg, false);
|
|
1368
1086
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
* It is vulnerable to name collisions if both aforementioned conditions are true, and should not be used.
|
|
1372
|
-
*
|
|
1373
|
-
* This method will be removed. See #6465.
|
|
1374
|
-
*/
|
|
1375
|
-
async _createDataStoreWithPropsLegacy(pkg, props, id = uuid(), isRoot = false) {
|
|
1376
|
-
const fluidDataStore = await this.dataStores._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
|
|
1377
|
-
if (isRoot) {
|
|
1378
|
-
fluidDataStore.makeVisibleAndAttachGraph();
|
|
1379
|
-
this.logger.sendTelemetryEvent({
|
|
1380
|
-
eventName: "Root datastore with props",
|
|
1381
|
-
hasProps: props !== undefined,
|
|
1382
|
-
});
|
|
1383
|
-
}
|
|
1087
|
+
async _createDataStoreWithProps(pkg, props, id = uuid()) {
|
|
1088
|
+
const fluidDataStore = await this.dataStores._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props).realize();
|
|
1384
1089
|
return channelToDataStore(fluidDataStore, id, this, this.dataStores, this.mc.logger);
|
|
1385
1090
|
}
|
|
1386
|
-
async
|
|
1387
|
-
return this._aliasingEnabled === true && isRoot ?
|
|
1388
|
-
this.createAndAliasDataStore(pkg, id, props) :
|
|
1389
|
-
this._createDataStoreWithPropsLegacy(pkg, props, id, isRoot);
|
|
1390
|
-
}
|
|
1391
|
-
async _createDataStore(pkg, isRoot, id = uuid(), props) {
|
|
1091
|
+
async _createDataStore(pkg, id = uuid(), props) {
|
|
1392
1092
|
return this.dataStores
|
|
1393
|
-
._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id,
|
|
1093
|
+
._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
|
|
1394
1094
|
.realize();
|
|
1395
1095
|
}
|
|
1396
1096
|
canSendOps() {
|
|
@@ -1482,10 +1182,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1482
1182
|
this.blobManager.setRedirectTable(blobRedirectTable);
|
|
1483
1183
|
}
|
|
1484
1184
|
const summarizeResult = this.dataStores.createSummary(telemetryContext);
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
wrapSummaryInChannelsTree(summarizeResult);
|
|
1488
|
-
}
|
|
1185
|
+
// Wrap data store summaries in .channels subtree.
|
|
1186
|
+
wrapSummaryInChannelsTree(summarizeResult);
|
|
1489
1187
|
this.addContainerStateToSummary(summarizeResult, true /* fullTree */, false /* trackState */, telemetryContext);
|
|
1490
1188
|
return summarizeResult.summary;
|
|
1491
1189
|
}
|
|
@@ -1500,12 +1198,9 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1500
1198
|
}
|
|
1501
1199
|
async summarizeInternal(fullTree, trackState, telemetryContext) {
|
|
1502
1200
|
const summarizeResult = await this.dataStores.summarize(fullTree, trackState, telemetryContext);
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
wrapSummaryInChannelsTree(summarizeResult);
|
|
1507
|
-
pathPartsForChildren = [channelsTreeName];
|
|
1508
|
-
}
|
|
1201
|
+
// Wrap data store summaries in .channels subtree.
|
|
1202
|
+
wrapSummaryInChannelsTree(summarizeResult);
|
|
1203
|
+
const pathPartsForChildren = [channelsTreeName];
|
|
1509
1204
|
this.addContainerStateToSummary(summarizeResult, fullTree, trackState, telemetryContext);
|
|
1510
1205
|
return Object.assign(Object.assign({}, summarizeResult), { id: "", pathPartsForChildren });
|
|
1511
1206
|
}
|
|
@@ -1664,16 +1359,19 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1664
1359
|
const summaryNumberLogger = ChildLogger.create(summaryLogger, undefined, {
|
|
1665
1360
|
all: { summaryNumber },
|
|
1666
1361
|
});
|
|
1362
|
+
let latestSnapshotVersionId;
|
|
1667
1363
|
if (refreshLatestAck) {
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1364
|
+
const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
|
|
1365
|
+
const latestSnapshotRefSeq = latestSnapshotInfo.latestSnapshotRefSeq;
|
|
1366
|
+
latestSnapshotVersionId = latestSnapshotInfo.latestSnapshotVersionId;
|
|
1367
|
+
if (latestSnapshotRefSeq > this.deltaManager.lastSequenceNumber) {
|
|
1670
1368
|
// We need to catch up to the latest summary's reference sequence number before pausing.
|
|
1671
1369
|
await PerformanceEvent.timedExecAsync(summaryNumberLogger, {
|
|
1672
1370
|
eventName: "WaitingForSeq",
|
|
1673
1371
|
lastSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
1674
|
-
targetSequenceNumber:
|
|
1372
|
+
targetSequenceNumber: latestSnapshotRefSeq,
|
|
1675
1373
|
lastKnownSeqNumber: this.deltaManager.lastKnownSeqNumber,
|
|
1676
|
-
}, async () => waitForSeq(this.deltaManager,
|
|
1374
|
+
}, async () => waitForSeq(this.deltaManager, latestSnapshotRefSeq), { start: true, end: true, cancel: "error" });
|
|
1677
1375
|
}
|
|
1678
1376
|
}
|
|
1679
1377
|
try {
|
|
@@ -1709,7 +1407,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1709
1407
|
error: `lastSequenceNumber changed before uploading to storage. ${this.deltaManager.lastSequenceNumber} !== ${summaryRefSeqNum}`,
|
|
1710
1408
|
};
|
|
1711
1409
|
}
|
|
1712
|
-
assert(summaryRefSeqNum === ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber),
|
|
1410
|
+
assert(summaryRefSeqNum === ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber), 0x395 /* it's one and the same thing */);
|
|
1713
1411
|
if (lastAck !== this.summaryCollection.latestAck) {
|
|
1714
1412
|
return {
|
|
1715
1413
|
continue: false,
|
|
@@ -1755,7 +1453,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1755
1453
|
// Counting dataStores and handles
|
|
1756
1454
|
// Because handles are unchanged dataStores in the current logic,
|
|
1757
1455
|
// summarized dataStore count is total dataStore count minus handle count
|
|
1758
|
-
const dataStoreTree =
|
|
1456
|
+
const dataStoreTree = summaryTree.tree[channelsTreeName];
|
|
1759
1457
|
assert(dataStoreTree.type === SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
|
|
1760
1458
|
const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === SummaryType.Handle).length;
|
|
1761
1459
|
const gcSummaryTreeStats = summaryTree.tree[gcTreeKey]
|
|
@@ -1774,17 +1472,34 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1774
1472
|
if (!continueResult.continue) {
|
|
1775
1473
|
return Object.assign(Object.assign({ stage: "generate" }, generateSummaryData), { error: continueResult.error });
|
|
1776
1474
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1475
|
+
// It may happen that the lastAck it not correct due to missing summaryAck in case of single commit
|
|
1476
|
+
// summary. So if the previous summarizer closes just after submitting the summary and before
|
|
1477
|
+
// submitting the summaryOp then we can't rely on summaryAck. So in case we have
|
|
1478
|
+
// latestSnapshotVersionId from storage and it does not match with the lastAck ackHandle, then use
|
|
1479
|
+
// the one fetched from storage as parent as that is the latest.
|
|
1480
|
+
let summaryContext;
|
|
1481
|
+
if ((lastAck === null || lastAck === void 0 ? void 0 : lastAck.summaryAck.contents.handle) !== latestSnapshotVersionId
|
|
1482
|
+
&& latestSnapshotVersionId !== undefined) {
|
|
1483
|
+
summaryContext = {
|
|
1484
|
+
proposalHandle: undefined,
|
|
1485
|
+
ackHandle: latestSnapshotVersionId,
|
|
1486
|
+
referenceSequenceNumber: summaryRefSeqNum,
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
else if (lastAck === undefined) {
|
|
1490
|
+
summaryContext = {
|
|
1779
1491
|
proposalHandle: undefined,
|
|
1780
1492
|
ackHandle: (_b = this.context.getLoadedFromVersion()) === null || _b === void 0 ? void 0 : _b.id,
|
|
1781
1493
|
referenceSequenceNumber: summaryRefSeqNum,
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
else {
|
|
1497
|
+
summaryContext = {
|
|
1784
1498
|
proposalHandle: lastAck.summaryOp.contents.handle,
|
|
1785
1499
|
ackHandle: lastAck.summaryAck.contents.handle,
|
|
1786
1500
|
referenceSequenceNumber: summaryRefSeqNum,
|
|
1787
1501
|
};
|
|
1502
|
+
}
|
|
1788
1503
|
let handle;
|
|
1789
1504
|
try {
|
|
1790
1505
|
handle = await this.storage.uploadSummaryWithContext(summarizeResult.summary, summaryContext);
|
|
@@ -1891,7 +1606,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1891
1606
|
let opMetadataInternal = opMetadata;
|
|
1892
1607
|
if (this.canSendOps()) {
|
|
1893
1608
|
const serializedContent = JSON.stringify(content);
|
|
1894
|
-
const maxOpSize = this.context.deltaManager.maxMessageSize;
|
|
1895
1609
|
// If in TurnBased flush mode we will trigger a flush at the next turn break
|
|
1896
1610
|
if (this.flushMode === FlushMode.TurnBased && !this.needsFlush) {
|
|
1897
1611
|
opMetadataInternal = Object.assign(Object.assign({}, opMetadata), { batch: true });
|
|
@@ -1905,7 +1619,19 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1905
1619
|
});
|
|
1906
1620
|
}
|
|
1907
1621
|
}
|
|
1908
|
-
|
|
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
|
+
}
|
|
1909
1635
|
}
|
|
1910
1636
|
// Let the PendingStateManager know that a message was submitted.
|
|
1911
1637
|
this.pendingStateManager.onSubmitMessage(type, clientSequenceNumber, this.deltaManager.lastSequenceNumber, content, localOpMetadata, opMetadataInternal);
|
|
@@ -1913,46 +1639,6 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1913
1639
|
this.updateDocumentDirtyState(true);
|
|
1914
1640
|
}
|
|
1915
1641
|
}
|
|
1916
|
-
submitMaybeChunkedMessages(type, content, serializedContent, serverMaxOpSize, batch, opMetadataInternal = undefined) {
|
|
1917
|
-
if (this._maxOpSizeInBytes >= 0) {
|
|
1918
|
-
// Chunking disabled
|
|
1919
|
-
if (!serializedContent || serializedContent.length <= this._maxOpSizeInBytes) {
|
|
1920
|
-
return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
|
|
1921
|
-
}
|
|
1922
|
-
// When chunking is disabled, we ignore the server max message size
|
|
1923
|
-
// and if the content length is larger than the client configured message size
|
|
1924
|
-
// instead of splitting the content, we will fail by explicitly close the container
|
|
1925
|
-
this.closeFn(new GenericError("OpTooLarge",
|
|
1926
|
-
/* error */ undefined, {
|
|
1927
|
-
length: serializedContent.length,
|
|
1928
|
-
limit: this._maxOpSizeInBytes,
|
|
1929
|
-
}));
|
|
1930
|
-
return -1;
|
|
1931
|
-
}
|
|
1932
|
-
// Chunking enabled, fallback on the server's max message size
|
|
1933
|
-
// and split the content accordingly
|
|
1934
|
-
if (!serializedContent || serializedContent.length <= serverMaxOpSize) {
|
|
1935
|
-
return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
|
|
1936
|
-
}
|
|
1937
|
-
return this.submitChunkedMessage(type, serializedContent, serverMaxOpSize);
|
|
1938
|
-
}
|
|
1939
|
-
submitChunkedMessage(type, content, maxOpSize) {
|
|
1940
|
-
const contentLength = content.length;
|
|
1941
|
-
const chunkN = Math.floor((contentLength - 1) / maxOpSize) + 1;
|
|
1942
|
-
let offset = 0;
|
|
1943
|
-
let clientSequenceNumber = 0;
|
|
1944
|
-
for (let i = 1; i <= chunkN; i = i + 1) {
|
|
1945
|
-
const chunkedOp = {
|
|
1946
|
-
chunkId: i,
|
|
1947
|
-
contents: content.substr(offset, maxOpSize),
|
|
1948
|
-
originalType: type,
|
|
1949
|
-
totalChunks: chunkN,
|
|
1950
|
-
};
|
|
1951
|
-
offset += maxOpSize;
|
|
1952
|
-
clientSequenceNumber = this.submitRuntimeMessage(ContainerMessageType.ChunkedOp, chunkedOp, false);
|
|
1953
|
-
}
|
|
1954
|
-
return clientSequenceNumber;
|
|
1955
|
-
}
|
|
1956
1642
|
submitSystemMessage(type, contents) {
|
|
1957
1643
|
this.verifyNotClosed();
|
|
1958
1644
|
assert(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
|
|
@@ -2023,12 +1709,13 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2023
1709
|
/** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
|
|
2024
1710
|
async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
|
|
2025
1711
|
const readAndParseBlob = async (id) => readAndParse(this.storage, id);
|
|
2026
|
-
const
|
|
1712
|
+
const { snapshotTree } = await this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
|
|
2027
1713
|
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
2028
1714
|
ackHandle,
|
|
2029
1715
|
summaryRefSeq,
|
|
2030
1716
|
fetchLatest: false,
|
|
2031
|
-
})
|
|
1717
|
+
});
|
|
1718
|
+
const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, async () => snapshotTree, readAndParseBlob, summaryLogger);
|
|
2032
1719
|
// Notify the garbage collector so it can update its latest summary state.
|
|
2033
1720
|
await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
|
|
2034
1721
|
}
|
|
@@ -2039,16 +1726,16 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2039
1726
|
* @returns downloaded snapshot's reference sequence number
|
|
2040
1727
|
*/
|
|
2041
1728
|
async refreshLatestSummaryAckFromServer(summaryLogger) {
|
|
2042
|
-
const
|
|
1729
|
+
const { snapshotTree, versionId } = await this.fetchSnapshotFromStorage(null, summaryLogger, {
|
|
2043
1730
|
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
2044
1731
|
fetchLatest: true,
|
|
2045
1732
|
}, FetchSource.noCache);
|
|
2046
1733
|
const readAndParseBlob = async (id) => readAndParse(this.storage, id);
|
|
2047
|
-
const
|
|
2048
|
-
const result = await this.summarizerNode.refreshLatestSummary(undefined,
|
|
1734
|
+
const latestSnapshotRefSeq = await seqFromTree(snapshotTree, readAndParseBlob);
|
|
1735
|
+
const result = await this.summarizerNode.refreshLatestSummary(undefined, latestSnapshotRefSeq, async () => snapshotTree, readAndParseBlob, summaryLogger);
|
|
2049
1736
|
// Notify the garbage collector so it can update its latest summary state.
|
|
2050
1737
|
await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
|
|
2051
|
-
return
|
|
1738
|
+
return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
|
|
2052
1739
|
}
|
|
2053
1740
|
async fetchSnapshotFromStorage(versionId, logger, event, fetchSource) {
|
|
2054
1741
|
return PerformanceEvent.timedExecAsync(logger, event, async (perfEvent) => {
|
|
@@ -2061,7 +1748,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2061
1748
|
assert(!!maybeSnapshot, 0x138 /* "Failed to get snapshot from storage" */);
|
|
2062
1749
|
stats.getSnapshotDuration = trace.trace().duration;
|
|
2063
1750
|
perfEvent.end(stats);
|
|
2064
|
-
return maybeSnapshot;
|
|
1751
|
+
return { snapshotTree: maybeSnapshot, versionId: versions[0].id };
|
|
2065
1752
|
});
|
|
2066
1753
|
}
|
|
2067
1754
|
notifyAttaching(snapshot) {
|
|
@@ -2088,6 +1775,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2088
1775
|
if (previousPendingState) {
|
|
2089
1776
|
return {
|
|
2090
1777
|
pending: this.pendingStateManager.getLocalState(),
|
|
1778
|
+
pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
|
|
2091
1779
|
snapshotBlobs: previousPendingState.snapshotBlobs,
|
|
2092
1780
|
baseSnapshot: previousPendingState.baseSnapshot,
|
|
2093
1781
|
savedOps: this.savedOps,
|
|
@@ -2097,6 +1785,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2097
1785
|
assert(!!this.baseSnapshotBlobs, 0x2e7 /* "Must serialize base snapshot blobs before getting runtime state" */);
|
|
2098
1786
|
return {
|
|
2099
1787
|
pending: this.pendingStateManager.getLocalState(),
|
|
1788
|
+
pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
|
|
2100
1789
|
snapshotBlobs: this.baseSnapshotBlobs,
|
|
2101
1790
|
baseSnapshot: this.context.baseSnapshot,
|
|
2102
1791
|
savedOps: this.savedOps,
|