@fluidframework/container-runtime 1.3.0 → 2.0.0-dev.1.4.5.105745
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/.eslintrc.js +8 -22
- package/.mocharc.js +12 -0
- package/dist/batchManager.d.ts +37 -0
- package/dist/batchManager.d.ts.map +1 -0
- package/dist/batchManager.js +73 -0
- package/dist/batchManager.js.map +1 -0
- package/dist/batchTracker.d.ts +1 -2
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js +2 -3
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +87 -25
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +317 -99
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +110 -125
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +360 -549
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.js +29 -24
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +20 -14
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +49 -58
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +12 -5
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +21 -20
- package/dist/dataStores.js.map +1 -1
- package/dist/deltaScheduler.d.ts +6 -4
- package/dist/deltaScheduler.d.ts.map +1 -1
- package/dist/deltaScheduler.js +6 -4
- package/dist/deltaScheduler.js.map +1 -1
- package/dist/garbageCollection.d.ts +74 -14
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +248 -169
- package/dist/garbageCollection.js.map +1 -1
- package/dist/gcSweepReadyUsageDetection.d.ts +53 -0
- package/dist/gcSweepReadyUsageDetection.d.ts.map +1 -0
- package/dist/gcSweepReadyUsageDetection.js +135 -0
- package/dist/gcSweepReadyUsageDetection.js.map +1 -0
- 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/opProperties.d.ts +7 -0
- package/dist/opProperties.d.ts.map +1 -0
- package/dist/opProperties.js +20 -0
- package/dist/opProperties.js.map +1 -0
- package/dist/orderedClientElection.d.ts +28 -10
- package/dist/orderedClientElection.d.ts.map +1 -1
- package/dist/orderedClientElection.js +14 -4
- package/dist/orderedClientElection.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 +0 -11
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +24 -46
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runningSummarizer.d.ts +14 -4
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +69 -27
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/scheduleManager.d.ts +31 -0
- package/dist/scheduleManager.d.ts.map +1 -0
- package/dist/scheduleManager.js +243 -0
- package/dist/scheduleManager.js.map +1 -0
- package/dist/summarizer.d.ts +0 -2
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +1 -12
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerHeuristics.d.ts +26 -4
- package/dist/summarizerHeuristics.d.ts.map +1 -1
- package/dist/summarizerHeuristics.js +98 -18
- package/dist/summarizerHeuristics.js.map +1 -1
- package/dist/summarizerTypes.d.ts +45 -18
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryCollection.d.ts +1 -0
- package/dist/summaryCollection.d.ts.map +1 -1
- package/dist/summaryCollection.js +31 -15
- package/dist/summaryCollection.js.map +1 -1
- package/dist/summaryFormat.d.ts +0 -5
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js.map +1 -1
- package/dist/summaryGenerator.d.ts +1 -0
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +11 -9
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts +2 -2
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +22 -7
- package/dist/summaryManager.js.map +1 -1
- package/lib/batchManager.d.ts +37 -0
- package/lib/batchManager.d.ts.map +1 -0
- package/lib/batchManager.js +69 -0
- package/lib/batchManager.js.map +1 -0
- package/lib/batchTracker.d.ts +1 -2
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js +2 -3
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +87 -25
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +319 -101
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +110 -125
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +366 -554
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.js +29 -24
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +20 -14
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +46 -55
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +12 -5
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +21 -20
- package/lib/dataStores.js.map +1 -1
- package/lib/deltaScheduler.d.ts +6 -4
- package/lib/deltaScheduler.d.ts.map +1 -1
- package/lib/deltaScheduler.js +6 -4
- package/lib/deltaScheduler.js.map +1 -1
- package/lib/garbageCollection.d.ts +74 -14
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +237 -159
- package/lib/garbageCollection.js.map +1 -1
- package/lib/gcSweepReadyUsageDetection.d.ts +53 -0
- package/lib/gcSweepReadyUsageDetection.d.ts.map +1 -0
- package/lib/gcSweepReadyUsageDetection.js +130 -0
- package/lib/gcSweepReadyUsageDetection.js.map +1 -0
- 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/opProperties.d.ts +7 -0
- package/lib/opProperties.d.ts.map +1 -0
- package/lib/opProperties.js +16 -0
- package/lib/opProperties.js.map +1 -0
- package/lib/orderedClientElection.d.ts +28 -10
- package/lib/orderedClientElection.d.ts.map +1 -1
- package/lib/orderedClientElection.js +14 -4
- package/lib/orderedClientElection.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 +0 -11
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +24 -46
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runningSummarizer.d.ts +14 -4
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +69 -27
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/scheduleManager.d.ts +31 -0
- package/lib/scheduleManager.d.ts.map +1 -0
- package/lib/scheduleManager.js +239 -0
- package/lib/scheduleManager.js.map +1 -0
- package/lib/summarizer.d.ts +0 -2
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +1 -12
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerHeuristics.d.ts +26 -4
- package/lib/summarizerHeuristics.d.ts.map +1 -1
- package/lib/summarizerHeuristics.js +98 -18
- package/lib/summarizerHeuristics.js.map +1 -1
- package/lib/summarizerTypes.d.ts +45 -18
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryCollection.d.ts +1 -0
- package/lib/summaryCollection.d.ts.map +1 -1
- package/lib/summaryCollection.js +31 -15
- package/lib/summaryCollection.js.map +1 -1
- package/lib/summaryFormat.d.ts +0 -5
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js.map +1 -1
- package/lib/summaryGenerator.d.ts +1 -0
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +11 -9
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts +2 -2
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +22 -7
- package/lib/summaryManager.js.map +1 -1
- package/package.json +68 -25
- package/src/batchManager.ts +91 -0
- package/src/batchTracker.ts +2 -3
- package/src/blobManager.ts +385 -118
- package/src/containerRuntime.ts +523 -732
- package/src/dataStore.ts +49 -37
- package/src/dataStoreContext.ts +44 -56
- package/src/dataStores.ts +34 -30
- package/src/deltaScheduler.ts +6 -4
- package/src/garbageCollection.ts +296 -205
- package/src/gcSweepReadyUsageDetection.ts +147 -0
- package/src/index.ts +1 -2
- package/src/opProperties.ts +19 -0
- package/src/orderedClientElection.ts +31 -10
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +27 -59
- package/src/runningSummarizer.ts +76 -23
- package/src/scheduleManager.ts +314 -0
- package/src/summarizer.ts +1 -18
- package/src/summarizerHeuristics.ts +136 -19
- package/src/summarizerTypes.ts +53 -18
- package/src/summaryCollection.ts +33 -18
- package/src/summaryFormat.ts +0 -6
- package/src/summaryGenerator.ts +40 -22
- package/src/summaryManager.ts +22 -7
- package/dist/opTelemetry.d.ts +0 -22
- package/dist/opTelemetry.d.ts.map +0 -1
- package/dist/opTelemetry.js +0 -59
- package/dist/opTelemetry.js.map +0 -1
- package/lib/opTelemetry.d.ts +0 -22
- package/lib/opTelemetry.d.ts.map +0 -1
- package/lib/opTelemetry.js +0 -55
- package/lib/opTelemetry.js.map +0 -1
- package/src/opTelemetry.ts +0 -71
package/dist/containerRuntime.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.
|
|
3
|
+
exports.ContainerRuntime = exports.getDeviceSpec = exports.agentSchedulerId = exports.unpackRuntimeMessage = exports.isRuntimeMessage = exports.RuntimeMessage = exports.RuntimeHeaders = exports.DefaultSummaryConfiguration = exports.ContainerMessageType = void 0;
|
|
4
4
|
const container_definitions_1 = require("@fluidframework/container-definitions");
|
|
5
5
|
const common_utils_1 = require("@fluidframework/common-utils");
|
|
6
6
|
const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
|
|
@@ -16,9 +16,9 @@ const containerHandleContext_1 = require("./containerHandleContext");
|
|
|
16
16
|
const dataStoreRegistry_1 = require("./dataStoreRegistry");
|
|
17
17
|
const summarizer_1 = require("./summarizer");
|
|
18
18
|
const summaryManager_1 = require("./summaryManager");
|
|
19
|
-
const deltaScheduler_1 = require("./deltaScheduler");
|
|
20
19
|
const connectionTelemetry_1 = require("./connectionTelemetry");
|
|
21
20
|
const pendingStateManager_1 = require("./pendingStateManager");
|
|
21
|
+
const batchManager_1 = require("./batchManager");
|
|
22
22
|
const packageVersion_1 = require("./packageVersion");
|
|
23
23
|
const blobManager_1 = require("./blobManager");
|
|
24
24
|
const dataStores_1 = require("./dataStores");
|
|
@@ -32,7 +32,7 @@ const garbageCollection_1 = require("./garbageCollection");
|
|
|
32
32
|
const dataStore_1 = require("./dataStore");
|
|
33
33
|
const batchTracker_1 = require("./batchTracker");
|
|
34
34
|
const serializedSnapshotStorage_1 = require("./serializedSnapshotStorage");
|
|
35
|
-
const
|
|
35
|
+
const scheduleManager_1 = require("./scheduleManager");
|
|
36
36
|
var ContainerMessageType;
|
|
37
37
|
(function (ContainerMessageType) {
|
|
38
38
|
// An op to be delivered to store
|
|
@@ -50,14 +50,17 @@ var ContainerMessageType;
|
|
|
50
50
|
})(ContainerMessageType = exports.ContainerMessageType || (exports.ContainerMessageType = {}));
|
|
51
51
|
exports.DefaultSummaryConfiguration = {
|
|
52
52
|
state: "enabled",
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
minIdleTime: 0,
|
|
54
|
+
maxIdleTime: 30 * 1000,
|
|
55
|
+
maxTime: 60 * 1000,
|
|
55
56
|
maxOps: 100,
|
|
56
57
|
minOpsForLastSummaryAttempt: 10,
|
|
57
|
-
maxAckWaitTime:
|
|
58
|
+
maxAckWaitTime: 10 * 60 * 1000,
|
|
58
59
|
maxOpsSinceLastSummary: 7000,
|
|
59
|
-
initialSummarizerDelayMs:
|
|
60
|
+
initialSummarizerDelayMs: 5 * 1000,
|
|
60
61
|
summarizerClientElection: false,
|
|
62
|
+
nonRuntimeOpWeight: 0.1,
|
|
63
|
+
runtimeOpWeight: 1.0,
|
|
61
64
|
};
|
|
62
65
|
/**
|
|
63
66
|
* Accepted header keys for requests coming to the runtime.
|
|
@@ -74,21 +77,11 @@ var RuntimeHeaders;
|
|
|
74
77
|
/** True if the request is coming from an IFluidHandle. */
|
|
75
78
|
RuntimeHeaders["viaHandle"] = "viaHandle";
|
|
76
79
|
})(RuntimeHeaders = exports.RuntimeHeaders || (exports.RuntimeHeaders = {}));
|
|
77
|
-
const useDataStoreAliasingKey = "Fluid.ContainerRuntime.UseDataStoreAliasing";
|
|
78
80
|
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
|
-
// By default, we should reject any op larger than 768KB,
|
|
84
|
-
// in order to account for some extra overhead from serialization
|
|
85
|
-
// to not reach the 1MB limits in socket.io and Kafka.
|
|
86
|
-
const defaultMaxOpSizeInBytes = 768000;
|
|
87
|
-
// By default, the size of the contents for the incoming ops is tracked.
|
|
88
|
-
// However, in certain situations, this may incur a performance hit.
|
|
89
|
-
// The feature-gate below can be used to disable this feature.
|
|
90
|
-
const disableOpTrackingKey = "Fluid.ContainerRuntime.DisableOpTracking";
|
|
91
81
|
const defaultFlushMode = runtime_definitions_1.FlushMode.TurnBased;
|
|
82
|
+
/**
|
|
83
|
+
* @deprecated - use ContainerRuntimeMessage instead
|
|
84
|
+
*/
|
|
92
85
|
var RuntimeMessage;
|
|
93
86
|
(function (RuntimeMessage) {
|
|
94
87
|
RuntimeMessage["FluidDataStoreOp"] = "component";
|
|
@@ -99,6 +92,9 @@ var RuntimeMessage;
|
|
|
99
92
|
RuntimeMessage["Alias"] = "alias";
|
|
100
93
|
RuntimeMessage["Operation"] = "op";
|
|
101
94
|
})(RuntimeMessage = exports.RuntimeMessage || (exports.RuntimeMessage = {}));
|
|
95
|
+
/**
|
|
96
|
+
* @deprecated - please use version in driver-utils
|
|
97
|
+
*/
|
|
102
98
|
function isRuntimeMessage(message) {
|
|
103
99
|
if (Object.values(RuntimeMessage).includes(message.type)) {
|
|
104
100
|
return true;
|
|
@@ -106,6 +102,15 @@ function isRuntimeMessage(message) {
|
|
|
106
102
|
return false;
|
|
107
103
|
}
|
|
108
104
|
exports.isRuntimeMessage = isRuntimeMessage;
|
|
105
|
+
/**
|
|
106
|
+
* Unpacks runtime messages
|
|
107
|
+
*
|
|
108
|
+
* @remarks This API makes no promises regarding backward-compatability. This is internal API.
|
|
109
|
+
* @param message - message (as it observed in storage / service)
|
|
110
|
+
* @returns unpacked runtime message
|
|
111
|
+
*
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
109
114
|
function unpackRuntimeMessage(message) {
|
|
110
115
|
if (message.type === protocol_definitions_1.MessageType.Operation) {
|
|
111
116
|
// legacy op format?
|
|
@@ -119,240 +124,17 @@ function unpackRuntimeMessage(message) {
|
|
|
119
124
|
message.type = innerContents.type;
|
|
120
125
|
message.contents = innerContents.contents;
|
|
121
126
|
}
|
|
122
|
-
|
|
127
|
+
return true;
|
|
123
128
|
}
|
|
124
129
|
else {
|
|
125
130
|
// Legacy format, but it's already "unpacked",
|
|
126
131
|
// i.e. message.type is actually ContainerMessageType.
|
|
132
|
+
// Or it's non-runtime message.
|
|
127
133
|
// Nothing to do in such case.
|
|
134
|
+
return false;
|
|
128
135
|
}
|
|
129
|
-
return message;
|
|
130
136
|
}
|
|
131
137
|
exports.unpackRuntimeMessage = unpackRuntimeMessage;
|
|
132
|
-
/**
|
|
133
|
-
* This class controls pausing and resuming of inbound queue to ensure that we never
|
|
134
|
-
* start processing ops in a batch IF we do not have all ops in the batch.
|
|
135
|
-
*/
|
|
136
|
-
class ScheduleManagerCore {
|
|
137
|
-
constructor(deltaManager, logger) {
|
|
138
|
-
this.deltaManager = deltaManager;
|
|
139
|
-
this.logger = logger;
|
|
140
|
-
this.localPaused = false;
|
|
141
|
-
this.timePaused = 0;
|
|
142
|
-
this.batchCount = 0;
|
|
143
|
-
// Listen for delta manager sends and add batch metadata to messages
|
|
144
|
-
this.deltaManager.on("prepareSend", (messages) => {
|
|
145
|
-
if (messages.length === 0) {
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
// First message will have the batch flag set to true if doing a batched send
|
|
149
|
-
const firstMessageMetadata = messages[0].metadata;
|
|
150
|
-
if (!(firstMessageMetadata === null || firstMessageMetadata === void 0 ? void 0 : firstMessageMetadata.batch)) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
// If the batch contains only a single op, clear the batch flag.
|
|
154
|
-
if (messages.length === 1) {
|
|
155
|
-
delete firstMessageMetadata.batch;
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
// Set the batch flag to false on the last message to indicate the end of the send batch
|
|
159
|
-
const lastMessage = messages[messages.length - 1];
|
|
160
|
-
lastMessage.metadata = Object.assign(Object.assign({}, lastMessage.metadata), { batch: false });
|
|
161
|
-
});
|
|
162
|
-
// Listen for updates and peek at the inbound
|
|
163
|
-
this.deltaManager.inbound.on("push", (message) => {
|
|
164
|
-
this.trackPending(message);
|
|
165
|
-
});
|
|
166
|
-
// Start with baseline - empty inbound queue.
|
|
167
|
-
(0, common_utils_1.assert)(!this.localPaused, 0x293 /* "initial state" */);
|
|
168
|
-
const allPending = this.deltaManager.inbound.toArray();
|
|
169
|
-
for (const pending of allPending) {
|
|
170
|
-
this.trackPending(pending);
|
|
171
|
-
}
|
|
172
|
-
// We are intentionally directly listening to the "op" to inspect system ops as well.
|
|
173
|
-
// If we do not observe system ops, we are likely to hit 0x296 assert when system ops
|
|
174
|
-
// precedes start of incomplete batch.
|
|
175
|
-
this.deltaManager.on("op", (message) => this.afterOpProcessing(message.sequenceNumber));
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* The only public function in this class - called when we processed an op,
|
|
179
|
-
* to make decision if op processing should be paused or not afer that.
|
|
180
|
-
*/
|
|
181
|
-
afterOpProcessing(sequenceNumber) {
|
|
182
|
-
(0, common_utils_1.assert)(!this.localPaused, 0x294 /* "can't have op processing paused if we are processing an op" */);
|
|
183
|
-
// If the inbound queue is ever empty, nothing to do!
|
|
184
|
-
if (this.deltaManager.inbound.length === 0) {
|
|
185
|
-
(0, common_utils_1.assert)(this.pauseSequenceNumber === undefined, 0x295 /* "there should be no pending batch if we have no ops" */);
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
// The queue is
|
|
189
|
-
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
190
|
-
// - here (processing ops until reaching start of incomplete batch)
|
|
191
|
-
// - in trackPending(), when queue was empty and start of batch showed up.
|
|
192
|
-
// 2. resumed when batch end comes in (in trackPending())
|
|
193
|
-
// do we have incomplete batch to worry about?
|
|
194
|
-
if (this.pauseSequenceNumber !== undefined) {
|
|
195
|
-
(0, common_utils_1.assert)(sequenceNumber < this.pauseSequenceNumber, 0x296 /* "we should never start processing incomplete batch!" */);
|
|
196
|
-
// If the next op is the start of incomplete batch, then we can't process it until it's fully in - pause!
|
|
197
|
-
if (sequenceNumber + 1 === this.pauseSequenceNumber) {
|
|
198
|
-
this.pauseQueue();
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
pauseQueue() {
|
|
203
|
-
(0, common_utils_1.assert)(!this.localPaused, 0x297 /* "always called from resumed state" */);
|
|
204
|
-
this.localPaused = true;
|
|
205
|
-
this.timePaused = common_utils_1.performance.now();
|
|
206
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
207
|
-
this.deltaManager.inbound.pause();
|
|
208
|
-
}
|
|
209
|
-
resumeQueue(startBatch, messageEndBatch) {
|
|
210
|
-
const endBatch = messageEndBatch.sequenceNumber;
|
|
211
|
-
const duration = this.localPaused ? (common_utils_1.performance.now() - this.timePaused) : undefined;
|
|
212
|
-
this.batchCount++;
|
|
213
|
-
if (this.batchCount % 1000 === 1) {
|
|
214
|
-
this.logger.sendTelemetryEvent({
|
|
215
|
-
eventName: "BatchStats",
|
|
216
|
-
sequenceNumber: endBatch,
|
|
217
|
-
length: endBatch - startBatch + 1,
|
|
218
|
-
msnDistance: endBatch - messageEndBatch.minimumSequenceNumber,
|
|
219
|
-
duration,
|
|
220
|
-
batchCount: this.batchCount,
|
|
221
|
-
interrupted: this.localPaused,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
// Return early if no change in value
|
|
225
|
-
if (!this.localPaused) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
this.localPaused = false;
|
|
229
|
-
// Random round number - we want to know when batch waiting paused op processing.
|
|
230
|
-
if (duration !== undefined && duration > connectionTelemetry_1.latencyThreshold) {
|
|
231
|
-
this.logger.sendErrorEvent({
|
|
232
|
-
eventName: "MaxBatchWaitTimeExceeded",
|
|
233
|
-
duration,
|
|
234
|
-
sequenceNumber: endBatch,
|
|
235
|
-
length: endBatch - startBatch,
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
this.deltaManager.inbound.resume();
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Called for each incoming op (i.e. inbound "push" notification)
|
|
242
|
-
*/
|
|
243
|
-
trackPending(message) {
|
|
244
|
-
(0, common_utils_1.assert)(this.deltaManager.inbound.length !== 0, 0x298 /* "we have something in the queue that generates this event" */);
|
|
245
|
-
(0, common_utils_1.assert)((this.currentBatchClientId === undefined) === (this.pauseSequenceNumber === undefined), 0x299 /* "non-synchronized state" */);
|
|
246
|
-
const metadata = message.metadata;
|
|
247
|
-
const batchMetadata = metadata === null || metadata === void 0 ? void 0 : metadata.batch;
|
|
248
|
-
// Protocol messages are never part of a runtime batch of messages
|
|
249
|
-
if (!(0, driver_utils_1.isUnpackedRuntimeMessage)(message)) {
|
|
250
|
-
// Protocol messages should never show up in the middle of the batch!
|
|
251
|
-
(0, common_utils_1.assert)(this.currentBatchClientId === undefined, 0x29a /* "System message in the middle of batch!" */);
|
|
252
|
-
(0, common_utils_1.assert)(batchMetadata === undefined, 0x29b /* "system op in a batch?" */);
|
|
253
|
-
(0, common_utils_1.assert)(!this.localPaused, 0x29c /* "we should be processing ops when there is no active batch" */);
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
if (this.currentBatchClientId === undefined && batchMetadata === undefined) {
|
|
257
|
-
(0, common_utils_1.assert)(!this.localPaused, 0x29d /* "we should be processing ops when there is no active batch" */);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
// If the client ID changes then we can move the pause point. If it stayed the same then we need to check.
|
|
261
|
-
// If batchMetadata is not undefined then if it's true we've begun a new batch - if false we've ended
|
|
262
|
-
// the previous one
|
|
263
|
-
if (this.currentBatchClientId !== undefined || batchMetadata === false) {
|
|
264
|
-
if (this.currentBatchClientId !== message.clientId) {
|
|
265
|
-
// "Batch not closed, yet message from another client!"
|
|
266
|
-
throw new container_utils_1.DataCorruptionError("OpBatchIncomplete", Object.assign({ runtimeVersion: packageVersion_1.pkgVersion, batchClientId: this.currentBatchClientId }, (0, container_utils_1.extractSafePropertiesFromMessage)(message)));
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
// The queue is
|
|
270
|
-
// 1. paused only when the next message to be processed is the beginning of a batch. Done in two places:
|
|
271
|
-
// - in afterOpProcessing() - processing ops until reaching start of incomplete batch
|
|
272
|
-
// - here (batchMetadata == false below), when queue was empty and start of batch showed up.
|
|
273
|
-
// 2. resumed when batch end comes in (batchMetadata === true case below)
|
|
274
|
-
if (batchMetadata) {
|
|
275
|
-
(0, common_utils_1.assert)(this.currentBatchClientId === undefined, 0x29e /* "there can't be active batch" */);
|
|
276
|
-
(0, common_utils_1.assert)(!this.localPaused, 0x29f /* "we should be processing ops when there is no active batch" */);
|
|
277
|
-
this.pauseSequenceNumber = message.sequenceNumber;
|
|
278
|
-
this.currentBatchClientId = message.clientId;
|
|
279
|
-
// Start of the batch
|
|
280
|
-
// Only pause processing if queue has no other ops!
|
|
281
|
-
// If there are any other ops in the queue, processing will be stopped when they are processed!
|
|
282
|
-
if (this.deltaManager.inbound.length === 1) {
|
|
283
|
-
this.pauseQueue();
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
else if (batchMetadata === false) {
|
|
287
|
-
(0, common_utils_1.assert)(this.pauseSequenceNumber !== undefined, 0x2a0 /* "batch presence was validated above" */);
|
|
288
|
-
// Batch is complete, we can process it!
|
|
289
|
-
this.resumeQueue(this.pauseSequenceNumber, message);
|
|
290
|
-
this.pauseSequenceNumber = undefined;
|
|
291
|
-
this.currentBatchClientId = undefined;
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
// Continuation of current batch. Do nothing
|
|
295
|
-
(0, common_utils_1.assert)(this.currentBatchClientId !== undefined, 0x2a1 /* "logic error" */);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* This class has the following responsibilities:
|
|
301
|
-
* 1. It tracks batches as we process ops and raises "batchBegin" and "batchEnd" events.
|
|
302
|
-
* As part of it, it validates batch correctness (i.e. no system ops in the middle of batch)
|
|
303
|
-
* 2. It creates instance of ScheduleManagerCore that ensures we never start processing ops from batch
|
|
304
|
-
* unless all ops of the batch are in.
|
|
305
|
-
*/
|
|
306
|
-
class ScheduleManager {
|
|
307
|
-
constructor(deltaManager, emitter, logger) {
|
|
308
|
-
this.deltaManager = deltaManager;
|
|
309
|
-
this.emitter = emitter;
|
|
310
|
-
this.logger = logger;
|
|
311
|
-
this.hitError = false;
|
|
312
|
-
this.deltaScheduler = new deltaScheduler_1.DeltaScheduler(this.deltaManager, telemetry_utils_1.ChildLogger.create(this.logger, "DeltaScheduler"));
|
|
313
|
-
void new ScheduleManagerCore(deltaManager, logger);
|
|
314
|
-
}
|
|
315
|
-
beforeOpProcessing(message) {
|
|
316
|
-
var _a;
|
|
317
|
-
if (this.batchClientId !== message.clientId) {
|
|
318
|
-
(0, common_utils_1.assert)(this.batchClientId === undefined, 0x2a2 /* "Batch is interrupted by other client op. Should be caught by trackPending()" */);
|
|
319
|
-
// This could be the beginning of a new batch or an individual message.
|
|
320
|
-
this.emitter.emit("batchBegin", message);
|
|
321
|
-
this.deltaScheduler.batchBegin(message);
|
|
322
|
-
const batch = (_a = message === null || message === void 0 ? void 0 : message.metadata) === null || _a === void 0 ? void 0 : _a.batch;
|
|
323
|
-
if (batch) {
|
|
324
|
-
this.batchClientId = message.clientId;
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
this.batchClientId = undefined;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
afterOpProcessing(error, message) {
|
|
332
|
-
var _a;
|
|
333
|
-
// If this is no longer true, we need to revisit what we do where we set this.hitError.
|
|
334
|
-
(0, common_utils_1.assert)(!this.hitError, 0x2a3 /* "container should be closed on any error" */);
|
|
335
|
-
if (error) {
|
|
336
|
-
// We assume here that loader will close container and stop processing all future ops.
|
|
337
|
-
// This is implicit dependency. If this flow changes, this code might no longer be correct.
|
|
338
|
-
this.hitError = true;
|
|
339
|
-
this.batchClientId = undefined;
|
|
340
|
-
this.emitter.emit("batchEnd", error, message);
|
|
341
|
-
this.deltaScheduler.batchEnd(message);
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
const batch = (_a = message === null || message === void 0 ? void 0 : message.metadata) === null || _a === void 0 ? void 0 : _a.batch;
|
|
345
|
-
// If no batchClientId has been set then we're in an individual batch. Else, if we get
|
|
346
|
-
// batch end metadata, this is end of the current batch.
|
|
347
|
-
if (this.batchClientId === undefined || batch === false) {
|
|
348
|
-
this.batchClientId = undefined;
|
|
349
|
-
this.emitter.emit("batchEnd", undefined, message);
|
|
350
|
-
this.deltaScheduler.batchEnd(message);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
exports.ScheduleManager = ScheduleManager;
|
|
356
138
|
/**
|
|
357
139
|
* Legacy ID for the built-in AgentScheduler. To minimize disruption while removing it, retaining this as a
|
|
358
140
|
* special-case for document dirty state. Ultimately we should have no special-cases from the
|
|
@@ -380,7 +162,7 @@ exports.getDeviceSpec = getDeviceSpec;
|
|
|
380
162
|
*/
|
|
381
163
|
class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
382
164
|
constructor(context, registry, metadata, electedSummarizerData, chunks, dataStoreAliasMap, runtimeOptions, containerScope, logger, existing, blobManagerSnapshot, _storage, requestHandler, summaryConfiguration) {
|
|
383
|
-
var _a, _b, _c, _d
|
|
165
|
+
var _a, _b, _c, _d;
|
|
384
166
|
if (summaryConfiguration === void 0) { summaryConfiguration = Object.assign(Object.assign({}, exports.DefaultSummaryConfiguration), (_a = runtimeOptions.summaryOptions) === null || _a === void 0 ? void 0 : _a.summaryConfigOverrides); }
|
|
385
167
|
super();
|
|
386
168
|
this.context = context;
|
|
@@ -391,9 +173,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
391
173
|
this._storage = _storage;
|
|
392
174
|
this.requestHandler = requestHandler;
|
|
393
175
|
this.summaryConfiguration = summaryConfiguration;
|
|
394
|
-
this.defaultMaxConsecutiveReconnects =
|
|
176
|
+
this.defaultMaxConsecutiveReconnects = 7;
|
|
395
177
|
this._orderSequentiallyCalls = 0;
|
|
396
|
-
this.needsFlush = false;
|
|
397
178
|
this.flushTrigger = false;
|
|
398
179
|
this.savedOps = [];
|
|
399
180
|
this.consecutiveReconnects = 0;
|
|
@@ -406,6 +187,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
406
187
|
signalTimestamp: 0,
|
|
407
188
|
trackingSignalSequenceNumber: undefined,
|
|
408
189
|
};
|
|
190
|
+
// Provide lower soft limit - we want to have some number of ops to get efficiency in compression & bandwidth usage,
|
|
191
|
+
// but at the same time we want to send these ops sooner, to reduce overall latency of processing a batch.
|
|
192
|
+
// So there is some ballance here, that depends on compression algorithm and its efficiency working with smaller
|
|
193
|
+
// payloads. That number represents final (compressed) bits (once compression is implemented).
|
|
194
|
+
this.pendingAttachBatch = new batchManager_1.BatchManager(64 * 1024);
|
|
195
|
+
this.pendingBatch = new batchManager_1.BatchManager();
|
|
409
196
|
this.summarizeOnDemand = (...args) => {
|
|
410
197
|
if (this.clientDetails.type === summarizerClientElection_1.summarizerClientType) {
|
|
411
198
|
return this.summarizer.summarizeOnDemand(...args);
|
|
@@ -435,26 +222,23 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
435
222
|
}
|
|
436
223
|
};
|
|
437
224
|
this.messageAtLastSummary = metadata === null || metadata === void 0 ? void 0 : metadata.message;
|
|
438
|
-
// Default to false (enabled).
|
|
439
|
-
this.disableIsolatedChannels = (_b = this.runtimeOptions.summaryOptions.disableIsolatedChannels) !== null && _b !== void 0 ? _b : false;
|
|
440
225
|
this._connected = this.context.connected;
|
|
441
226
|
this.chunkMap = new Map(chunks);
|
|
442
227
|
this.handleContext = new containerHandleContext_1.ContainerFluidHandleContext("", this);
|
|
443
228
|
this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(this.logger, "ContainerRuntime"));
|
|
229
|
+
if (this.summaryConfiguration.state === "enabled") {
|
|
230
|
+
this.validateSummaryHeuristicConfiguration(this.summaryConfiguration);
|
|
231
|
+
}
|
|
444
232
|
this.summariesDisabled = this.isSummariesDisabled();
|
|
445
233
|
this.heuristicsDisabled = this.isHeuristicsDisabled();
|
|
446
234
|
this.summarizerClientElectionEnabled = this.isSummarizerClientElectionEnabled();
|
|
447
235
|
this.maxOpsSinceLastSummary = this.getMaxOpsSinceLastSummary();
|
|
448
236
|
this.initialSummarizerDelayMs = this.getInitialSummarizerDelayMs();
|
|
449
|
-
this._aliasingEnabled =
|
|
450
|
-
((_c = this.mc.config.getBoolean(useDataStoreAliasingKey)) !== null && _c !== void 0 ? _c : false) ||
|
|
451
|
-
((_d = runtimeOptions.useDataStoreAliasing) !== null && _d !== void 0 ? _d : false);
|
|
452
|
-
this._maxOpSizeInBytes = ((_e = this.mc.config.getNumber(maxOpSizeInBytesKey)) !== null && _e !== void 0 ? _e : defaultMaxOpSizeInBytes);
|
|
453
237
|
this.maxConsecutiveReconnects =
|
|
454
|
-
(
|
|
238
|
+
(_b = this.mc.config.getNumber(maxConsecutiveReconnectsKey)) !== null && _b !== void 0 ? _b : this.defaultMaxConsecutiveReconnects;
|
|
455
239
|
this._flushMode = runtimeOptions.flushMode;
|
|
456
240
|
const pendingRuntimeState = context.pendingLocalState;
|
|
457
|
-
const baseSnapshot = (
|
|
241
|
+
const baseSnapshot = (_c = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _c !== void 0 ? _c : context.baseSnapshot;
|
|
458
242
|
this.garbageCollector = garbageCollection_1.GarbageCollector.create({
|
|
459
243
|
runtime: this,
|
|
460
244
|
gcOptions: this.runtimeOptions.gcOptions,
|
|
@@ -466,6 +250,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
466
250
|
getNodePackagePath: async (nodePath) => this.getGCNodePackagePath(nodePath),
|
|
467
251
|
getLastSummaryTimestampMs: () => { var _a; return (_a = this.messageAtLastSummary) === null || _a === void 0 ? void 0 : _a.timestamp; },
|
|
468
252
|
readAndParseBlob: async (id) => (0, driver_utils_1.readAndParse)(this.storage, id),
|
|
253
|
+
getContainerDiagnosticId: () => this.context.id,
|
|
254
|
+
activeConnection: () => this.deltaManager.active,
|
|
469
255
|
});
|
|
470
256
|
const loadedFromSequenceNumber = this.deltaManager.initialSequenceNumber;
|
|
471
257
|
this.summarizerNode = (0, runtime_utils_1.createRootSummarizerNodeWithGC)(telemetry_utils_1.ChildLogger.create(this.logger, "SummarizerNode"),
|
|
@@ -488,8 +274,12 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
488
274
|
this.summarizerNode.loadBaseSummaryWithoutDifferential(baseSnapshot);
|
|
489
275
|
}
|
|
490
276
|
this.dataStores = new dataStores_1.DataStores((0, dataStores_1.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);
|
|
491
|
-
this.blobManager = new blobManager_1.BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId
|
|
492
|
-
|
|
277
|
+
this.blobManager = new blobManager_1.BlobManager(this.handleContext, blobManagerSnapshot, () => this.storage, (blobId, localId) => {
|
|
278
|
+
if (!this.disposed) {
|
|
279
|
+
this.submit(ContainerMessageType.BlobAttach, undefined, undefined, { blobId, localId });
|
|
280
|
+
}
|
|
281
|
+
}, (blobPath) => this.garbageCollector.nodeUpdated(blobPath, "Loaded"), this, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pendingAttachmentBlobs);
|
|
282
|
+
this.scheduleManager = new scheduleManager_1.ScheduleManager(context.deltaManager, this, () => this.clientId, telemetry_utils_1.ChildLogger.create(this.logger, "ScheduleManager"));
|
|
493
283
|
this.deltaSender = this.deltaManager;
|
|
494
284
|
this.pendingStateManager = new pendingStateManager_1.PendingStateManager({
|
|
495
285
|
applyStashedOp: this.applyStashedOp.bind(this),
|
|
@@ -499,7 +289,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
499
289
|
flush: this.flush.bind(this),
|
|
500
290
|
flushMode: () => this.flushMode,
|
|
501
291
|
reSubmit: this.reSubmit.bind(this),
|
|
502
|
-
rollback: this.rollback.bind(this),
|
|
503
292
|
setFlushMode: (mode) => this.setFlushMode(mode),
|
|
504
293
|
}, this._flushMode, pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.pending);
|
|
505
294
|
this.context.quorum.on("removeMember", (clientId) => {
|
|
@@ -579,9 +368,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
579
368
|
createContainerRuntimeVersion: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerRuntimeVersion,
|
|
580
369
|
createContainerTimestamp: metadata === null || metadata === void 0 ? void 0 : metadata.createContainerTimestamp,
|
|
581
370
|
};
|
|
582
|
-
//
|
|
583
|
-
//
|
|
584
|
-
loadSummaryNumber = (
|
|
371
|
+
// summaryNumber was renamed from summaryCount. For older docs that haven't been opened for a long time,
|
|
372
|
+
// the count is reset to 0.
|
|
373
|
+
loadSummaryNumber = (_d = metadata === null || metadata === void 0 ? void 0 : metadata.summaryNumber) !== null && _d !== void 0 ? _d : 0;
|
|
585
374
|
}
|
|
586
375
|
else {
|
|
587
376
|
this.createContainerMetadata = {
|
|
@@ -594,7 +383,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
594
383
|
this.logger.sendTelemetryEvent(Object.assign(Object.assign(Object.assign({ eventName: "ContainerLoadStats" }, this.createContainerMetadata), this.dataStores.containerLoadStats), { summaryNumber: loadSummaryNumber, summaryFormatVersion: metadata === null || metadata === void 0 ? void 0 : metadata.summaryFormatVersion, disableIsolatedChannels: metadata === null || metadata === void 0 ? void 0 : metadata.disableIsolatedChannels, gcVersion: metadata === null || metadata === void 0 ? void 0 : metadata.gcFeature }));
|
|
595
384
|
(0, connectionTelemetry_1.ReportOpPerfTelemetry)(this.context.clientId, this.deltaManager, this.logger);
|
|
596
385
|
(0, batchTracker_1.BindBatchTracker)(this, this.logger);
|
|
597
|
-
this.opTracker = new opTelemetry_1.OpTracker(this.deltaManager, this.mc.config.getBoolean(disableOpTrackingKey) === true);
|
|
598
386
|
}
|
|
599
387
|
get IContainerRuntime() { return this; }
|
|
600
388
|
get IFluidRouter() { return this; }
|
|
@@ -617,7 +405,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
617
405
|
runtimeVersion: packageVersion_1.pkgVersion,
|
|
618
406
|
},
|
|
619
407
|
});
|
|
620
|
-
const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close",
|
|
408
|
+
const { summaryOptions = {}, gcOptions = {}, loadSequenceNumberVerification = "close", flushMode = defaultFlushMode, enableOfflineLoad = false, } = runtimeOptions;
|
|
621
409
|
const pendingRuntimeState = context.pendingLocalState;
|
|
622
410
|
const baseSnapshot = (_b = pendingRuntimeState === null || pendingRuntimeState === void 0 ? void 0 : pendingRuntimeState.baseSnapshot) !== null && _b !== void 0 ? _b : context.baseSnapshot;
|
|
623
411
|
const storage = !pendingRuntimeState ?
|
|
@@ -670,7 +458,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
670
458
|
summaryOptions,
|
|
671
459
|
gcOptions,
|
|
672
460
|
loadSequenceNumberVerification,
|
|
673
|
-
useDataStoreAliasing,
|
|
674
461
|
flushMode,
|
|
675
462
|
enableOfflineLoad,
|
|
676
463
|
}, containerScope, logger, loadExisting, blobManagerSnapshot, storage, requestHandler);
|
|
@@ -728,6 +515,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
728
515
|
return (_a = this.summarizerClientElection) === null || _a === void 0 ? void 0 : _a.electedClientId;
|
|
729
516
|
}
|
|
730
517
|
get disposed() { return this._disposed; }
|
|
518
|
+
get emptyBatch() {
|
|
519
|
+
return this.pendingBatch.empty && this.pendingAttachBatch.empty;
|
|
520
|
+
}
|
|
731
521
|
get summarizer() {
|
|
732
522
|
(0, common_utils_1.assert)(this._summarizer !== undefined, 0x257 /* "This is not summarizing container" */);
|
|
733
523
|
return this._summarizer;
|
|
@@ -759,12 +549,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
759
549
|
if (this.runtimeOptions.summaryOptions.summarizerClientElection === true) {
|
|
760
550
|
return true;
|
|
761
551
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
else {
|
|
766
|
-
return false;
|
|
767
|
-
}
|
|
552
|
+
return this.summaryConfiguration.state !== "disabled"
|
|
553
|
+
? this.summaryConfiguration.summarizerClientElection === true
|
|
554
|
+
: false;
|
|
768
555
|
}
|
|
769
556
|
getMaxOpsSinceLastSummary() {
|
|
770
557
|
// back-compat: maxOpsSinceLastSummary was moved from ISummaryRuntimeOptions
|
|
@@ -772,12 +559,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
772
559
|
if (this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary !== undefined) {
|
|
773
560
|
return this.runtimeOptions.summaryOptions.maxOpsSinceLastSummary;
|
|
774
561
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
else {
|
|
779
|
-
return 0;
|
|
780
|
-
}
|
|
562
|
+
return this.summaryConfiguration.state !== "disabled"
|
|
563
|
+
? this.summaryConfiguration.maxOpsSinceLastSummary
|
|
564
|
+
: 0;
|
|
781
565
|
}
|
|
782
566
|
getInitialSummarizerDelayMs() {
|
|
783
567
|
// back-compat: initialSummarizerDelayMs was moved from ISummaryRuntimeOptions
|
|
@@ -785,12 +569,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
785
569
|
if (this.runtimeOptions.summaryOptions.initialSummarizerDelayMs !== undefined) {
|
|
786
570
|
return this.runtimeOptions.summaryOptions.initialSummarizerDelayMs;
|
|
787
571
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
else {
|
|
792
|
-
return 0;
|
|
793
|
-
}
|
|
572
|
+
return this.summaryConfiguration.state !== "disabled"
|
|
573
|
+
? this.summaryConfiguration.initialSummarizerDelayMs
|
|
574
|
+
: 0;
|
|
794
575
|
}
|
|
795
576
|
dispose(error) {
|
|
796
577
|
var _a;
|
|
@@ -863,17 +644,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
863
644
|
return this.resolveHandle(requestParser.createSubRequest(1));
|
|
864
645
|
}
|
|
865
646
|
if (id === blobManager_1.BlobManager.basePath && requestParser.isLeaf(2)) {
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
647
|
+
const blob = await this.blobManager.getBlob(requestParser.pathParts[1]);
|
|
648
|
+
return blob
|
|
649
|
+
? {
|
|
869
650
|
status: 200,
|
|
870
651
|
mimeType: "fluid/object",
|
|
871
|
-
value:
|
|
872
|
-
};
|
|
873
|
-
}
|
|
874
|
-
else {
|
|
875
|
-
return (0, runtime_utils_1.create404Response)(request);
|
|
876
|
-
}
|
|
652
|
+
value: blob,
|
|
653
|
+
} : (0, runtime_utils_1.create404Response)(request);
|
|
877
654
|
}
|
|
878
655
|
else if (requestParser.pathParts.length > 0) {
|
|
879
656
|
const dataStore = await this.getDataStoreFromRequest(id, request);
|
|
@@ -891,13 +668,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
891
668
|
}
|
|
892
669
|
internalId(maybeAlias) {
|
|
893
670
|
var _a;
|
|
894
|
-
return (_a = this.dataStores.aliases
|
|
671
|
+
return (_a = this.dataStores.aliases.get(maybeAlias)) !== null && _a !== void 0 ? _a : maybeAlias;
|
|
895
672
|
}
|
|
896
673
|
async getDataStoreFromRequest(id, request) {
|
|
897
674
|
var _a, _b, _c;
|
|
898
675
|
const wait = typeof ((_a = request.headers) === null || _a === void 0 ? void 0 : _a[RuntimeHeaders.wait]) === "boolean"
|
|
899
676
|
? (_b = request.headers) === null || _b === void 0 ? void 0 : _b[RuntimeHeaders.wait]
|
|
900
677
|
: true;
|
|
678
|
+
await this.dataStores.waitIfPendingAlias(id);
|
|
901
679
|
const internalId = this.internalId(id);
|
|
902
680
|
const dataStoreContext = await this.dataStores.getDataStore(internalId, wait);
|
|
903
681
|
/**
|
|
@@ -927,10 +705,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
927
705
|
addMetadataToSummary(summaryTree) {
|
|
928
706
|
var _a;
|
|
929
707
|
const metadata = Object.assign(Object.assign(Object.assign(Object.assign({}, this.createContainerMetadata), {
|
|
930
|
-
// back-compat 0.59.3000: This is renamed to summaryNumber. Can be removed when 0.59.3000 saturates.
|
|
931
|
-
summaryCount: this.nextSummaryNumber,
|
|
932
708
|
// Increment the summary number for the next summary that will be generated.
|
|
933
|
-
summaryNumber: this.nextSummaryNumber++, summaryFormatVersion: 1
|
|
709
|
+
summaryNumber: this.nextSummaryNumber++, summaryFormatVersion: 1 }), this.garbageCollector.getMetadata()), {
|
|
934
710
|
// The last message processed at the time of summary. If there are no new messages, use the message from the
|
|
935
711
|
// last summary.
|
|
936
712
|
message: (_a = (0, summaryFormat_1.extractSummaryMetadataMessage)(this.deltaManager.lastMessage)) !== null && _a !== void 0 ? _a : this.messageAtLastSummary });
|
|
@@ -943,7 +719,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
943
719
|
const content = JSON.stringify([...this.chunkMap]);
|
|
944
720
|
(0, runtime_utils_1.addBlobToSummary)(summaryTree, summaryFormat_1.chunksBlobName, content);
|
|
945
721
|
}
|
|
946
|
-
const dataStoreAliases = this.dataStores.aliases
|
|
722
|
+
const dataStoreAliases = this.dataStores.aliases;
|
|
947
723
|
if (dataStoreAliases.size > 0) {
|
|
948
724
|
(0, runtime_utils_1.addBlobToSummary)(summaryTree, summaryFormat_1.aliasBlobName, JSON.stringify([...dataStoreAliases]));
|
|
949
725
|
}
|
|
@@ -974,7 +750,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
974
750
|
// Feature disabled, we never stop reconnecting
|
|
975
751
|
return true;
|
|
976
752
|
}
|
|
977
|
-
if (!this.
|
|
753
|
+
if (!this.hasPendingMessages()) {
|
|
978
754
|
// If there are no pending messages, we can always reconnect
|
|
979
755
|
this.resetReconnectCount();
|
|
980
756
|
return true;
|
|
@@ -1040,22 +816,55 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1040
816
|
}
|
|
1041
817
|
}
|
|
1042
818
|
setConnectionState(connected, clientId) {
|
|
819
|
+
if (connected === false && this.delayConnectClientId !== undefined) {
|
|
820
|
+
this.delayConnectClientId = undefined;
|
|
821
|
+
this.mc.logger.sendTelemetryEvent({
|
|
822
|
+
eventName: "UnsuccessfulConnectedTransition",
|
|
823
|
+
});
|
|
824
|
+
// Don't propagate "disconnected" event because we didn't propagate the previous "connected" event
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
// If attachment blobs were added while disconnected, we need to delay
|
|
828
|
+
// propagation of the "connected" event until we have uploaded them to
|
|
829
|
+
// ensure we don't submit ops referencing a blob that has not been uploaded
|
|
830
|
+
const connecting = connected && !this._connected && !this.deltaManager.readOnlyInfo.readonly;
|
|
831
|
+
if (connecting && this.blobManager.hasPendingOfflineUploads) {
|
|
832
|
+
(0, common_utils_1.assert)(!this.delayConnectClientId, 0x392 /* Connect event delay must be canceled before subsequent connect event */);
|
|
833
|
+
(0, common_utils_1.assert)(!!clientId, 0x393 /* Must have clientId when connecting */);
|
|
834
|
+
this.delayConnectClientId = clientId;
|
|
835
|
+
this.blobManager.onConnected().then(() => {
|
|
836
|
+
// make sure we didn't reconnect before the promise resolved
|
|
837
|
+
if (this.delayConnectClientId === clientId && !this.disposed) {
|
|
838
|
+
this.delayConnectClientId = undefined;
|
|
839
|
+
this.setConnectionStateCore(connected, clientId);
|
|
840
|
+
}
|
|
841
|
+
}, (error) => this.closeFn(error));
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
this.setConnectionStateCore(connected, clientId);
|
|
845
|
+
}
|
|
846
|
+
setConnectionStateCore(connected, clientId) {
|
|
847
|
+
(0, common_utils_1.assert)(!this.delayConnectClientId, 0x394 /* connect event delay must be cleared before propagating connect event */);
|
|
1043
848
|
this.verifyNotClosed();
|
|
1044
849
|
// There might be no change of state due to Container calling this API after loading runtime.
|
|
1045
850
|
const changeOfState = this._connected !== connected;
|
|
1046
|
-
const reconnection = changeOfState && connected;
|
|
851
|
+
const reconnection = changeOfState && !connected;
|
|
1047
852
|
this._connected = connected;
|
|
1048
853
|
if (!connected) {
|
|
1049
854
|
this._perfSignalData.signalsLost = 0;
|
|
1050
855
|
this._perfSignalData.signalTimestamp = 0;
|
|
1051
856
|
this._perfSignalData.trackingSignalSequenceNumber = undefined;
|
|
1052
857
|
}
|
|
858
|
+
else {
|
|
859
|
+
(0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x3cd /* Connection is possible only if container exists in storage */);
|
|
860
|
+
}
|
|
861
|
+
// Fail while disconnected
|
|
1053
862
|
if (reconnection) {
|
|
1054
863
|
this.consecutiveReconnects++;
|
|
1055
864
|
if (!this.shouldContinueReconnecting()) {
|
|
1056
|
-
this.closeFn(
|
|
1057
|
-
//
|
|
1058
|
-
|
|
865
|
+
this.closeFn(container_utils_1.DataProcessingError.create(
|
|
866
|
+
// eslint-disable-next-line max-len
|
|
867
|
+
"Runtime detected too many reconnects with no progress syncing local ops. Batch of ops is likely too large (over 1Mb)", "setConnectionState", undefined, {
|
|
1059
868
|
dataLoss: 1,
|
|
1060
869
|
attempts: this.consecutiveReconnects,
|
|
1061
870
|
pendingMessages: this.pendingStateManager.pendingMessagesCount,
|
|
@@ -1067,46 +876,48 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1067
876
|
this.replayPendingStates();
|
|
1068
877
|
}
|
|
1069
878
|
this.dataStores.setConnectionState(connected, clientId);
|
|
879
|
+
this.garbageCollector.setConnectionState(connected, clientId);
|
|
1070
880
|
(0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, connected, clientId);
|
|
1071
881
|
}
|
|
1072
882
|
process(messageArg, local) {
|
|
1073
|
-
var _a
|
|
883
|
+
var _a;
|
|
1074
884
|
this.verifyNotClosed();
|
|
1075
|
-
// If it's not message for runtime, bail out right away.
|
|
1076
|
-
if (!(0, driver_utils_1.isUnpackedRuntimeMessage)(messageArg)) {
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
|
|
1080
|
-
this.savedOps.push(messageArg);
|
|
1081
|
-
}
|
|
1082
885
|
// Do shallow copy of message, as methods below will modify it.
|
|
1083
886
|
// There might be multiple container instances receiving same message
|
|
1084
887
|
// We do not need to make deep copy, as each layer will just replace message.content itself,
|
|
1085
888
|
// but would not modify contents details
|
|
1086
889
|
let message = Object.assign({}, messageArg);
|
|
890
|
+
// back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
|
|
891
|
+
// System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
|
|
892
|
+
// Old ops may contain empty string (I assume noops).
|
|
893
|
+
if (typeof message.contents === "string" && message.contents !== "") {
|
|
894
|
+
message.contents = JSON.parse(message.contents);
|
|
895
|
+
}
|
|
896
|
+
// Caveat: This will return false for runtime message in very old format, that are used in snapshot tests
|
|
897
|
+
// This format was not shipped to production workflows.
|
|
898
|
+
const runtimeMessage = unpackRuntimeMessage(message);
|
|
899
|
+
if ((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad) {
|
|
900
|
+
this.savedOps.push(messageArg);
|
|
901
|
+
}
|
|
1087
902
|
// Surround the actual processing of the operation with messages to the schedule manager indicating
|
|
1088
903
|
// the beginning and end. This allows it to emit appropriate events and/or pause the processing of new
|
|
1089
904
|
// messages once a batch has been fully processed.
|
|
1090
905
|
this.scheduleManager.beforeOpProcessing(message);
|
|
1091
906
|
try {
|
|
1092
|
-
message = unpackRuntimeMessage(message);
|
|
1093
907
|
// Chunk processing must come first given that we will transform the message to the unchunked version
|
|
1094
908
|
// once all pieces are available
|
|
1095
909
|
message = this.processRemoteChunkedMessage(message);
|
|
1096
910
|
let localOpMetadata;
|
|
1097
|
-
if (local) {
|
|
1098
|
-
|
|
1099
|
-
// Do not process local chunked ops until all pieces are available.
|
|
1100
|
-
if (message.type !== ContainerMessageType.ChunkedOp) {
|
|
1101
|
-
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
1102
|
-
}
|
|
911
|
+
if (local && runtimeMessage) {
|
|
912
|
+
localOpMetadata = this.pendingStateManager.processPendingLocalMessage(message);
|
|
1103
913
|
}
|
|
1104
914
|
// If there are no more pending messages after processing a local message,
|
|
1105
915
|
// the document is no longer dirty.
|
|
1106
|
-
if (!this.
|
|
916
|
+
if (!this.hasPendingMessages()) {
|
|
1107
917
|
this.updateDocumentDirtyState(false);
|
|
1108
918
|
}
|
|
1109
|
-
|
|
919
|
+
const type = message.type;
|
|
920
|
+
switch (type) {
|
|
1110
921
|
case ContainerMessageType.Attach:
|
|
1111
922
|
this.dataStores.processAttachMessage(message, local);
|
|
1112
923
|
break;
|
|
@@ -1117,12 +928,18 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1117
928
|
this.dataStores.processFluidDataStoreOp(message, local, localOpMetadata);
|
|
1118
929
|
break;
|
|
1119
930
|
case ContainerMessageType.BlobAttach:
|
|
1120
|
-
|
|
1121
|
-
|
|
931
|
+
this.blobManager.processBlobAttachOp(message, local);
|
|
932
|
+
break;
|
|
933
|
+
case ContainerMessageType.ChunkedOp:
|
|
934
|
+
case ContainerMessageType.Rejoin:
|
|
1122
935
|
break;
|
|
1123
936
|
default:
|
|
937
|
+
(0, common_utils_1.assert)(!runtimeMessage, 0x3ce /* Runtime message of unknown type */);
|
|
938
|
+
}
|
|
939
|
+
// For back-compat, notify only about runtime messages for now.
|
|
940
|
+
if (runtimeMessage) {
|
|
941
|
+
this.emit("op", message, runtimeMessage);
|
|
1124
942
|
}
|
|
1125
|
-
this.emit("op", message);
|
|
1126
943
|
this.scheduleManager.afterOpProcessing(undefined, message);
|
|
1127
944
|
if (local) {
|
|
1128
945
|
// If we have processed a local op, this means that the container is
|
|
@@ -1188,6 +1005,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1188
1005
|
this.dataStores.processSignal(envelope.address, transformed, local);
|
|
1189
1006
|
}
|
|
1190
1007
|
async getRootDataStore(id, wait = true) {
|
|
1008
|
+
return this.getRootDataStoreChannel(id, wait);
|
|
1009
|
+
}
|
|
1010
|
+
async getRootDataStoreChannel(id, wait = true) {
|
|
1011
|
+
await this.dataStores.waitIfPendingAlias(id);
|
|
1191
1012
|
const internalId = this.internalId(id);
|
|
1192
1013
|
const context = await this.dataStores.getDataStore(internalId, wait);
|
|
1193
1014
|
(0, common_utils_1.assert)(await context.isRoot(), 0x12b /* "did not get root data store" */);
|
|
@@ -1212,25 +1033,57 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1212
1033
|
}
|
|
1213
1034
|
flush() {
|
|
1214
1035
|
(0, common_utils_1.assert)(this._orderSequentiallyCalls === 0, 0x24c /* "Cannot call `flush()` from `orderSequentially`'s callback" */);
|
|
1215
|
-
|
|
1216
|
-
|
|
1036
|
+
this.flushBatch(this.pendingAttachBatch.popBatch());
|
|
1037
|
+
this.flushBatch(this.pendingBatch.popBatch());
|
|
1038
|
+
(0, common_utils_1.assert)(this.emptyBatch, 0x3cf /* reentrancy */);
|
|
1039
|
+
}
|
|
1040
|
+
flushBatch(batch) {
|
|
1041
|
+
const length = batch.length;
|
|
1042
|
+
if (length > 1) {
|
|
1043
|
+
batch[0].metadata = Object.assign(Object.assign({}, batch[0].metadata), { batch: true });
|
|
1044
|
+
batch[length - 1].metadata = Object.assign(Object.assign({}, batch[length - 1].metadata), { batch: false });
|
|
1045
|
+
// This assert fires for the following reason (there might be more cases like that):
|
|
1046
|
+
// AgentScheduler will send ops in response to ConsensusRegisterCollection's "atomicChanged" event handler,
|
|
1047
|
+
// i.e. in the middle of op processing!
|
|
1048
|
+
// Sending ops while processing ops is not good idea - it's not defined when
|
|
1049
|
+
// referenceSequenceNumber changes in op processing sequence (at the beginning or end of op processing),
|
|
1050
|
+
// If we send ops in response to processing multiple ops, then we for sure hit this assert!
|
|
1051
|
+
// Tracked via ADO #1834
|
|
1052
|
+
// assert(batch[0].referenceSequenceNumber === batch[length - 1].referenceSequenceNumber,
|
|
1053
|
+
// "Batch should be generated synchronously, without processing ops in the middle!");
|
|
1217
1054
|
}
|
|
1218
|
-
|
|
1219
|
-
// Note that this should happen before the `this.needsFlush` check below because in the scenario where we are
|
|
1220
|
-
// not connected, `this.needsFlush` will be false but the PendingStateManager might have pending messages and
|
|
1221
|
-
// hence needs to track this.
|
|
1222
|
-
this.pendingStateManager.onFlush();
|
|
1223
|
-
// If flush has already been called then exit early
|
|
1224
|
-
if (!this.needsFlush) {
|
|
1225
|
-
return;
|
|
1226
|
-
}
|
|
1227
|
-
this.needsFlush = false;
|
|
1055
|
+
let clientSequenceNumber = -1;
|
|
1228
1056
|
// Did we disconnect in the middle of turn-based batch?
|
|
1229
1057
|
// If so, do nothing, as pending state manager will resubmit it correctly on reconnect.
|
|
1230
|
-
if (
|
|
1231
|
-
|
|
1058
|
+
if (this.canSendOps()) {
|
|
1059
|
+
if (this.context.submitBatchFn !== undefined) {
|
|
1060
|
+
const batchToSend = [];
|
|
1061
|
+
for (const message of batch) {
|
|
1062
|
+
batchToSend.push({ contents: message.contents, metadata: message.metadata });
|
|
1063
|
+
}
|
|
1064
|
+
// returns clientSequenceNumber of last message in a batch
|
|
1065
|
+
clientSequenceNumber = this.context.submitBatchFn(batchToSend);
|
|
1066
|
+
}
|
|
1067
|
+
else {
|
|
1068
|
+
// Legacy path - supporting old loader versions. Can be removed only when LTS moves above
|
|
1069
|
+
// version that has support for batches (submitBatchFn)
|
|
1070
|
+
for (const message of batch) {
|
|
1071
|
+
clientSequenceNumber = this.context.submitFn(protocol_definitions_1.MessageType.Operation, message.deserializedContent, true, // batch
|
|
1072
|
+
message.metadata);
|
|
1073
|
+
}
|
|
1074
|
+
this.deltaSender.flush();
|
|
1075
|
+
}
|
|
1076
|
+
// Convert from clientSequenceNumber of last message in the batch to clientSequenceNumber of first message.
|
|
1077
|
+
clientSequenceNumber -= batch.length - 1;
|
|
1078
|
+
(0, common_utils_1.assert)(clientSequenceNumber >= 0, 0x3d0 /* clientSequenceNumber can't be negative */);
|
|
1079
|
+
}
|
|
1080
|
+
// Let the PendingStateManager know that a message was submitted.
|
|
1081
|
+
// In future, need to shift toward keeping batch as a whole!
|
|
1082
|
+
for (const message of batch) {
|
|
1083
|
+
this.pendingStateManager.onSubmitMessage(message.deserializedContent.type, clientSequenceNumber, message.referenceSequenceNumber, message.deserializedContent.contents, message.localOpMetadata, message.metadata);
|
|
1084
|
+
clientSequenceNumber++;
|
|
1232
1085
|
}
|
|
1233
|
-
|
|
1086
|
+
this.pendingStateManager.onFlush();
|
|
1234
1087
|
}
|
|
1235
1088
|
orderSequentially(callback) {
|
|
1236
1089
|
// If flush mode is already TurnBased we are either
|
|
@@ -1255,7 +1108,10 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1255
1108
|
trackOrderSequentiallyCalls(callback) {
|
|
1256
1109
|
let checkpoint;
|
|
1257
1110
|
if (this.mc.config.getBoolean("Fluid.ContainerRuntime.EnableRollback")) {
|
|
1258
|
-
|
|
1111
|
+
// Note: we are not touching this.pendingAttachBatch here, for two reasons:
|
|
1112
|
+
// 1. It would not help, as we flush attach ops as they become available.
|
|
1113
|
+
// 2. There is no way to undo process of data store creation.
|
|
1114
|
+
checkpoint = this.pendingBatch.checkpoint();
|
|
1259
1115
|
}
|
|
1260
1116
|
try {
|
|
1261
1117
|
this._orderSequentiallyCalls++;
|
|
@@ -1264,7 +1120,16 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1264
1120
|
catch (error) {
|
|
1265
1121
|
if (checkpoint) {
|
|
1266
1122
|
// This will throw and close the container if rollback fails
|
|
1267
|
-
|
|
1123
|
+
try {
|
|
1124
|
+
checkpoint.rollback((message) => this.rollback(message.deserializedContent.type, message.deserializedContent.contents, message.localOpMetadata));
|
|
1125
|
+
}
|
|
1126
|
+
catch (err) {
|
|
1127
|
+
const error2 = (0, telemetry_utils_1.wrapError)(err, (message) => {
|
|
1128
|
+
return container_utils_1.DataProcessingError.create(`RollbackError: ${message}`, "checkpointRollback", undefined);
|
|
1129
|
+
});
|
|
1130
|
+
this.closeFn(error2);
|
|
1131
|
+
throw error2;
|
|
1132
|
+
}
|
|
1268
1133
|
}
|
|
1269
1134
|
else {
|
|
1270
1135
|
// pre-0.58 error message: orderSequentiallyCallbackException
|
|
@@ -1278,67 +1143,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1278
1143
|
}
|
|
1279
1144
|
async createDataStore(pkg) {
|
|
1280
1145
|
const internalId = (0, uuid_1.v4)();
|
|
1281
|
-
return (0, dataStore_1.channelToDataStore)(await this._createDataStore(pkg,
|
|
1282
|
-
}
|
|
1283
|
-
/**
|
|
1284
|
-
* Creates a root datastore directly with a user generated id and attaches it to storage.
|
|
1285
|
-
* It is vulnerable to name collisions and should not be used.
|
|
1286
|
-
*
|
|
1287
|
-
* This method will be removed. See #6465.
|
|
1288
|
-
*/
|
|
1289
|
-
async createRootDataStoreLegacy(pkg, rootDataStoreId) {
|
|
1290
|
-
const fluidDataStore = await this._createDataStore(pkg, true /* isRoot */, rootDataStoreId);
|
|
1291
|
-
// back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel. For
|
|
1292
|
-
// older versions, we still have to call bindToContext.
|
|
1293
|
-
if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
|
|
1294
|
-
fluidDataStore.makeVisibleAndAttachGraph();
|
|
1295
|
-
}
|
|
1296
|
-
else {
|
|
1297
|
-
fluidDataStore.bindToContext();
|
|
1298
|
-
}
|
|
1299
|
-
return fluidDataStore;
|
|
1300
|
-
}
|
|
1301
|
-
/**
|
|
1302
|
-
* @deprecated - will be removed in an upcoming release. See #9660.
|
|
1303
|
-
*/
|
|
1304
|
-
async createRootDataStore(pkg, rootDataStoreId) {
|
|
1305
|
-
if (rootDataStoreId.includes("/")) {
|
|
1306
|
-
throw new container_utils_1.UsageError(`Id cannot contain slashes: '${rootDataStoreId}'`);
|
|
1307
|
-
}
|
|
1308
|
-
return this._aliasingEnabled === true ?
|
|
1309
|
-
this.createAndAliasDataStore(pkg, rootDataStoreId) :
|
|
1310
|
-
this.createRootDataStoreLegacy(pkg, rootDataStoreId);
|
|
1311
|
-
}
|
|
1312
|
-
/**
|
|
1313
|
-
* Creates a data store then attempts to alias it.
|
|
1314
|
-
* If aliasing fails, it will raise an exception.
|
|
1315
|
-
*
|
|
1316
|
-
* This method will be removed. See #6465.
|
|
1317
|
-
*
|
|
1318
|
-
* @param pkg - Package name of the data store
|
|
1319
|
-
* @param alias - Alias to be assigned to the data store
|
|
1320
|
-
* @param props - Properties for the data store
|
|
1321
|
-
* @returns - An aliased data store which can can be found / loaded by alias.
|
|
1322
|
-
*/
|
|
1323
|
-
async createAndAliasDataStore(pkg, alias, props) {
|
|
1324
|
-
const internalId = (0, uuid_1.v4)();
|
|
1325
|
-
const dataStore = await this._createDataStore(pkg, false /* isRoot */, internalId, props);
|
|
1326
|
-
const aliasedDataStore = (0, dataStore_1.channelToDataStore)(dataStore, internalId, this, this.dataStores, this.mc.logger);
|
|
1327
|
-
const result = await aliasedDataStore.trySetAlias(alias);
|
|
1328
|
-
if (result !== "Success") {
|
|
1329
|
-
throw new container_utils_1.GenericError("dataStoreAliasFailure", undefined /* error */, {
|
|
1330
|
-
alias: {
|
|
1331
|
-
value: alias,
|
|
1332
|
-
tag: telemetry_utils_1.TelemetryDataTag.UserData,
|
|
1333
|
-
},
|
|
1334
|
-
internalId: {
|
|
1335
|
-
value: internalId,
|
|
1336
|
-
tag: telemetry_utils_1.TelemetryDataTag.PackageData,
|
|
1337
|
-
},
|
|
1338
|
-
aliasResult: result,
|
|
1339
|
-
});
|
|
1340
|
-
}
|
|
1341
|
-
return aliasedDataStore;
|
|
1146
|
+
return (0, dataStore_1.channelToDataStore)(await this._createDataStore(pkg, internalId), internalId, this, this.dataStores, this.mc.logger);
|
|
1342
1147
|
}
|
|
1343
1148
|
createDetachedRootDataStore(pkg, rootDataStoreId) {
|
|
1344
1149
|
if (rootDataStoreId.includes("/")) {
|
|
@@ -1349,38 +1154,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1349
1154
|
createDetachedDataStore(pkg) {
|
|
1350
1155
|
return this.dataStores.createDetachedDataStoreCore(pkg, false);
|
|
1351
1156
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
* It is vulnerable to name collisions if both aforementioned conditions are true, and should not be used.
|
|
1355
|
-
*
|
|
1356
|
-
* This method will be removed. See #6465.
|
|
1357
|
-
*/
|
|
1358
|
-
async _createDataStoreWithPropsLegacy(pkg, props, id = (0, uuid_1.v4)(), isRoot = false) {
|
|
1359
|
-
const fluidDataStore = await this.dataStores._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
|
|
1360
|
-
if (isRoot) {
|
|
1361
|
-
// back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel.
|
|
1362
|
-
// For older versions, we still have to call bindToContext.
|
|
1363
|
-
if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
|
|
1364
|
-
fluidDataStore.makeVisibleAndAttachGraph();
|
|
1365
|
-
}
|
|
1366
|
-
else {
|
|
1367
|
-
fluidDataStore.bindToContext();
|
|
1368
|
-
}
|
|
1369
|
-
this.logger.sendTelemetryEvent({
|
|
1370
|
-
eventName: "Root datastore with props",
|
|
1371
|
-
hasProps: props !== undefined,
|
|
1372
|
-
});
|
|
1373
|
-
}
|
|
1157
|
+
async _createDataStoreWithProps(pkg, props, id = (0, uuid_1.v4)()) {
|
|
1158
|
+
const fluidDataStore = await this.dataStores._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props).realize();
|
|
1374
1159
|
return (0, dataStore_1.channelToDataStore)(fluidDataStore, id, this, this.dataStores, this.mc.logger);
|
|
1375
1160
|
}
|
|
1376
|
-
async
|
|
1377
|
-
return this._aliasingEnabled === true && isRoot ?
|
|
1378
|
-
this.createAndAliasDataStore(pkg, id, props) :
|
|
1379
|
-
this._createDataStoreWithPropsLegacy(pkg, props, id, isRoot);
|
|
1380
|
-
}
|
|
1381
|
-
async _createDataStore(pkg, isRoot, id = (0, uuid_1.v4)(), props) {
|
|
1161
|
+
async _createDataStore(pkg, id = (0, uuid_1.v4)(), props) {
|
|
1382
1162
|
return this.dataStores
|
|
1383
|
-
._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id,
|
|
1163
|
+
._createFluidDataStoreContext(Array.isArray(pkg) ? pkg : [pkg], id, props)
|
|
1384
1164
|
.realize();
|
|
1385
1165
|
}
|
|
1386
1166
|
canSendOps() {
|
|
@@ -1454,7 +1234,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1454
1234
|
(0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x12e /* "Container Context should already be in attached state" */);
|
|
1455
1235
|
this.emit("attached");
|
|
1456
1236
|
}
|
|
1457
|
-
if (attachState === container_definitions_1.AttachState.Attached && !this.
|
|
1237
|
+
if (attachState === container_definitions_1.AttachState.Attached && !this.hasPendingMessages()) {
|
|
1458
1238
|
this.updateDocumentDirtyState(false);
|
|
1459
1239
|
}
|
|
1460
1240
|
this.dataStores.setAttachState(attachState);
|
|
@@ -1472,10 +1252,8 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1472
1252
|
this.blobManager.setRedirectTable(blobRedirectTable);
|
|
1473
1253
|
}
|
|
1474
1254
|
const summarizeResult = this.dataStores.createSummary(telemetryContext);
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
(0, summaryFormat_1.wrapSummaryInChannelsTree)(summarizeResult);
|
|
1478
|
-
}
|
|
1255
|
+
// Wrap data store summaries in .channels subtree.
|
|
1256
|
+
(0, summaryFormat_1.wrapSummaryInChannelsTree)(summarizeResult);
|
|
1479
1257
|
this.addContainerStateToSummary(summarizeResult, true /* fullTree */, false /* trackState */, telemetryContext);
|
|
1480
1258
|
return summarizeResult.summary;
|
|
1481
1259
|
}
|
|
@@ -1490,12 +1268,9 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1490
1268
|
}
|
|
1491
1269
|
async summarizeInternal(fullTree, trackState, telemetryContext) {
|
|
1492
1270
|
const summarizeResult = await this.dataStores.summarize(fullTree, trackState, telemetryContext);
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
(0, summaryFormat_1.wrapSummaryInChannelsTree)(summarizeResult);
|
|
1497
|
-
pathPartsForChildren = [runtime_definitions_1.channelsTreeName];
|
|
1498
|
-
}
|
|
1271
|
+
// Wrap data store summaries in .channels subtree.
|
|
1272
|
+
(0, summaryFormat_1.wrapSummaryInChannelsTree)(summarizeResult);
|
|
1273
|
+
const pathPartsForChildren = [runtime_definitions_1.channelsTreeName];
|
|
1499
1274
|
this.addContainerStateToSummary(summarizeResult, fullTree, trackState, telemetryContext);
|
|
1500
1275
|
return Object.assign(Object.assign({}, summarizeResult), { id: "", pathPartsForChildren });
|
|
1501
1276
|
}
|
|
@@ -1623,7 +1398,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1623
1398
|
}
|
|
1624
1399
|
/**
|
|
1625
1400
|
* Runs garbage collection and updates the reference / used state of the nodes in the container.
|
|
1626
|
-
* @returns the statistics of the garbage collection run.
|
|
1401
|
+
* @returns the statistics of the garbage collection run; undefined if GC did not run.
|
|
1627
1402
|
*/
|
|
1628
1403
|
async collectGarbage(options) {
|
|
1629
1404
|
return this.garbageCollector.collectGarbage(options);
|
|
@@ -1646,7 +1421,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1646
1421
|
* @param options - options controlling how the summary is generated or submitted
|
|
1647
1422
|
*/
|
|
1648
1423
|
async submitSummary(options) {
|
|
1649
|
-
var _a, _b
|
|
1424
|
+
var _a, _b;
|
|
1650
1425
|
const { fullTree, refreshLatestAck, summaryLogger } = options;
|
|
1651
1426
|
// The summary number for this summary. This will be updated during the summary process, so get it now and
|
|
1652
1427
|
// use it for all events logged during this summary.
|
|
@@ -1654,6 +1429,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1654
1429
|
const summaryNumberLogger = telemetry_utils_1.ChildLogger.create(summaryLogger, undefined, {
|
|
1655
1430
|
all: { summaryNumber },
|
|
1656
1431
|
});
|
|
1432
|
+
(0, common_utils_1.assert)(this.emptyBatch, 0x3d1 /* Can't trigger summary in the middle of a batch */);
|
|
1657
1433
|
let latestSnapshotVersionId;
|
|
1658
1434
|
if (refreshLatestAck) {
|
|
1659
1435
|
const latestSnapshotInfo = await this.refreshLatestSummaryAckFromServer(telemetry_utils_1.ChildLogger.create(summaryNumberLogger, undefined, { all: { safeSummary: true } }));
|
|
@@ -1674,17 +1450,11 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1674
1450
|
const summaryRefSeqNum = this.deltaManager.lastSequenceNumber;
|
|
1675
1451
|
const minimumSequenceNumber = this.deltaManager.minimumSequenceNumber;
|
|
1676
1452
|
const message = `Summary @${summaryRefSeqNum}:${this.deltaManager.minimumSequenceNumber}`;
|
|
1677
|
-
|
|
1678
|
-
// doesn't match the last processed sequence number, log an error.
|
|
1679
|
-
if (summaryRefSeqNum !== ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber)) {
|
|
1680
|
-
summaryNumberLogger.sendErrorEvent({
|
|
1681
|
-
eventName: "LastSequenceMismatch",
|
|
1682
|
-
error: message,
|
|
1683
|
-
});
|
|
1684
|
-
}
|
|
1453
|
+
const lastAck = this.summaryCollection.latestAck;
|
|
1685
1454
|
this.summarizerNode.startSummary(summaryRefSeqNum, summaryNumberLogger);
|
|
1686
1455
|
// Helper function to check whether we should still continue between each async step.
|
|
1687
1456
|
const checkContinue = () => {
|
|
1457
|
+
var _a;
|
|
1688
1458
|
// Do not check for loss of connectivity directly! Instead leave it up to
|
|
1689
1459
|
// RunWhileConnectedCoordinator to control policy in a single place.
|
|
1690
1460
|
// This will allow easier change of design if we chose to. For example, we may chose to allow
|
|
@@ -1708,6 +1478,14 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1708
1478
|
error: `lastSequenceNumber changed before uploading to storage. ${this.deltaManager.lastSequenceNumber} !== ${summaryRefSeqNum}`,
|
|
1709
1479
|
};
|
|
1710
1480
|
}
|
|
1481
|
+
(0, common_utils_1.assert)(summaryRefSeqNum === ((_a = this.deltaManager.lastMessage) === null || _a === void 0 ? void 0 : _a.sequenceNumber), 0x395 /* it's one and the same thing */);
|
|
1482
|
+
if (lastAck !== this.summaryCollection.latestAck) {
|
|
1483
|
+
return {
|
|
1484
|
+
continue: false,
|
|
1485
|
+
// eslint-disable-next-line max-len
|
|
1486
|
+
error: `Last summary changed while summarizing. ${this.summaryCollection.latestAck} !== ${lastAck}`,
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1711
1489
|
return { continue: true };
|
|
1712
1490
|
};
|
|
1713
1491
|
let continueResult = checkContinue();
|
|
@@ -1726,7 +1504,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1726
1504
|
const forcedFullTree = this.garbageCollector.summaryStateNeedsReset;
|
|
1727
1505
|
try {
|
|
1728
1506
|
summarizeResult = await this.summarize({
|
|
1729
|
-
fullTree: fullTree
|
|
1507
|
+
fullTree: fullTree !== null && fullTree !== void 0 ? fullTree : forcedFullTree,
|
|
1730
1508
|
trackState: true,
|
|
1731
1509
|
summaryLogger: summaryNumberLogger,
|
|
1732
1510
|
runGC: this.garbageCollector.shouldRunGC,
|
|
@@ -1746,13 +1524,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1746
1524
|
// Counting dataStores and handles
|
|
1747
1525
|
// Because handles are unchanged dataStores in the current logic,
|
|
1748
1526
|
// summarized dataStore count is total dataStore count minus handle count
|
|
1749
|
-
const dataStoreTree =
|
|
1527
|
+
const dataStoreTree = summaryTree.tree[runtime_definitions_1.channelsTreeName];
|
|
1750
1528
|
(0, common_utils_1.assert)(dataStoreTree.type === protocol_definitions_1.SummaryType.Tree, 0x1fc /* "summary is not a tree" */);
|
|
1751
1529
|
const handleCount = Object.values(dataStoreTree.tree).filter((value) => value.type === protocol_definitions_1.SummaryType.Handle).length;
|
|
1752
1530
|
const gcSummaryTreeStats = summaryTree.tree[garbageCollection_1.gcTreeKey]
|
|
1753
1531
|
? (0, runtime_utils_1.calculateStats)(summaryTree.tree[garbageCollection_1.gcTreeKey])
|
|
1754
1532
|
: undefined;
|
|
1755
|
-
const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (
|
|
1533
|
+
const summaryStats = Object.assign({ dataStoreCount: this.dataStores.size, summarizedDataStoreCount: this.dataStores.size - handleCount, gcStateUpdatedDataStoreCount: (_a = summarizeResult.gcStats) === null || _a === void 0 ? void 0 : _a.updatedDataStoreCount, gcBlobNodeCount: gcSummaryTreeStats === null || gcSummaryTreeStats === void 0 ? void 0 : gcSummaryTreeStats.blobNodeCount, gcTotalBlobsSize: gcSummaryTreeStats === null || gcSummaryTreeStats === void 0 ? void 0 : gcSummaryTreeStats.totalBlobSize, summaryNumber }, partialStats);
|
|
1756
1534
|
const generateSummaryData = {
|
|
1757
1535
|
referenceSequenceNumber: summaryRefSeqNum,
|
|
1758
1536
|
minimumSequenceNumber,
|
|
@@ -1770,7 +1548,6 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1770
1548
|
// submitting the summaryOp then we can't rely on summaryAck. So in case we have
|
|
1771
1549
|
// latestSnapshotVersionId from storage and it does not match with the lastAck ackHandle, then use
|
|
1772
1550
|
// the one fetched from storage as parent as that is the latest.
|
|
1773
|
-
const lastAck = this.summaryCollection.latestAck;
|
|
1774
1551
|
let summaryContext;
|
|
1775
1552
|
if ((lastAck === null || lastAck === void 0 ? void 0 : lastAck.summaryAck.contents.handle) !== latestSnapshotVersionId
|
|
1776
1553
|
&& latestSnapshotVersionId !== undefined) {
|
|
@@ -1783,7 +1560,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1783
1560
|
else if (lastAck === undefined) {
|
|
1784
1561
|
summaryContext = {
|
|
1785
1562
|
proposalHandle: undefined,
|
|
1786
|
-
ackHandle: (
|
|
1563
|
+
ackHandle: (_b = this.context.getLoadedFromVersion()) === null || _b === void 0 ? void 0 : _b.id,
|
|
1787
1564
|
referenceSequenceNumber: summaryRefSeqNum,
|
|
1788
1565
|
};
|
|
1789
1566
|
}
|
|
@@ -1816,14 +1593,13 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1816
1593
|
}
|
|
1817
1594
|
let clientSequenceNumber;
|
|
1818
1595
|
try {
|
|
1819
|
-
clientSequenceNumber = this.
|
|
1596
|
+
clientSequenceNumber = this.submitSummaryMessage(summaryMessage);
|
|
1820
1597
|
}
|
|
1821
1598
|
catch (error) {
|
|
1822
1599
|
return Object.assign(Object.assign({ stage: "upload" }, uploadData), { error });
|
|
1823
1600
|
}
|
|
1824
1601
|
const submitData = Object.assign(Object.assign({ stage: "submit" }, uploadData), { clientSequenceNumber, submitOpDuration: trace.trace().duration });
|
|
1825
1602
|
this.summarizerNode.completeSummary(handle);
|
|
1826
|
-
this.opTracker.reset();
|
|
1827
1603
|
return submitData;
|
|
1828
1604
|
}
|
|
1829
1605
|
finally {
|
|
@@ -1865,7 +1641,17 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1865
1641
|
this.chunkMap.delete(clientId);
|
|
1866
1642
|
}
|
|
1867
1643
|
}
|
|
1644
|
+
hasPendingMessages() {
|
|
1645
|
+
return this.pendingStateManager.hasPendingMessages() || !this.emptyBatch;
|
|
1646
|
+
}
|
|
1868
1647
|
updateDocumentDirtyState(dirty) {
|
|
1648
|
+
if (this.attachState !== container_definitions_1.AttachState.Attached) {
|
|
1649
|
+
(0, common_utils_1.assert)(dirty, 0x3d2 /* Non-attached container is dirty */);
|
|
1650
|
+
}
|
|
1651
|
+
else {
|
|
1652
|
+
// Other way is not true = see this.isContainerMessageDirtyable()
|
|
1653
|
+
(0, common_utils_1.assert)(!dirty || this.hasPendingMessages(), 0x3d3 /* if doc is dirty, there has to be pending ops */);
|
|
1654
|
+
}
|
|
1869
1655
|
if (this.dirtyContainer === dirty) {
|
|
1870
1656
|
return;
|
|
1871
1657
|
}
|
|
@@ -1893,99 +1679,100 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
1893
1679
|
this.verifyNotClosed();
|
|
1894
1680
|
return this.blobManager.createBlob(blob);
|
|
1895
1681
|
}
|
|
1896
|
-
submit(type,
|
|
1682
|
+
submit(type, contents, localOpMetadata = undefined, metadata = undefined) {
|
|
1897
1683
|
this.verifyNotClosed();
|
|
1898
1684
|
// There should be no ops in detached container state!
|
|
1899
1685
|
(0, common_utils_1.assert)(this.attachState !== container_definitions_1.AttachState.Detached, 0x132 /* "sending ops in detached container" */);
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
if (this.
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1686
|
+
const deserializedContent = { type, contents };
|
|
1687
|
+
const serializedContent = JSON.stringify(deserializedContent);
|
|
1688
|
+
if (this.deltaManager.readOnlyInfo.readonly) {
|
|
1689
|
+
this.logger.sendErrorEvent({ eventName: "SubmitOpInReadonly" });
|
|
1690
|
+
}
|
|
1691
|
+
const message = {
|
|
1692
|
+
contents: serializedContent,
|
|
1693
|
+
deserializedContent,
|
|
1694
|
+
metadata,
|
|
1695
|
+
localOpMetadata,
|
|
1696
|
+
referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
|
|
1697
|
+
};
|
|
1698
|
+
try {
|
|
1699
|
+
// If this is attach message for new data store, and we are in a batch, send this op out of order
|
|
1700
|
+
// Is it safe:
|
|
1701
|
+
// Yes, this should be safe reordering. Newly created data stores are not visible through API surface.
|
|
1702
|
+
// They become visible only when aliased, or handle to some sub-element of newly created datastore
|
|
1703
|
+
// is stored in some DDS, i.e. only after some other op.
|
|
1704
|
+
// Why:
|
|
1705
|
+
// Attach ops are large, and expensive to process. Plus there are scenarios where a lot of new data
|
|
1706
|
+
// stores are created, causing issues like relay service throttling (too many ops) and catastrophic
|
|
1707
|
+
// failure (batch is too large). Pushing them earlier and outside of main batch should alleviate
|
|
1708
|
+
// these issues.
|
|
1709
|
+
// Cons:
|
|
1710
|
+
// 1. With large batches, relay service may throttle clients. Clients may disconnect while throttled.
|
|
1711
|
+
// This change creates new possibility of a lot of newly created data stores never being referenced
|
|
1712
|
+
// because client died before it had a change to submit the rest of the ops. This will create more
|
|
1713
|
+
// garbage that needs to be collected leveraging GC (Garbage Collection) feature.
|
|
1714
|
+
// 2. Sending ops out of order means they are excluded from rollback functionality. This is not an issue
|
|
1715
|
+
// today as rollback can't undo creation of data store. To some extent not sending them is a bigger
|
|
1716
|
+
// issue than sending.
|
|
1717
|
+
// Please note that this does not change file format, so it can be disabled in the future if this
|
|
1718
|
+
// optimization no longer makes sense (for example, batch compression may make it less appealing).
|
|
1719
|
+
if (type === ContainerMessageType.Attach &&
|
|
1720
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.disableAttachOpReorder") !== true) {
|
|
1721
|
+
if (!this.pendingAttachBatch.push(message)) {
|
|
1722
|
+
// BatchManager has two limits - soft limit & hard limit. Soft limit is only engaged
|
|
1723
|
+
// when queue is not empty.
|
|
1724
|
+
// Flush queue & retry. Failure on retry would mean - single message is bigger than hard limit
|
|
1725
|
+
this.flushBatch(this.pendingAttachBatch.popBatch());
|
|
1726
|
+
if (!this.pendingAttachBatch.push(message)) {
|
|
1727
|
+
throw new container_utils_1.GenericError("BatchTooLarge",
|
|
1728
|
+
/* error */ undefined, {
|
|
1729
|
+
opSize: message.contents.length,
|
|
1730
|
+
count: this.pendingAttachBatch.length,
|
|
1731
|
+
limit: this.pendingAttachBatch.limit,
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
else {
|
|
1737
|
+
if (!this.pendingBatch.push(message)) {
|
|
1738
|
+
throw new container_utils_1.GenericError("BatchTooLarge",
|
|
1739
|
+
/* error */ undefined, {
|
|
1740
|
+
opSize: message.contents.length,
|
|
1741
|
+
count: this.pendingBatch.length,
|
|
1742
|
+
limit: this.pendingBatch.limit,
|
|
1915
1743
|
});
|
|
1916
1744
|
}
|
|
1917
1745
|
}
|
|
1918
|
-
|
|
1746
|
+
if (this._flushMode !== runtime_definitions_1.FlushMode.TurnBased) {
|
|
1747
|
+
this.flush();
|
|
1748
|
+
}
|
|
1749
|
+
else if (!this.flushTrigger) {
|
|
1750
|
+
this.flushTrigger = true;
|
|
1751
|
+
// Queue a microtask to detect the end of the turn and force a flush.
|
|
1752
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1753
|
+
Promise.resolve().then(() => {
|
|
1754
|
+
this.flushTrigger = false;
|
|
1755
|
+
this.flush();
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1919
1758
|
}
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
this.updateDocumentDirtyState(true);
|
|
1759
|
+
catch (error) {
|
|
1760
|
+
this.closeFn(error);
|
|
1761
|
+
throw error;
|
|
1924
1762
|
}
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
if (this._maxOpSizeInBytes >= 0) {
|
|
1928
|
-
// Chunking disabled
|
|
1929
|
-
if (!serializedContent || serializedContent.length <= this._maxOpSizeInBytes) {
|
|
1930
|
-
return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
|
|
1931
|
-
}
|
|
1932
|
-
// When chunking is disabled, we ignore the server max message size
|
|
1933
|
-
// and if the content length is larger than the client configured message size
|
|
1934
|
-
// instead of splitting the content, we will fail by explicitly close the container
|
|
1935
|
-
this.closeFn(new container_utils_1.GenericError("OpTooLarge",
|
|
1936
|
-
/* error */ undefined, {
|
|
1937
|
-
length: {
|
|
1938
|
-
value: serializedContent.length,
|
|
1939
|
-
tag: telemetry_utils_1.TelemetryDataTag.PackageData,
|
|
1940
|
-
},
|
|
1941
|
-
limit: {
|
|
1942
|
-
value: this._maxOpSizeInBytes,
|
|
1943
|
-
tag: telemetry_utils_1.TelemetryDataTag.PackageData,
|
|
1944
|
-
},
|
|
1945
|
-
}));
|
|
1946
|
-
return -1;
|
|
1947
|
-
}
|
|
1948
|
-
// Chunking enabled, fallback on the server's max message size
|
|
1949
|
-
// and split the content accordingly
|
|
1950
|
-
if (!serializedContent || serializedContent.length <= serverMaxOpSize) {
|
|
1951
|
-
return this.submitRuntimeMessage(type, content, batch, opMetadataInternal);
|
|
1952
|
-
}
|
|
1953
|
-
return this.submitChunkedMessage(type, serializedContent, serverMaxOpSize);
|
|
1954
|
-
}
|
|
1955
|
-
submitChunkedMessage(type, content, maxOpSize) {
|
|
1956
|
-
const contentLength = content.length;
|
|
1957
|
-
const chunkN = Math.floor((contentLength - 1) / maxOpSize) + 1;
|
|
1958
|
-
let offset = 0;
|
|
1959
|
-
let clientSequenceNumber = 0;
|
|
1960
|
-
for (let i = 1; i <= chunkN; i = i + 1) {
|
|
1961
|
-
const chunkedOp = {
|
|
1962
|
-
chunkId: i,
|
|
1963
|
-
contents: content.substr(offset, maxOpSize),
|
|
1964
|
-
originalType: type,
|
|
1965
|
-
totalChunks: chunkN,
|
|
1966
|
-
};
|
|
1967
|
-
offset += maxOpSize;
|
|
1968
|
-
clientSequenceNumber = this.submitRuntimeMessage(ContainerMessageType.ChunkedOp, chunkedOp, false);
|
|
1763
|
+
if (this.isContainerMessageDirtyable(type, contents)) {
|
|
1764
|
+
this.updateDocumentDirtyState(true);
|
|
1969
1765
|
}
|
|
1970
|
-
return clientSequenceNumber;
|
|
1971
1766
|
}
|
|
1972
|
-
|
|
1767
|
+
submitSummaryMessage(contents) {
|
|
1973
1768
|
this.verifyNotClosed();
|
|
1974
1769
|
(0, common_utils_1.assert)(this.connected, 0x133 /* "Container disconnected when trying to submit system message" */);
|
|
1975
1770
|
// System message should not be sent in the middle of the batch.
|
|
1976
|
-
|
|
1977
|
-
//
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
this.
|
|
1981
|
-
}
|
|
1982
|
-
return this.context.submitFn(type, contents, middleOfBatch);
|
|
1983
|
-
}
|
|
1984
|
-
submitRuntimeMessage(type, contents, batch, appData) {
|
|
1985
|
-
this.verifyNotClosed();
|
|
1986
|
-
(0, common_utils_1.assert)(this.connected, 0x259 /* "Container disconnected when trying to submit system message" */);
|
|
1987
|
-
const payload = { type, contents };
|
|
1988
|
-
return this.context.submitFn(protocol_definitions_1.MessageType.Operation, payload, batch, appData);
|
|
1771
|
+
(0, common_utils_1.assert)(this.emptyBatch, 0x3d4 /* System op in the middle of a batch */);
|
|
1772
|
+
// back-compat: ADO #1385: Make this call unconditional in the future
|
|
1773
|
+
return this.context.submitSummaryFn !== undefined
|
|
1774
|
+
? this.context.submitSummaryFn(contents)
|
|
1775
|
+
: this.context.submitFn(protocol_definitions_1.MessageType.Summarize, contents, false);
|
|
1989
1776
|
}
|
|
1990
1777
|
/**
|
|
1991
1778
|
* Throw an error if the runtime is closed. Methods that are expected to potentially
|
|
@@ -2016,7 +1803,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
2016
1803
|
case ContainerMessageType.ChunkedOp:
|
|
2017
1804
|
throw new Error(`chunkedOp not expected here`);
|
|
2018
1805
|
case ContainerMessageType.BlobAttach:
|
|
2019
|
-
this.
|
|
1806
|
+
this.blobManager.reSubmit(opMetadata);
|
|
2020
1807
|
break;
|
|
2021
1808
|
case ContainerMessageType.Rejoin:
|
|
2022
1809
|
this.submit(type, content);
|
|
@@ -2039,13 +1826,18 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
2039
1826
|
/** Implementation of ISummarizerInternalsProvider.refreshLatestSummaryAck */
|
|
2040
1827
|
async refreshLatestSummaryAck(proposalHandle, ackHandle, summaryRefSeq, summaryLogger) {
|
|
2041
1828
|
const readAndParseBlob = async (id) => (0, driver_utils_1.readAndParse)(this.storage, id);
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
1829
|
+
// The call to fetch the snapshot is very expensive and not always needed.
|
|
1830
|
+
// It should only be done by the summarizerNode, if required.
|
|
1831
|
+
const snapshotTreeFetcher = async () => {
|
|
1832
|
+
const fetchResult = await this.fetchSnapshotFromStorage(ackHandle, summaryLogger, {
|
|
1833
|
+
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
1834
|
+
ackHandle,
|
|
1835
|
+
summaryRefSeq,
|
|
1836
|
+
fetchLatest: false,
|
|
1837
|
+
});
|
|
1838
|
+
return fetchResult.snapshotTree;
|
|
1839
|
+
};
|
|
1840
|
+
const result = await this.summarizerNode.refreshLatestSummary(proposalHandle, summaryRefSeq, snapshotTreeFetcher, readAndParseBlob, summaryLogger);
|
|
2049
1841
|
// Notify the garbage collector so it can update its latest summary state.
|
|
2050
1842
|
await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
|
|
2051
1843
|
}
|
|
@@ -2059,7 +1851,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
2059
1851
|
const { snapshotTree, versionId } = await this.fetchSnapshotFromStorage(null, summaryLogger, {
|
|
2060
1852
|
eventName: "RefreshLatestSummaryGetSnapshot",
|
|
2061
1853
|
fetchLatest: true,
|
|
2062
|
-
});
|
|
1854
|
+
}, driver_definitions_1.FetchSource.noCache);
|
|
2063
1855
|
const readAndParseBlob = async (id) => (0, driver_utils_1.readAndParse)(this.storage, id);
|
|
2064
1856
|
const latestSnapshotRefSeq = await (0, runtime_utils_1.seqFromTree)(snapshotTree, readAndParseBlob);
|
|
2065
1857
|
const result = await this.summarizerNode.refreshLatestSummary(undefined, latestSnapshotRefSeq, async () => snapshotTree, readAndParseBlob, summaryLogger);
|
|
@@ -2067,11 +1859,11 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
2067
1859
|
await this.garbageCollector.latestSummaryStateRefreshed(result, readAndParseBlob);
|
|
2068
1860
|
return { latestSnapshotRefSeq, latestSnapshotVersionId: versionId };
|
|
2069
1861
|
}
|
|
2070
|
-
async fetchSnapshotFromStorage(versionId, logger, event) {
|
|
1862
|
+
async fetchSnapshotFromStorage(versionId, logger, event, fetchSource) {
|
|
2071
1863
|
return telemetry_utils_1.PerformanceEvent.timedExecAsync(logger, event, async (perfEvent) => {
|
|
2072
1864
|
const stats = {};
|
|
2073
1865
|
const trace = common_utils_1.Trace.start();
|
|
2074
|
-
const versions = await this.storage.getVersions(versionId, 1);
|
|
1866
|
+
const versions = await this.storage.getVersions(versionId, 1, "refreshLatestSummaryAckFromServer", fetchSource);
|
|
2075
1867
|
(0, common_utils_1.assert)(!!versions && !!versions[0], 0x137 /* "Failed to get version from storage" */);
|
|
2076
1868
|
stats.getVersionDuration = trace.trace().duration;
|
|
2077
1869
|
const maybeSnapshot = await this.storage.getSnapshotTree(versions[0]);
|
|
@@ -2101,10 +1893,15 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
2101
1893
|
if (!((_a = this.mc.config.getBoolean("enableOfflineLoad")) !== null && _a !== void 0 ? _a : this.runtimeOptions.enableOfflineLoad)) {
|
|
2102
1894
|
throw new container_utils_1.UsageError("can't get state when offline load disabled");
|
|
2103
1895
|
}
|
|
1896
|
+
// Flush pending batch.
|
|
1897
|
+
// getPendingLocalState() is only exposed through Container.closeAndGetPendingLocalState(), so it's safe
|
|
1898
|
+
// to close current batch.
|
|
1899
|
+
this.flush();
|
|
2104
1900
|
const previousPendingState = this.context.pendingLocalState;
|
|
2105
1901
|
if (previousPendingState) {
|
|
2106
1902
|
return {
|
|
2107
1903
|
pending: this.pendingStateManager.getLocalState(),
|
|
1904
|
+
pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
|
|
2108
1905
|
snapshotBlobs: previousPendingState.snapshotBlobs,
|
|
2109
1906
|
baseSnapshot: previousPendingState.baseSnapshot,
|
|
2110
1907
|
savedOps: this.savedOps,
|
|
@@ -2114,6 +1911,7 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
2114
1911
|
(0, common_utils_1.assert)(!!this.baseSnapshotBlobs, 0x2e7 /* "Must serialize base snapshot blobs before getting runtime state" */);
|
|
2115
1912
|
return {
|
|
2116
1913
|
pending: this.pendingStateManager.getLocalState(),
|
|
1914
|
+
pendingAttachmentBlobs: this.blobManager.getPendingBlobs(),
|
|
2117
1915
|
snapshotBlobs: this.baseSnapshotBlobs,
|
|
2118
1916
|
baseSnapshot: this.context.baseSnapshot,
|
|
2119
1917
|
savedOps: this.savedOps,
|
|
@@ -2153,6 +1951,19 @@ class ContainerRuntime extends common_utils_1.TypedEventEmitter {
|
|
|
2153
1951
|
// we may not have seen every sequence number (because of system ops) so apply everything once we
|
|
2154
1952
|
// don't have any more saved ops
|
|
2155
1953
|
await this.pendingStateManager.applyStashedOpsAt();
|
|
1954
|
+
// If it's not the case, we should take it into account when calculating dirty state.
|
|
1955
|
+
(0, common_utils_1.assert)(this.context.attachState === container_definitions_1.AttachState.Attached, 0x3d5 /* this function is called for attached containers only */);
|
|
1956
|
+
if (!this.hasPendingMessages()) {
|
|
1957
|
+
this.updateDocumentDirtyState(false);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
validateSummaryHeuristicConfiguration(configuration) {
|
|
1961
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
1962
|
+
for (const prop in configuration) {
|
|
1963
|
+
if (typeof configuration[prop] === "number" && configuration[prop] < 0) {
|
|
1964
|
+
throw new container_utils_1.UsageError(`Summary heuristic configuration property "${prop}" cannot be less than 0`);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
2156
1967
|
}
|
|
2157
1968
|
}
|
|
2158
1969
|
exports.ContainerRuntime = ContainerRuntime;
|