@fluidframework/sequence 2.0.0-dev-rc.1.0.0.228517 → 2.0.0-dev-rc.1.0.0.232845

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/src/sequence.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
+ import Deque from "double-ended-queue";
6
7
  import { assert, Deferred } from "@fluidframework/core-utils";
7
8
  import { bufferToString } from "@fluid-internal/client-utils";
8
9
  import { LoggingError, createChildLogger } from "@fluidframework/telemetry-utils";
@@ -212,6 +213,17 @@ export abstract class SharedSegmentSequence<T extends ISegment>
212
213
  return ops;
213
214
  }
214
215
 
216
+ /**
217
+ * Note: this field only provides a lower-bound on the reference sequence numbers for in-flight ops.
218
+ * The exact reason isn't understood, but some e2e tests suggest that the runtime may sometimes process
219
+ * incoming leave/join ops before putting an op that this DDS submits over the wire.
220
+ *
221
+ * E.g. SharedString submits an op while deltaManager has lastSequenceNumber = 10, but before the runtime
222
+ * puts this op over the wire, it processes a client join/leave op with sequence number 11, so the referenceSequenceNumber
223
+ * on the SharedString op is 11.
224
+ */
225
+ private readonly inFlightRefSeqs = new Deque<number>();
226
+
215
227
  // eslint-disable-next-line import/no-deprecated
216
228
  protected client: Client;
217
229
  /** `Deferred` that triggers once the object is loaded */
@@ -233,6 +245,7 @@ export abstract class SharedSegmentSequence<T extends ISegment>
233
245
  ) {
234
246
  super(id, dataStoreRuntime, attributes, "fluid_sequence_");
235
247
 
248
+ const getMinInFlightRefSeq = () => this.inFlightRefSeqs.get(0);
236
249
  this.guardReentrancy =
237
250
  dataStoreRuntime.options.sharedStringPreventReentrancy ?? true
238
251
  ? ensureNoReentrancy
@@ -258,6 +271,7 @@ export abstract class SharedSegmentSequence<T extends ISegment>
258
271
  namespace: "SharedSegmentSequence.MergeTreeClient",
259
272
  }),
260
273
  dataStoreRuntime.options,
274
+ getMinInFlightRefSeq,
261
275
  );
262
276
 
263
277
  this.client.prependListener("delta", (opArgs, deltaArgs) => {
@@ -275,7 +289,14 @@ export abstract class SharedSegmentSequence<T extends ISegment>
275
289
  this.intervalCollections = new DefaultMap(
276
290
  this.serializer,
277
291
  this.handle,
278
- (op, localOpMetadata) => this.submitLocalMessage(op, localOpMetadata),
292
+ (op, localOpMetadata) => {
293
+ if (!this.isAttached()) {
294
+ return;
295
+ }
296
+
297
+ this.inFlightRefSeqs.push(this.runtime.deltaManager.lastSequenceNumber);
298
+ this.submitLocalMessage(op, localOpMetadata);
299
+ },
279
300
  new SequenceIntervalCollectionValueType(),
280
301
  dataStoreRuntime.options,
281
302
  );
@@ -427,6 +448,8 @@ export abstract class SharedSegmentSequence<T extends ISegment>
427
448
  if (!this.isAttached()) {
428
449
  return;
429
450
  }
451
+
452
+ this.inFlightRefSeqs.push(this.runtime.deltaManager.lastSequenceNumber);
430
453
  const translated = makeHandlesSerializable(message, this.serializer, this.handle);
431
454
  const metadata = this.client.peekPendingSegmentGroups(
432
455
  message.type === MergeTreeDeltaType.GROUP ? message.ops.length : 1,
@@ -596,6 +619,8 @@ export abstract class SharedSegmentSequence<T extends ISegment>
596
619
  * {@inheritDoc @fluidframework/shared-object-base#SharedObject.reSubmitCore}
597
620
  */
598
621
  protected reSubmitCore(content: any, localOpMetadata: unknown) {
622
+ const originalRefSeq = this.inFlightRefSeqs.shift();
623
+ assert(originalRefSeq !== undefined, "Expected a recorded refSeq when resubmitting an op");
599
624
  if (
600
625
  !this.intervalCollections.tryResubmitMessage(
601
626
  content,
@@ -683,6 +708,12 @@ export abstract class SharedSegmentSequence<T extends ISegment>
683
708
  local: boolean,
684
709
  localOpMetadata: unknown,
685
710
  ) {
711
+ if (local) {
712
+ const recordedRefSeq = this.inFlightRefSeqs.shift();
713
+ assert(recordedRefSeq !== undefined, "No pending recorded refSeq found");
714
+ assert(recordedRefSeq <= message.referenceSequenceNumber, "RefSeq mismatch");
715
+ }
716
+
686
717
  // if loading isn't complete, we need to cache all
687
718
  // incoming ops to be applied after loading is complete
688
719
  if (this.deferIncomingOps) {
@@ -730,6 +761,7 @@ export abstract class SharedSegmentSequence<T extends ISegment>
730
761
  * {@inheritDoc @fluidframework/shared-object-base#SharedObjectCore.applyStashedOp}
731
762
  */
732
763
  protected applyStashedOp(content: any): unknown {
764
+ this.inFlightRefSeqs.push(this.runtime.deltaManager.lastSequenceNumber);
733
765
  const parsedContent = parseHandles(content, this.serializer);
734
766
  const metadata =
735
767
  this.intervalCollections.tryGetStashedOpLocalMetadata(parsedContent) ??
File without changes