@fluidframework/test-utils 2.0.0-internal.4.0.5 → 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 ADDED
@@ -0,0 +1,5 @@
1
+ # @fluidframework/test-utils
2
+
3
+ ## 2.0.0-internal.4.1.0
4
+
5
+ Dependency updates only.
@@ -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
- private processSynchronized;
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 isSequenceNumberSynchronized;
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;AAS7F,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAsB/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;;OAEG;IACU,kBAAkB,CAAC,GAAG,UAAU,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3E;;OAEG;IACU,6BAA6B,CAAC,CAC1C,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,GAAG,UAAU,EAAE,UAAU,EAAE;IAK5B;;;;;;;;;;;;;;;;;;OAkBG;YACW,mBAAmB;IA+EjC;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IA8BzB;;;;;OAKG;IACH,OAAO,CAAC,4BAA4B;IA4CpC;;;;;;;OAOG;YACW,qBAAqB;IAoCnC;;;OAGG;YACW,oBAAoB;IAclC;;OAEG;IACI,gBAAgB,CAAC,GAAG,UAAU,EAAE,UAAU,EAAE;IAgBnD;;;OAGG;IACU,eAAe,CAAC,GAAG,UAAU,EAAE,UAAU,EAAE;IAexD;;;;OAIG;IACU,eAAe,CAAC,GAAG,UAAU,EAAE,UAAU,EAAE;IAIxD;;;;OAIG;IACU,eAAe,CAAC,GAAG,UAAU,EAAE,UAAU,EAAE;IAIxD;;OAEG;YACW,YAAY;IA0C1B;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IA2BzB;;OAEG;IACH,OAAO,CAAC,UAAU;IAwElB;;;;OAIG;IACH,OAAO,CAAC,aAAa;CAKrB"}
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.processSynchronized(timeoutDuration, ...containers);
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 processSynchronized(timeoutDuration, ...containers) {
154
+ async ensureSynchronized(...containers) {
161
155
  const resumed = this.resumeProcessing(...containers);
162
- let waitingSequenceNumberSynchronized = false;
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 up and nothing can be done about it being dirty
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
- if (this.isSequenceNumberSynchronized(containersToApply)) {
176
+ const needSync = this.needSequenceNumberSynchronize(containersToApply);
177
+ if (needSync === undefined) {
179
178
  // done, we are in sync
180
179
  break;
181
180
  }
182
- if (!waitingSequenceNumberSynchronized) {
183
- // Only write it out once
184
- waitingSequenceNumberSynchronized = true;
185
- debugWait("Waiting for sequence number synchronized");
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 = false;
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 ${dirtyContainers.map(
201
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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
- isSequenceNumberSynchronized(containersToApply) {
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 isClientSequenceNumberSynchronized = containersToApply.every((container) => {
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 true;
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 & clientSequenceNumberObserved moved to ConnectionManager in 0.53
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
- return (deltaManager.clientSequenceNumber ===
285
- deltaManager.clientSequenceNumberObserved + trailingNoOps);
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 (!isClientSequenceNumberSynchronized) {
288
- return false;
289
- }
290
- const minSeqNum = containersToApply[0].deltaManager.minimumSequenceNumber;
291
- if (minSeqNum < this.lastProposalSeqNum) {
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 seqNum = containersToApply[0].deltaManager.lastSequenceNumber;
297
- return containersToApply.every((c) => c.deltaManager.lastSequenceNumber === seqNum);
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 pauseP = [];
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
- debugWait(`${record.index}: container paused`);
380
- pauseP.push(container.deltaManager.inbound.pause());
381
- pauseP.push(container.deltaManager.outbound.pause());
382
- record.paused = true;
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(pauseP);
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.