@fluidframework/sequence 2.0.0-internal.6.1.1 → 2.0.0-internal.6.3.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +4 -3
  3. package/dist/defaultMap.d.ts +1 -1
  4. package/dist/defaultMap.d.ts.map +1 -1
  5. package/dist/defaultMap.js +6 -5
  6. package/dist/defaultMap.js.map +1 -1
  7. package/dist/defaultMapInterfaces.d.ts +1 -1
  8. package/dist/defaultMapInterfaces.d.ts.map +1 -1
  9. package/dist/defaultMapInterfaces.js.map +1 -1
  10. package/dist/intervalCollection.d.ts +2 -42
  11. package/dist/intervalCollection.d.ts.map +1 -1
  12. package/dist/intervalCollection.js +16 -34
  13. package/dist/intervalCollection.js.map +1 -1
  14. package/dist/intervalIndex/idIntervalIndex.js +3 -3
  15. package/dist/intervalIndex/idIntervalIndex.js.map +1 -1
  16. package/dist/intervalIndex/startpointInRangeIndex.js +2 -2
  17. package/dist/intervalIndex/startpointInRangeIndex.js.map +1 -1
  18. package/dist/intervals/interval.js +2 -2
  19. package/dist/intervals/interval.js.map +1 -1
  20. package/dist/intervals/intervalUtils.d.ts +6 -0
  21. package/dist/intervals/intervalUtils.d.ts.map +1 -1
  22. package/dist/intervals/intervalUtils.js.map +1 -1
  23. package/dist/intervals/sequenceInterval.js +6 -6
  24. package/dist/intervals/sequenceInterval.js.map +1 -1
  25. package/dist/packageVersion.d.ts +1 -1
  26. package/dist/packageVersion.js +1 -1
  27. package/dist/packageVersion.js.map +1 -1
  28. package/dist/revertibles.js +4 -4
  29. package/dist/revertibles.js.map +1 -1
  30. package/dist/sequence.d.ts +23 -2
  31. package/dist/sequence.d.ts.map +1 -1
  32. package/dist/sequence.js +61 -14
  33. package/dist/sequence.js.map +1 -1
  34. package/dist/sequenceDeltaEvent.js +2 -2
  35. package/dist/sequenceDeltaEvent.js.map +1 -1
  36. package/dist/sharedIntervalCollection.js +2 -2
  37. package/dist/sharedIntervalCollection.js.map +1 -1
  38. package/dist/sharedSequence.d.ts +9 -0
  39. package/dist/sharedSequence.d.ts.map +1 -1
  40. package/dist/sharedSequence.js +8 -2
  41. package/dist/sharedSequence.js.map +1 -1
  42. package/dist/sharedString.d.ts.map +1 -1
  43. package/dist/sharedString.js +6 -6
  44. package/dist/sharedString.js.map +1 -1
  45. package/lib/defaultMap.d.ts +1 -1
  46. package/lib/defaultMap.d.ts.map +1 -1
  47. package/lib/defaultMap.js +2 -1
  48. package/lib/defaultMap.js.map +1 -1
  49. package/lib/defaultMapInterfaces.d.ts +1 -1
  50. package/lib/defaultMapInterfaces.d.ts.map +1 -1
  51. package/lib/defaultMapInterfaces.js.map +1 -1
  52. package/lib/intervalCollection.d.ts +2 -42
  53. package/lib/intervalCollection.d.ts.map +1 -1
  54. package/lib/intervalCollection.js +3 -21
  55. package/lib/intervalCollection.js.map +1 -1
  56. package/lib/intervalIndex/idIntervalIndex.js +1 -1
  57. package/lib/intervalIndex/idIntervalIndex.js.map +1 -1
  58. package/lib/intervalIndex/startpointInRangeIndex.js +1 -1
  59. package/lib/intervalIndex/startpointInRangeIndex.js.map +1 -1
  60. package/lib/intervals/interval.js +1 -1
  61. package/lib/intervals/interval.js.map +1 -1
  62. package/lib/intervals/intervalUtils.d.ts +6 -0
  63. package/lib/intervals/intervalUtils.d.ts.map +1 -1
  64. package/lib/intervals/intervalUtils.js.map +1 -1
  65. package/lib/intervals/sequenceInterval.js +2 -2
  66. package/lib/intervals/sequenceInterval.js.map +1 -1
  67. package/lib/packageVersion.d.ts +1 -1
  68. package/lib/packageVersion.js +1 -1
  69. package/lib/packageVersion.js.map +1 -1
  70. package/lib/revertibles.js +1 -1
  71. package/lib/revertibles.js.map +1 -1
  72. package/lib/sequence.d.ts +23 -2
  73. package/lib/sequence.d.ts.map +1 -1
  74. package/lib/sequence.js +56 -8
  75. package/lib/sequence.js.map +1 -1
  76. package/lib/sequenceDeltaEvent.js +1 -1
  77. package/lib/sequenceDeltaEvent.js.map +1 -1
  78. package/lib/sharedIntervalCollection.js +1 -1
  79. package/lib/sharedIntervalCollection.js.map +1 -1
  80. package/lib/sharedSequence.d.ts +9 -0
  81. package/lib/sharedSequence.d.ts.map +1 -1
  82. package/lib/sharedSequence.js +7 -1
  83. package/lib/sharedSequence.js.map +1 -1
  84. package/lib/sharedString.d.ts.map +1 -1
  85. package/lib/sharedString.js +6 -6
  86. package/lib/sharedString.js.map +1 -1
  87. package/package.json +25 -28
  88. package/src/defaultMap.ts +2 -1
  89. package/src/defaultMapInterfaces.ts +1 -1
  90. package/src/intervalCollection.ts +4 -44
  91. package/src/intervalIndex/idIntervalIndex.ts +1 -1
  92. package/src/intervalIndex/startpointInRangeIndex.ts +1 -1
  93. package/src/intervals/interval.ts +1 -1
  94. package/src/intervals/intervalUtils.ts +7 -0
  95. package/src/intervals/sequenceInterval.ts +2 -2
  96. package/src/packageVersion.ts +1 -1
  97. package/src/revertibles.ts +1 -1
  98. package/src/sequence.ts +80 -9
  99. package/src/sequenceDeltaEvent.ts +1 -1
  100. package/src/sharedIntervalCollection.ts +1 -1
  101. package/src/sharedSequence.ts +10 -1
  102. package/src/sharedString.ts +8 -6
package/src/sequence.ts CHANGED
@@ -2,8 +2,9 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- import { Deferred, bufferToString, assert } from "@fluidframework/common-utils";
6
- import { createChildLogger } from "@fluidframework/telemetry-utils";
5
+ import { assert, Deferred } from "@fluidframework/core-utils";
6
+ import { bufferToString } from "@fluid-internal/client-utils";
7
+ import { LoggingError, createChildLogger } from "@fluidframework/telemetry-utils";
7
8
  import { ISequencedDocumentMessage, MessageType } from "@fluidframework/protocol-definitions";
8
9
  import {
9
10
  IChannelAttributes,
@@ -13,6 +14,7 @@ import {
13
14
  import {
14
15
  Client,
15
16
  createAnnotateRangeOp,
17
+ // eslint-disable-next-line import/no-deprecated
16
18
  createGroupOp,
17
19
  createInsertOp,
18
20
  createRemoveRangeOp,
@@ -46,9 +48,8 @@ import {
46
48
  ISharedObjectEvents,
47
49
  SummarySerializer,
48
50
  } from "@fluidframework/shared-object-base";
49
- import { IEventThisPlaceHolder } from "@fluidframework/common-definitions";
51
+ import { IEventThisPlaceHolder } from "@fluidframework/core-interfaces";
50
52
  import { ISummaryTreeWithStats, ITelemetryContext } from "@fluidframework/runtime-definitions";
51
-
52
53
  import { DefaultMap, IMapOperation } from "./defaultMap";
53
54
  import { IMapMessageLocalMetadata, IValueChanged } from "./defaultMapInterfaces";
54
55
  import { SequenceInterval } from "./intervals";
@@ -119,6 +120,21 @@ export abstract class SharedSegmentSequence<T extends ISegment>
119
120
  return this.loadedDeferred.promise;
120
121
  }
121
122
 
123
+ /**
124
+ * This is a safeguard to avoid problematic reentrancy of local ops. This type of scenario occurs if the user of SharedString subscribes
125
+ * to the `sequenceDelta` event and uses the callback for a local op to submit further local ops.
126
+ * Historically (before 2.0.0-internal.6.1.0), doing so would result in eventual consistency issues or a corrupted document.
127
+ * These issues were fixed in #16815 which makes such reentrancy no different from applying the ops in order but not from within the change events,
128
+ * but there is still little test coverage for reentrant scenarios.
129
+ * Additionally, applications submitting ops from inside change events need to take extreme care that their data models also support reentrancy.
130
+ * Since this is likely not the case, by default SharedString throws when encountering reentrant ops.
131
+ *
132
+ * An application using SharedString which explicitly wants to opt in to allowing reentrancy anyway can set `sharedStringPreventReentrancy`
133
+ * on the data store options to `false`.
134
+ * @internal
135
+ */
136
+ protected guardReentrancy: <TRet>(callback: () => TRet) => TRet;
137
+
122
138
  private static createOpsFromDelta(event: SequenceDeltaEvent): IMergeTreeDeltaOp[] {
123
139
  const ops: IMergeTreeDeltaOp[] = [];
124
140
  for (const r of event.ranges) {
@@ -194,6 +210,19 @@ export abstract class SharedSegmentSequence<T extends ISegment>
194
210
  ) {
195
211
  super(id, dataStoreRuntime, attributes, "fluid_sequence_");
196
212
 
213
+ this.guardReentrancy =
214
+ dataStoreRuntime.options.sharedStringPreventReentrancy ?? true
215
+ ? ensureNoReentrancy
216
+ : createReentrancyDetector((depth) => {
217
+ if (totalReentrancyLogs > 0) {
218
+ totalReentrancyLogs--;
219
+ this.logger.sendTelemetryEvent(
220
+ { eventName: "LocalOpReentry", depth },
221
+ new LoggingError(reentrancyErrorMessage),
222
+ );
223
+ }
224
+ });
225
+
197
226
  this.loadedDeferred.promise.catch((error) => {
198
227
  this.logger.sendErrorEvent({ eventName: "SequenceLoadFailed" }, error);
199
228
  });
@@ -233,14 +262,14 @@ export abstract class SharedSegmentSequence<T extends ISegment>
233
262
  * @param end - The exclusive end of the range to remove
234
263
  */
235
264
  public removeRange(start: number, end: number): IMergeTreeRemoveMsg {
236
- return this.client.removeRangeLocal(start, end);
265
+ return this.guardReentrancy(() => this.client.removeRangeLocal(start, end));
237
266
  }
238
267
 
239
268
  /**
240
269
  * @deprecated - The ability to create group ops will be removed in an upcoming release, as group ops are redundant with the native batching capabilities of the runtime
241
270
  */
242
271
  public groupOperation(groupOp: IMergeTreeGroupMsg) {
243
- this.client.localTransaction(groupOp);
272
+ this.guardReentrancy(() => this.client.localTransaction(groupOp));
244
273
  }
245
274
 
246
275
  /**
@@ -286,7 +315,7 @@ export abstract class SharedSegmentSequence<T extends ISegment>
286
315
  props: PropertySet,
287
316
  combiningOp?: ICombiningOp,
288
317
  ) {
289
- this.client.annotateRangeLocal(start, end, props, combiningOp);
318
+ this.guardReentrancy(() => this.client.annotateRangeLocal(start, end, props, combiningOp));
290
319
  }
291
320
 
292
321
  public getPropertiesAtPosition(pos: number) {
@@ -415,6 +444,9 @@ export abstract class SharedSegmentSequence<T extends ISegment>
415
444
  this.client.walkSegments(handler, start, end, accum as TClientData, splitRange);
416
445
  }
417
446
 
447
+ /**
448
+ * @deprecated - this functionality is no longer supported and will be removed
449
+ */
418
450
  public getStackContext(startPos: number, rangeLabels: string[]): RangeStackMap {
419
451
  return this.client.getStackContext(startPos, rangeLabels);
420
452
  }
@@ -433,7 +465,7 @@ export abstract class SharedSegmentSequence<T extends ISegment>
433
465
  * @param segment - The segment to insert
434
466
  */
435
467
  public insertAtReferencePosition(pos: ReferencePosition, segment: T) {
436
- this.client.insertAtReferencePositionLocal(pos, segment);
468
+ this.guardReentrancy(() => this.client.insertAtReferencePositionLocal(pos, segment));
437
469
  }
438
470
  /**
439
471
  * Inserts a segment
@@ -442,7 +474,7 @@ export abstract class SharedSegmentSequence<T extends ISegment>
442
474
  */
443
475
  public insertFromSpec(pos: number, spec: IJSONSegment) {
444
476
  const segment = this.segmentFromSpec(spec);
445
- this.client.insertSegmentLocal(pos, segment);
477
+ this.guardReentrancy(() => this.client.insertSegmentLocal(pos, segment));
446
478
  }
447
479
 
448
480
  /**
@@ -722,6 +754,7 @@ export abstract class SharedSegmentSequence<T extends ISegment>
722
754
  stashMessage = {
723
755
  ...message,
724
756
  referenceSequenceNumber: stashMessage.sequenceNumber - 1,
757
+ // eslint-disable-next-line import/no-deprecated
725
758
  contents: ops.length !== 1 ? createGroupOp(...ops) : ops[0],
726
759
  };
727
760
  }
@@ -803,3 +836,41 @@ export abstract class SharedSegmentSequence<T extends ISegment>
803
836
  }
804
837
  }
805
838
  }
839
+
840
+ function createReentrancyDetector(
841
+ onReentrancy: (depth: number) => void,
842
+ ): <T>(callback: () => T) => T {
843
+ let depth = 0;
844
+ function detectReentrancy<T>(callback: () => T): T {
845
+ if (depth > 0) {
846
+ onReentrancy(depth);
847
+ }
848
+ depth++;
849
+ try {
850
+ return callback();
851
+ } finally {
852
+ depth--;
853
+ }
854
+ }
855
+
856
+ return detectReentrancy;
857
+ }
858
+
859
+ /**
860
+ * Apps which generate reentrant behavior may do so at a high frequency.
861
+ * Logging even per-SharedSegmentSequence instance might be too noisy, and having a few logs from a session
862
+ * is likely enough.
863
+ */
864
+ let totalReentrancyLogs = 3;
865
+
866
+ /**
867
+ * Resets the reentrancy log counter. Test-only API.
868
+ */
869
+ export function resetReentrancyLogCounter() {
870
+ totalReentrancyLogs = 3;
871
+ }
872
+
873
+ const reentrancyErrorMessage = "Reentrancy detected in sequence local ops";
874
+ const ensureNoReentrancy = createReentrancyDetector(() => {
875
+ throw new LoggingError(reentrancyErrorMessage);
876
+ });
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { assert } from "@fluidframework/common-utils";
6
+ import { assert } from "@fluidframework/core-utils";
7
7
  import {
8
8
  Client,
9
9
  IMergeTreeDeltaCallbackArgs,
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { bufferToString } from "@fluidframework/common-utils";
6
+ import { bufferToString } from "@fluid-internal/client-utils";
7
7
  import {
8
8
  IChannelAttributes,
9
9
  IFluidDataStoreRuntime,
@@ -3,7 +3,7 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { assert } from "@fluidframework/common-utils";
6
+ import { assert } from "@fluidframework/core-utils";
7
7
  import { BaseSegment, IJSONSegment, ISegment, PropertySet } from "@fluidframework/merge-tree";
8
8
  import {
9
9
  IChannelAttributes,
@@ -14,10 +14,16 @@ import { SharedSegmentSequence } from "./sequence";
14
14
 
15
15
  const MaxRun = 128;
16
16
 
17
+ /**
18
+ * @deprecated - IJSONRunSegment will be removed in a upcoming release. It has been moved to the fluid-experimental/sequence-deprecated package
19
+ */
17
20
  export interface IJSONRunSegment<T> extends IJSONSegment {
18
21
  items: Serializable<T>[];
19
22
  }
20
23
 
24
+ /**
25
+ * @deprecated - SubSequence will be removed in a upcoming release. It has been moved to the fluid-experimental/sequence-deprecated package
26
+ */
21
27
  export class SubSequence<T> extends BaseSegment {
22
28
  public static readonly typeString: string = "SubSequence";
23
29
  public static is(segment: ISegment): segment is SubSequence<any> {
@@ -98,6 +104,9 @@ export class SubSequence<T> extends BaseSegment {
98
104
  }
99
105
  }
100
106
 
107
+ /**
108
+ * @deprecated - SharedSequence will be removed in a upcoming release. It has been moved to the fluid-experimental/sequence-deprecated package
109
+ */
101
110
  export class SharedSequence<T> extends SharedSegmentSequence<SubSequence<T>> {
102
111
  constructor(
103
112
  document: IFluidDataStoreRuntime,
@@ -118,7 +118,7 @@ export class SharedString
118
118
  }
119
119
 
120
120
  const pos = this.posFromRelativePos(relativePos1);
121
- this.client.insertSegmentLocal(pos, segment);
121
+ this.guardReentrancy(() => this.client.insertSegmentLocal(pos, segment));
122
122
  }
123
123
 
124
124
  /**
@@ -134,7 +134,7 @@ export class SharedString
134
134
  segment.addProperties(props);
135
135
  }
136
136
 
137
- return this.client.insertSegmentLocal(pos, segment);
137
+ return this.guardReentrancy(() => this.client.insertSegmentLocal(pos, segment));
138
138
  }
139
139
 
140
140
  /**
@@ -150,7 +150,7 @@ export class SharedString
150
150
  }
151
151
 
152
152
  const pos = this.posFromRelativePos(relativePos1);
153
- this.client.insertSegmentLocal(pos, segment);
153
+ this.guardReentrancy(() => this.client.insertSegmentLocal(pos, segment));
154
154
  }
155
155
 
156
156
  /**
@@ -162,7 +162,7 @@ export class SharedString
162
162
  segment.addProperties(props);
163
163
  }
164
164
 
165
- this.client.insertSegmentLocal(pos, segment);
165
+ this.guardReentrancy(() => this.client.insertSegmentLocal(pos, segment));
166
166
  }
167
167
 
168
168
  /**
@@ -197,7 +197,9 @@ export class SharedString
197
197
  props: PropertySet,
198
198
  callback: (m: Marker) => void,
199
199
  ) {
200
- this.client.annotateMarkerNotifyConsensus(marker, props, callback);
200
+ this.guardReentrancy(() =>
201
+ this.client.annotateMarkerNotifyConsensus(marker, props, callback),
202
+ );
201
203
  }
202
204
 
203
205
  /**
@@ -207,7 +209,7 @@ export class SharedString
207
209
  * @param combiningOp - Optional. Specifies how to combine values for the property, such as "incr" for increment.
208
210
  */
209
211
  public annotateMarker(marker: Marker, props: PropertySet, combiningOp?: ICombiningOp) {
210
- this.client.annotateMarker(marker, props, combiningOp);
212
+ this.guardReentrancy(() => this.client.annotateMarker(marker, props, combiningOp));
211
213
  }
212
214
 
213
215
  /**