@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.
- package/CHANGELOG.md +52 -0
- package/README.md +4 -3
- package/dist/defaultMap.d.ts +1 -1
- package/dist/defaultMap.d.ts.map +1 -1
- package/dist/defaultMap.js +6 -5
- package/dist/defaultMap.js.map +1 -1
- package/dist/defaultMapInterfaces.d.ts +1 -1
- package/dist/defaultMapInterfaces.d.ts.map +1 -1
- package/dist/defaultMapInterfaces.js.map +1 -1
- package/dist/intervalCollection.d.ts +2 -42
- package/dist/intervalCollection.d.ts.map +1 -1
- package/dist/intervalCollection.js +16 -34
- package/dist/intervalCollection.js.map +1 -1
- package/dist/intervalIndex/idIntervalIndex.js +3 -3
- package/dist/intervalIndex/idIntervalIndex.js.map +1 -1
- package/dist/intervalIndex/startpointInRangeIndex.js +2 -2
- package/dist/intervalIndex/startpointInRangeIndex.js.map +1 -1
- package/dist/intervals/interval.js +2 -2
- package/dist/intervals/interval.js.map +1 -1
- package/dist/intervals/intervalUtils.d.ts +6 -0
- package/dist/intervals/intervalUtils.d.ts.map +1 -1
- package/dist/intervals/intervalUtils.js.map +1 -1
- package/dist/intervals/sequenceInterval.js +6 -6
- package/dist/intervals/sequenceInterval.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/revertibles.js +4 -4
- package/dist/revertibles.js.map +1 -1
- package/dist/sequence.d.ts +23 -2
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +61 -14
- package/dist/sequence.js.map +1 -1
- package/dist/sequenceDeltaEvent.js +2 -2
- package/dist/sequenceDeltaEvent.js.map +1 -1
- package/dist/sharedIntervalCollection.js +2 -2
- package/dist/sharedIntervalCollection.js.map +1 -1
- package/dist/sharedSequence.d.ts +9 -0
- package/dist/sharedSequence.d.ts.map +1 -1
- package/dist/sharedSequence.js +8 -2
- package/dist/sharedSequence.js.map +1 -1
- package/dist/sharedString.d.ts.map +1 -1
- package/dist/sharedString.js +6 -6
- package/dist/sharedString.js.map +1 -1
- package/lib/defaultMap.d.ts +1 -1
- package/lib/defaultMap.d.ts.map +1 -1
- package/lib/defaultMap.js +2 -1
- package/lib/defaultMap.js.map +1 -1
- package/lib/defaultMapInterfaces.d.ts +1 -1
- package/lib/defaultMapInterfaces.d.ts.map +1 -1
- package/lib/defaultMapInterfaces.js.map +1 -1
- package/lib/intervalCollection.d.ts +2 -42
- package/lib/intervalCollection.d.ts.map +1 -1
- package/lib/intervalCollection.js +3 -21
- package/lib/intervalCollection.js.map +1 -1
- package/lib/intervalIndex/idIntervalIndex.js +1 -1
- package/lib/intervalIndex/idIntervalIndex.js.map +1 -1
- package/lib/intervalIndex/startpointInRangeIndex.js +1 -1
- package/lib/intervalIndex/startpointInRangeIndex.js.map +1 -1
- package/lib/intervals/interval.js +1 -1
- package/lib/intervals/interval.js.map +1 -1
- package/lib/intervals/intervalUtils.d.ts +6 -0
- package/lib/intervals/intervalUtils.d.ts.map +1 -1
- package/lib/intervals/intervalUtils.js.map +1 -1
- package/lib/intervals/sequenceInterval.js +2 -2
- package/lib/intervals/sequenceInterval.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/revertibles.js +1 -1
- package/lib/revertibles.js.map +1 -1
- package/lib/sequence.d.ts +23 -2
- package/lib/sequence.d.ts.map +1 -1
- package/lib/sequence.js +56 -8
- package/lib/sequence.js.map +1 -1
- package/lib/sequenceDeltaEvent.js +1 -1
- package/lib/sequenceDeltaEvent.js.map +1 -1
- package/lib/sharedIntervalCollection.js +1 -1
- package/lib/sharedIntervalCollection.js.map +1 -1
- package/lib/sharedSequence.d.ts +9 -0
- package/lib/sharedSequence.d.ts.map +1 -1
- package/lib/sharedSequence.js +7 -1
- package/lib/sharedSequence.js.map +1 -1
- package/lib/sharedString.d.ts.map +1 -1
- package/lib/sharedString.js +6 -6
- package/lib/sharedString.js.map +1 -1
- package/package.json +25 -28
- package/src/defaultMap.ts +2 -1
- package/src/defaultMapInterfaces.ts +1 -1
- package/src/intervalCollection.ts +4 -44
- package/src/intervalIndex/idIntervalIndex.ts +1 -1
- package/src/intervalIndex/startpointInRangeIndex.ts +1 -1
- package/src/intervals/interval.ts +1 -1
- package/src/intervals/intervalUtils.ts +7 -0
- package/src/intervals/sequenceInterval.ts +2 -2
- package/src/packageVersion.ts +1 -1
- package/src/revertibles.ts +1 -1
- package/src/sequence.ts +80 -9
- package/src/sequenceDeltaEvent.ts +1 -1
- package/src/sharedIntervalCollection.ts +1 -1
- package/src/sharedSequence.ts +10 -1
- 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 {
|
|
6
|
-
import {
|
|
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/
|
|
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
|
+
});
|
package/src/sharedSequence.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { assert } from "@fluidframework/
|
|
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,
|
package/src/sharedString.ts
CHANGED
|
@@ -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.
|
|
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
|
/**
|