@fluidframework/test-utils 2.0.0-internal.4.0.6 → 2.0.0-internal.4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/dist/loaderContainerTracker.d.ts +30 -15
- package/dist/loaderContainerTracker.d.ts.map +1 -1
- package/dist/loaderContainerTracker.js +136 -57
- package/dist/loaderContainerTracker.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/testObjectProvider.d.ts +3 -0
- package/dist/testObjectProvider.d.ts.map +1 -1
- package/dist/testObjectProvider.js +28 -9
- package/dist/testObjectProvider.js.map +1 -1
- package/package.json +27 -23
- package/src/loaderContainerTracker.ts +167 -63
- package/src/packageVersion.ts +1 -1
- package/src/testObjectProvider.ts +53 -10
package/CHANGELOG.md
ADDED
|
@@ -29,34 +29,29 @@ export declare class LoaderContainerTracker implements IOpProcessingController {
|
|
|
29
29
|
* Reset the tracker, closing all containers and stop tracking them.
|
|
30
30
|
*/
|
|
31
31
|
reset(): void;
|
|
32
|
-
/**
|
|
33
|
-
* Ensure all tracked containers are synchronized
|
|
34
|
-
*/
|
|
35
|
-
ensureSynchronized(...containers: IContainer[]): Promise<void>;
|
|
36
32
|
/**
|
|
37
33
|
* Ensure all tracked containers are synchronized with a time limit
|
|
34
|
+
*
|
|
35
|
+
* @deprecated - this method is equivalent to @see {@link LoaderContainerTracker.ensureSynchronized}, please configure the test timeout instead
|
|
38
36
|
*/
|
|
39
37
|
ensureSynchronizedWithTimeout?(timeoutDuration: number | undefined, ...containers: IContainer[]): Promise<void>;
|
|
40
38
|
/**
|
|
41
39
|
* Make sure all the tracked containers are synchronized.
|
|
42
40
|
*
|
|
43
41
|
* No isDirty (non-readonly) containers
|
|
44
|
-
*
|
|
45
42
|
* No extra clientId in quorum of any container that is not tracked and still opened.
|
|
46
|
-
*
|
|
47
43
|
* - i.e. no pending Join/Leave message.
|
|
48
|
-
*
|
|
49
44
|
* No unresolved proposal (minSeqNum \>= lastProposalSeqNum)
|
|
50
|
-
*
|
|
51
45
|
* lastSequenceNumber of all container is the same
|
|
52
|
-
*
|
|
53
46
|
* clientSequenceNumberObserved is the same as clientSequenceNumber sent
|
|
54
|
-
*
|
|
55
47
|
* - this overlaps with !isDirty, but include task scheduler ops.
|
|
56
|
-
*
|
|
57
48
|
* - Trailing NoOp is tracked and don't count as pending ops.
|
|
49
|
+
*
|
|
50
|
+
* Containers that are already pause will resume process and paused again once
|
|
51
|
+
* everything is synchronized. Containers that aren't paused will remain unpaused when this
|
|
52
|
+
* function returns.
|
|
58
53
|
*/
|
|
59
|
-
|
|
54
|
+
ensureSynchronized(...containers: IContainer[]): Promise<void>;
|
|
60
55
|
/**
|
|
61
56
|
* Utility to calculate the set of clientId per container in quorum that is NOT associated with
|
|
62
57
|
* any container we tracked, indicating there is a pending join or leave op that we need to wait.
|
|
@@ -70,7 +65,8 @@ export declare class LoaderContainerTracker implements IOpProcessingController {
|
|
|
70
65
|
*
|
|
71
66
|
* @param containersToApply - the set of containers to check
|
|
72
67
|
*/
|
|
73
|
-
private
|
|
68
|
+
private needSequenceNumberSynchronize;
|
|
69
|
+
private containerIndexStrings;
|
|
74
70
|
/**
|
|
75
71
|
* Utility to wait for any clientId in quorum that is NOT associated with any container we
|
|
76
72
|
* tracked, indicating there is a pending join or leave op that we need to wait.
|
|
@@ -92,18 +88,37 @@ export declare class LoaderContainerTracker implements IOpProcessingController {
|
|
|
92
88
|
/**
|
|
93
89
|
* Pause all queue activities on the containers given, or all tracked containers
|
|
94
90
|
* Any containers given that is not tracked will be ignored.
|
|
91
|
+
*
|
|
92
|
+
* When a container is paused, it is assumed that we want fine grain control over op
|
|
93
|
+
* sequencing. This function will prepare the container and force it into write mode to
|
|
94
|
+
* avoid missing join messages or change the sequence of event when switching from read to
|
|
95
|
+
* write mode.
|
|
95
96
|
*/
|
|
96
97
|
pauseProcessing(...containers: IContainer[]): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* When a container is paused, it is assumed that we want fine grain control over op
|
|
100
|
+
* sequencing. This function will prepare the container and force it into write mode to
|
|
101
|
+
* avoid missing join messages or change the sequence of event when switching from read to
|
|
102
|
+
* write mode.
|
|
103
|
+
*
|
|
104
|
+
* @param container - the container to pause
|
|
105
|
+
* @param record - the record for the container
|
|
106
|
+
*/
|
|
107
|
+
private pauseContainer;
|
|
97
108
|
/**
|
|
98
109
|
* Pause all queue activities on all tracked containers, and resume only
|
|
99
110
|
* inbound to process ops until it is idle. All queues are left in the paused state
|
|
100
|
-
* after the function
|
|
111
|
+
* after the function.
|
|
112
|
+
*
|
|
113
|
+
* Pausing will switch the container to write mode. See `pauseProcessing`
|
|
101
114
|
*/
|
|
102
115
|
processIncoming(...containers: IContainer[]): Promise<void>;
|
|
103
116
|
/**
|
|
104
117
|
* Pause all queue activities on all tracked containers, and resume only
|
|
105
118
|
* outbound to process ops until it is idle. All queues are left in the paused state
|
|
106
|
-
* after the function
|
|
119
|
+
* after the function.
|
|
120
|
+
*
|
|
121
|
+
* Pausing will switch the container to write mode. See `pauseProcessing`
|
|
107
122
|
*/
|
|
108
123
|
processOutgoing(...containers: IContainer[]): Promise<void>;
|
|
109
124
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loaderContainerTracker.d.ts","sourceRoot":"","sources":["../src/loaderContainerTracker.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAe,WAAW,EAAE,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"loaderContainerTracker.d.ts","sourceRoot":"","sources":["../src/loaderContainerTracker.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAe,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAU7F,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAuB/D,qBAAa,sBAAuB,YAAW,uBAAuB;IAIzD,OAAO,CAAC,QAAQ,CAAC,qBAAqB;IAHlD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA0C;IACrE,OAAO,CAAC,kBAAkB,CAAa;gBAEV,qBAAqB,GAAE,OAAe;IAEnE;;;OAGG;IACI,GAAG,CAAC,UAAU,SAAS,WAAW,EAAE,MAAM,EAAE,UAAU;IAmB7D;;;;OAIG;IACH,OAAO,CAAC,YAAY;IA2BpB;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IAuC1B,OAAO,CAAC,iBAAiB;IAQzB;;OAEG;IACI,KAAK;IAUZ;;;;OAIG;IACU,6BAA6B,CAAC,CAC1C,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,GAAG,UAAU,EAAE,UAAU,EAAE;IAK5B;;;;;;;;;;;;;;;OAeG;IACU,kBAAkB,CAAC,GAAG,UAAU,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA2E3E;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IA8BzB;;;;;OAKG;IACH,OAAO,CAAC,6BAA6B;IAmErC,OAAO,CAAC,qBAAqB;IAO7B;;;;;;;OAOG;YACW,qBAAqB;IAoCnC;;;OAGG;YACW,oBAAoB;IAclC;;OAEG;IACI,gBAAgB,CAAC,GAAG,UAAU,EAAE,UAAU,EAAE;IAoBnD;;;;;;;;OAQG;IACU,eAAe,CAAC,GAAG,UAAU,EAAE,UAAU,EAAE;IAexD;;;;;;;;OAQG;YACW,cAAc;IAwD5B;;;;;;OAMG;IACU,eAAe,CAAC,GAAG,UAAU,EAAE,UAAU,EAAE;IAIxD;;;;;;OAMG;IACU,eAAe,CAAC,GAAG,UAAU,EAAE,UAAU,EAAE;IAIxD;;OAEG;YACW,YAAY;IAgD1B;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IA2BzB;;OAEG;IACH,OAAO,CAAC,UAAU;IAwElB;;;;OAIG;IACH,OAAO,CAAC,aAAa;CAKrB"}
|
|
@@ -9,6 +9,7 @@ const common_utils_1 = require("@fluidframework/common-utils");
|
|
|
9
9
|
const container_loader_1 = require("@fluidframework/container-loader");
|
|
10
10
|
const driver_utils_1 = require("@fluidframework/driver-utils");
|
|
11
11
|
const protocol_definitions_1 = require("@fluidframework/protocol-definitions");
|
|
12
|
+
const containerUtils_1 = require("./containerUtils");
|
|
12
13
|
const debug_1 = require("./debug");
|
|
13
14
|
const timeoutUtils_1 = require("./timeoutUtils");
|
|
14
15
|
const debugOp = debug_1.debug.extend("ops");
|
|
@@ -126,47 +127,44 @@ class LoaderContainerTracker {
|
|
|
126
127
|
this.containers.clear();
|
|
127
128
|
// REVIEW: do we need to unpatch the loaders?
|
|
128
129
|
}
|
|
129
|
-
/**
|
|
130
|
-
* Ensure all tracked containers are synchronized
|
|
131
|
-
*/
|
|
132
|
-
async ensureSynchronized(...containers) {
|
|
133
|
-
await this.processSynchronized(undefined, ...containers);
|
|
134
|
-
}
|
|
135
130
|
/**
|
|
136
131
|
* Ensure all tracked containers are synchronized with a time limit
|
|
132
|
+
*
|
|
133
|
+
* @deprecated - this method is equivalent to @see {@link LoaderContainerTracker.ensureSynchronized}, please configure the test timeout instead
|
|
137
134
|
*/
|
|
138
135
|
async ensureSynchronizedWithTimeout(timeoutDuration, ...containers) {
|
|
139
|
-
await this.
|
|
136
|
+
await this.ensureSynchronized(...containers);
|
|
140
137
|
}
|
|
141
138
|
/**
|
|
142
139
|
* Make sure all the tracked containers are synchronized.
|
|
143
140
|
*
|
|
144
141
|
* No isDirty (non-readonly) containers
|
|
145
|
-
*
|
|
146
142
|
* No extra clientId in quorum of any container that is not tracked and still opened.
|
|
147
|
-
*
|
|
148
143
|
* - i.e. no pending Join/Leave message.
|
|
149
|
-
*
|
|
150
144
|
* No unresolved proposal (minSeqNum \>= lastProposalSeqNum)
|
|
151
|
-
*
|
|
152
145
|
* lastSequenceNumber of all container is the same
|
|
153
|
-
*
|
|
154
146
|
* clientSequenceNumberObserved is the same as clientSequenceNumber sent
|
|
155
|
-
*
|
|
156
147
|
* - this overlaps with !isDirty, but include task scheduler ops.
|
|
157
|
-
*
|
|
158
148
|
* - Trailing NoOp is tracked and don't count as pending ops.
|
|
149
|
+
*
|
|
150
|
+
* Containers that are already pause will resume process and paused again once
|
|
151
|
+
* everything is synchronized. Containers that aren't paused will remain unpaused when this
|
|
152
|
+
* function returns.
|
|
159
153
|
*/
|
|
160
|
-
async
|
|
154
|
+
async ensureSynchronized(...containers) {
|
|
161
155
|
const resumed = this.resumeProcessing(...containers);
|
|
162
|
-
let waitingSequenceNumberSynchronized
|
|
156
|
+
let waitingSequenceNumberSynchronized;
|
|
163
157
|
// eslint-disable-next-line no-constant-condition
|
|
164
158
|
while (true) {
|
|
159
|
+
// yield a turn to allow side effect of resuming or the ops we just processed execute before we check
|
|
160
|
+
await new Promise((resolve) => {
|
|
161
|
+
setTimeout(resolve, 0);
|
|
162
|
+
});
|
|
165
163
|
const containersToApply = this.getContainers(containers);
|
|
166
164
|
if (containersToApply.length === 0) {
|
|
167
165
|
break;
|
|
168
166
|
}
|
|
169
|
-
// Ignore readonly dirty containers, because it can't sent
|
|
167
|
+
// Ignore readonly dirty containers, because it can't sent ops and nothing can be done about it being dirty
|
|
170
168
|
const dirtyContainers = containersToApply.filter((c) => {
|
|
171
169
|
const { deltaManager, isDirty } = c;
|
|
172
170
|
return deltaManager.readOnlyInfo.readonly !== true && isDirty;
|
|
@@ -175,21 +173,23 @@ class LoaderContainerTracker {
|
|
|
175
173
|
// Wait for all the leave messages
|
|
176
174
|
const pendingClients = this.getPendingClients(containersToApply);
|
|
177
175
|
if (pendingClients.length === 0) {
|
|
178
|
-
|
|
176
|
+
const needSync = this.needSequenceNumberSynchronize(containersToApply);
|
|
177
|
+
if (needSync === undefined) {
|
|
179
178
|
// done, we are in sync
|
|
180
179
|
break;
|
|
181
180
|
}
|
|
182
|
-
if (
|
|
183
|
-
//
|
|
184
|
-
waitingSequenceNumberSynchronized =
|
|
185
|
-
debugWait(
|
|
186
|
-
await (0, timeoutUtils_1.timeoutAwait)(this.waitForAnyInboundOps(containersToApply), {
|
|
187
|
-
errorMsg: "Timeout on waiting for sequence number synchronized",
|
|
188
|
-
});
|
|
181
|
+
if (waitingSequenceNumberSynchronized !== needSync.reason) {
|
|
182
|
+
// Don't repeat writing to console if it is the same reason
|
|
183
|
+
waitingSequenceNumberSynchronized = needSync.reason;
|
|
184
|
+
debugWait(needSync.message);
|
|
189
185
|
}
|
|
186
|
+
// Wait for one inbounds ops which might change the state of things
|
|
187
|
+
await (0, timeoutUtils_1.timeoutAwait)(this.waitForAnyInboundOps(containersToApply), {
|
|
188
|
+
errorMsg: `Timeout on ${needSync.message}`,
|
|
189
|
+
});
|
|
190
190
|
}
|
|
191
191
|
else {
|
|
192
|
-
waitingSequenceNumberSynchronized =
|
|
192
|
+
waitingSequenceNumberSynchronized = undefined;
|
|
193
193
|
await (0, timeoutUtils_1.timeoutAwait)(this.waitForPendingClients(pendingClients), {
|
|
194
194
|
errorMsg: "Timeout on waiting for pending join or leave op",
|
|
195
195
|
});
|
|
@@ -197,10 +197,8 @@ class LoaderContainerTracker {
|
|
|
197
197
|
}
|
|
198
198
|
else {
|
|
199
199
|
// Wait for all the containers to be saved
|
|
200
|
-
debugWait(`Waiting container to be saved ${
|
|
201
|
-
|
|
202
|
-
(c) => this.containers.get(c).index)}`);
|
|
203
|
-
waitingSequenceNumberSynchronized = false;
|
|
200
|
+
debugWait(`Waiting container to be saved ${this.containerIndexStrings(dirtyContainers)}`);
|
|
201
|
+
waitingSequenceNumberSynchronized = undefined;
|
|
204
202
|
await Promise.all(dirtyContainers.map(async (c) => Promise.race([
|
|
205
203
|
(0, timeoutUtils_1.timeoutPromise)((resolve) => c.once("saved", () => resolve()), {
|
|
206
204
|
errorMsg: "Timeout on waiting a container to be saved",
|
|
@@ -208,10 +206,6 @@ class LoaderContainerTracker {
|
|
|
208
206
|
new Promise((resolve) => c.once("closed", resolve)),
|
|
209
207
|
])));
|
|
210
208
|
}
|
|
211
|
-
// yield a turn to allow side effect of the ops we just processed execute before we check again
|
|
212
|
-
await new Promise((resolve) => {
|
|
213
|
-
setTimeout(resolve, 0);
|
|
214
|
-
});
|
|
215
209
|
}
|
|
216
210
|
// Pause all container that was resumed
|
|
217
211
|
// don't call pause if resumed is empty and pause everything, which is not what we want
|
|
@@ -259,42 +253,62 @@ class LoaderContainerTracker {
|
|
|
259
253
|
*
|
|
260
254
|
* @param containersToApply - the set of containers to check
|
|
261
255
|
*/
|
|
262
|
-
|
|
256
|
+
needSequenceNumberSynchronize(containersToApply) {
|
|
257
|
+
// If there is a pending proposal, wait for it to be accepted
|
|
258
|
+
const minSeqNum = containersToApply[0].deltaManager.minimumSequenceNumber;
|
|
259
|
+
if (minSeqNum < this.lastProposalSeqNum) {
|
|
260
|
+
return {
|
|
261
|
+
reason: "Proposal",
|
|
262
|
+
message: `waiting for MSN to advance to proposal at sequence number ${this.lastProposalSeqNum}`,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
263
265
|
// clientSequenceNumber check detects ops in flight, both on the wire and in the outbound queue
|
|
264
266
|
// We need both client sequence number and isDirty check because:
|
|
265
267
|
// - Currently isDirty flag ignores ops for task scheduler, so we need the client sequence number check
|
|
266
268
|
// - But isDirty flags include ops during forceReadonly and disconnected, because we don't submit
|
|
267
269
|
// the ops in the first place, clientSequenceNumber is not assigned
|
|
268
|
-
const
|
|
270
|
+
const containerWithInflightOps = containersToApply.filter((container) => {
|
|
269
271
|
if (container.deltaManager.readOnlyInfo.readonly === true) {
|
|
270
272
|
// Ignore readonly container. the clientSeqNum and clientSeqNumObserved might be out of sync
|
|
271
273
|
// because we transition to readonly when outbound is not empty or the in transit op got lost
|
|
272
|
-
return
|
|
274
|
+
return false;
|
|
273
275
|
}
|
|
274
276
|
// Note that in read only mode, the op won't be submitted
|
|
275
277
|
let deltaManager = container.deltaManager;
|
|
276
278
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
277
279
|
const { trailingNoOps } = this.containers.get(container);
|
|
278
|
-
// Back-compat: clientSequenceNumber
|
|
280
|
+
// Back-compat: lastSubmittedClientId/clientSequenceNumber/clientSequenceNumberObserved moved to ConnectionManager in 0.53
|
|
279
281
|
if (!("clientSequenceNumber" in deltaManager)) {
|
|
280
282
|
deltaManager = deltaManager.connectionManager;
|
|
281
283
|
}
|
|
282
284
|
(0, common_utils_1.assert)("clientSequenceNumber" in deltaManager, "no clientSequenceNumber");
|
|
283
285
|
(0, common_utils_1.assert)("clientSequenceNumberObserved" in deltaManager, "no clientSequenceNumber");
|
|
284
|
-
|
|
285
|
-
|
|
286
|
+
// If last submittedClientId isn't the current clientId, then we haven't send any ops
|
|
287
|
+
return (deltaManager.lastSubmittedClientId === container.clientId &&
|
|
288
|
+
deltaManager.clientSequenceNumber !==
|
|
289
|
+
deltaManager.clientSequenceNumberObserved + trailingNoOps);
|
|
286
290
|
});
|
|
287
|
-
if (
|
|
288
|
-
return
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
// There is an unresolved proposal
|
|
293
|
-
return false;
|
|
291
|
+
if (containerWithInflightOps.length !== 0) {
|
|
292
|
+
return {
|
|
293
|
+
reason: "InflightOps",
|
|
294
|
+
message: `waiting for containers with inflight ops: ${this.containerIndexStrings(containerWithInflightOps)}`,
|
|
295
|
+
};
|
|
294
296
|
}
|
|
295
297
|
// Check to see if all the container has process the same number of ops.
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
+
const maxSeqNum = Math.max(...containersToApply.map((c) => c.deltaManager.lastSequenceNumber));
|
|
299
|
+
const containerWithPendingIncoming = containersToApply.filter((c) => c.deltaManager.lastSequenceNumber !== maxSeqNum);
|
|
300
|
+
if (containerWithPendingIncoming.length !== 0) {
|
|
301
|
+
return {
|
|
302
|
+
reason: "Pending",
|
|
303
|
+
message: `waiting for containers with pending incoming ops up to sequence number ${maxSeqNum}: ${this.containerIndexStrings(containerWithPendingIncoming)}`,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
containerIndexStrings(containers) {
|
|
309
|
+
return containers.map(
|
|
310
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
311
|
+
(c) => this.containers.get(c).index);
|
|
298
312
|
}
|
|
299
313
|
/**
|
|
300
314
|
* Utility to wait for any clientId in quorum that is NOT associated with any container we
|
|
@@ -356,6 +370,7 @@ class LoaderContainerTracker {
|
|
|
356
370
|
const containersToApply = this.getContainers(containers);
|
|
357
371
|
for (const container of containersToApply) {
|
|
358
372
|
const record = this.containers.get(container);
|
|
373
|
+
(0, common_utils_1.assert)((record === null || record === void 0 ? void 0 : record.pauseP) === undefined, "Cannot resume container while pausing is in progress");
|
|
359
374
|
if ((record === null || record === void 0 ? void 0 : record.paused) === true) {
|
|
360
375
|
debugWait(`${record.index}: container resumed`);
|
|
361
376
|
container.deltaManager.inbound.resume();
|
|
@@ -369,25 +384,86 @@ class LoaderContainerTracker {
|
|
|
369
384
|
/**
|
|
370
385
|
* Pause all queue activities on the containers given, or all tracked containers
|
|
371
386
|
* Any containers given that is not tracked will be ignored.
|
|
387
|
+
*
|
|
388
|
+
* When a container is paused, it is assumed that we want fine grain control over op
|
|
389
|
+
* sequencing. This function will prepare the container and force it into write mode to
|
|
390
|
+
* avoid missing join messages or change the sequence of event when switching from read to
|
|
391
|
+
* write mode.
|
|
372
392
|
*/
|
|
373
393
|
async pauseProcessing(...containers) {
|
|
374
|
-
const
|
|
394
|
+
const waitP = [];
|
|
375
395
|
const containersToApply = this.getContainers(containers);
|
|
376
396
|
for (const container of containersToApply) {
|
|
377
397
|
const record = this.containers.get(container);
|
|
378
398
|
if (record !== undefined && !record.paused) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
record.
|
|
399
|
+
if (record.pauseP === undefined) {
|
|
400
|
+
record.pauseP = this.pauseContainer(container, record);
|
|
401
|
+
}
|
|
402
|
+
waitP.push(record.pauseP);
|
|
383
403
|
}
|
|
384
404
|
}
|
|
385
|
-
await Promise.all(
|
|
405
|
+
await Promise.all(waitP);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* When a container is paused, it is assumed that we want fine grain control over op
|
|
409
|
+
* sequencing. This function will prepare the container and force it into write mode to
|
|
410
|
+
* avoid missing join messages or change the sequence of event when switching from read to
|
|
411
|
+
* write mode.
|
|
412
|
+
*
|
|
413
|
+
* @param container - the container to pause
|
|
414
|
+
* @param record - the record for the container
|
|
415
|
+
*/
|
|
416
|
+
async pauseContainer(container, record) {
|
|
417
|
+
debugWait(`${record.index}: pausing container`);
|
|
418
|
+
(0, common_utils_1.assert)(!container.deltaManager.outbound.paused, "Container should not be paused yet");
|
|
419
|
+
(0, common_utils_1.assert)(!container.deltaManager.inbound.paused, "Container should not be paused yet");
|
|
420
|
+
// Pause outbound
|
|
421
|
+
debugWait(`${record.index}: pausing container outbound queues`);
|
|
422
|
+
await container.deltaManager.outbound.pause();
|
|
423
|
+
// Ensure the container is connected first.
|
|
424
|
+
if (container.connectionState !== container_loader_1.ConnectionState.Connected) {
|
|
425
|
+
debugWait(`${record.index}: Wait for container connection`);
|
|
426
|
+
await (0, containerUtils_1.waitForContainerConnection)(container);
|
|
427
|
+
}
|
|
428
|
+
// Check if the container is in write mode
|
|
429
|
+
if (!container.deltaManager.active) {
|
|
430
|
+
let proposalP;
|
|
431
|
+
if (container.deltaManager.outbound.idle) {
|
|
432
|
+
// Need to generate an op to force write mode
|
|
433
|
+
debugWait(`${record.index}: container force write connection`);
|
|
434
|
+
const maybeContainer = container;
|
|
435
|
+
const codeProposal = maybeContainer.getLoadedCodeDetails
|
|
436
|
+
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
437
|
+
container.getLoadedCodeDetails()
|
|
438
|
+
: container.chaincodePackage;
|
|
439
|
+
proposalP = container.proposeCodeDetails(codeProposal);
|
|
440
|
+
}
|
|
441
|
+
// Wait for nack
|
|
442
|
+
debugWait(`${record.index}: Wait for container disconnect`);
|
|
443
|
+
container.deltaManager.outbound.resume();
|
|
444
|
+
await new Promise((resolve) => container.once("disconnected", resolve));
|
|
445
|
+
const accepted = proposalP ? await proposalP : false;
|
|
446
|
+
(0, common_utils_1.assert)(!accepted, "A proposal in read mode should be rejected");
|
|
447
|
+
await container.deltaManager.outbound.pause();
|
|
448
|
+
// Ensure the container is reconnect.
|
|
449
|
+
if (container.connectionState !== container_loader_1.ConnectionState.Connected) {
|
|
450
|
+
debugWait(`${record.index}: Wait for container reconnection`);
|
|
451
|
+
await (0, containerUtils_1.waitForContainerConnection)(container);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
debugWait(`${record.index}: pausing container inbound queues`);
|
|
455
|
+
// Pause inbound
|
|
456
|
+
await container.deltaManager.inbound.pause();
|
|
457
|
+
debugWait(`${record.index}: container paused`);
|
|
458
|
+
record.pauseP = undefined;
|
|
459
|
+
record.paused = true;
|
|
386
460
|
}
|
|
387
461
|
/**
|
|
388
462
|
* Pause all queue activities on all tracked containers, and resume only
|
|
389
463
|
* inbound to process ops until it is idle. All queues are left in the paused state
|
|
390
|
-
* after the function
|
|
464
|
+
* after the function.
|
|
465
|
+
*
|
|
466
|
+
* Pausing will switch the container to write mode. See `pauseProcessing`
|
|
391
467
|
*/
|
|
392
468
|
async processIncoming(...containers) {
|
|
393
469
|
return this.processQueue(containers, (container) => container.deltaManager.inbound);
|
|
@@ -395,7 +471,9 @@ class LoaderContainerTracker {
|
|
|
395
471
|
/**
|
|
396
472
|
* Pause all queue activities on all tracked containers, and resume only
|
|
397
473
|
* outbound to process ops until it is idle. All queues are left in the paused state
|
|
398
|
-
* after the function
|
|
474
|
+
* after the function.
|
|
475
|
+
*
|
|
476
|
+
* Pausing will switch the container to write mode. See `pauseProcessing`
|
|
399
477
|
*/
|
|
400
478
|
async processOutgoing(...containers) {
|
|
401
479
|
return this.processQueue(containers, (container) => container.deltaManager.outbound);
|
|
@@ -410,6 +488,7 @@ class LoaderContainerTracker {
|
|
|
410
488
|
const inflightTracker = new Map();
|
|
411
489
|
const cleanup = [];
|
|
412
490
|
for (const container of containersToApply) {
|
|
491
|
+
(0, common_utils_1.assert)(container.deltaManager.active, "Container should be connected in write mode already");
|
|
413
492
|
const queue = getQueue(container);
|
|
414
493
|
// track the outgoing ops (if any) to make sure they make the round trip to at least to the same client
|
|
415
494
|
// to make sure they are sequenced.
|