@fluidframework/id-compressor 2.0.0-rc.3.0.3 → 2.0.0-rc.3.0.4
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/api-report/id-compressor.api.md +2 -1
- package/dist/idCompressor.d.ts +3 -1
- package/dist/idCompressor.d.ts.map +1 -1
- package/dist/idCompressor.js +42 -5
- package/dist/idCompressor.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/test/idCompressor.spec.js.map +1 -1
- package/dist/test/idCompressorTestUtilities.d.ts.map +1 -1
- package/dist/test/idCompressorTestUtilities.js +81 -18
- package/dist/test/idCompressorTestUtilities.js.map +1 -1
- package/dist/types/idCompressor.d.ts +11 -1
- package/dist/types/idCompressor.d.ts.map +1 -1
- package/dist/types/idCompressor.js.map +1 -1
- package/lib/idCompressor.d.ts +3 -1
- package/lib/idCompressor.d.ts.map +1 -1
- package/lib/idCompressor.js +43 -6
- package/lib/idCompressor.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/test/idCompressor.spec.js.map +1 -1
- package/lib/test/idCompressorTestUtilities.d.ts.map +1 -1
- package/lib/test/idCompressorTestUtilities.js +81 -18
- package/lib/test/idCompressorTestUtilities.js.map +1 -1
- package/lib/types/idCompressor.d.ts +11 -1
- package/lib/types/idCompressor.d.ts.map +1 -1
- package/lib/types/idCompressor.js.map +1 -1
- package/package.json +7 -7
- package/src/idCompressor.ts +57 -8
- package/src/packageVersion.ts +1 -1
- package/src/test/idCompressorTestUtilities.ts +94 -19
- package/src/types/idCompressor.ts +12 -1
package/src/idCompressor.ts
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
import { bufferToString, stringToBuffer } from "@fluid-internal/client-utils";
|
|
7
7
|
import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
|
|
8
8
|
import { assert } from "@fluidframework/core-utils/internal";
|
|
9
|
-
import {
|
|
10
|
-
|
|
9
|
+
import {
|
|
10
|
+
ITelemetryLoggerExt,
|
|
11
|
+
LoggingError,
|
|
12
|
+
createChildLogger,
|
|
13
|
+
} from "@fluidframework/telemetry-utils/internal";
|
|
11
14
|
|
|
12
15
|
import { FinalSpace } from "./finalSpace.js";
|
|
13
16
|
import { FinalCompressedId, LocalCompressedId, NumericUuid, isFinalId } from "./identifiers.js";
|
|
@@ -58,6 +61,13 @@ import {
|
|
|
58
61
|
*/
|
|
59
62
|
const currentWrittenVersion = 2.0;
|
|
60
63
|
|
|
64
|
+
function rangeFinalizationError(expectedStart: number, actualStart: number): LoggingError {
|
|
65
|
+
return new LoggingError("Ranges finalized out of order", {
|
|
66
|
+
expectedStart,
|
|
67
|
+
actualStart,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
61
71
|
/**
|
|
62
72
|
* See {@link IIdCompressor} and {@link IIdCompressorCore}
|
|
63
73
|
*/
|
|
@@ -223,14 +233,49 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
223
233
|
),
|
|
224
234
|
},
|
|
225
235
|
};
|
|
236
|
+
return this.updateToRange(range);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public takeUnfinalizedCreationRange(): IdCreationRange {
|
|
240
|
+
const lastLocalCluster = this.localSession.getLastCluster();
|
|
241
|
+
let count: number;
|
|
242
|
+
let firstGenCount: number;
|
|
243
|
+
if (lastLocalCluster === undefined) {
|
|
244
|
+
firstGenCount = 1;
|
|
245
|
+
count = this.localGenCount;
|
|
246
|
+
} else {
|
|
247
|
+
firstGenCount = genCountFromLocalId(
|
|
248
|
+
(lastLocalCluster.baseLocalId - lastLocalCluster.count) as LocalCompressedId,
|
|
249
|
+
);
|
|
250
|
+
count = this.localGenCount - firstGenCount + 1;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (count === 0) {
|
|
254
|
+
return {
|
|
255
|
+
sessionId: this.localSessionId,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const range: IdCreationRange = {
|
|
260
|
+
ids: {
|
|
261
|
+
count,
|
|
262
|
+
firstGenCount,
|
|
263
|
+
localIdRanges: this.normalizer.getRangesBetween(firstGenCount, this.localGenCount),
|
|
264
|
+
requestedClusterSize: this.nextRequestedClusterSize,
|
|
265
|
+
},
|
|
266
|
+
sessionId: this.localSessionId,
|
|
267
|
+
};
|
|
268
|
+
return this.updateToRange(range);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private updateToRange(range: IdCreationRange): IdCreationRange {
|
|
226
272
|
this.nextRangeBaseGenCount = this.localGenCount + 1;
|
|
227
|
-
IdCompressor.assertValidRange(range);
|
|
228
|
-
return range;
|
|
273
|
+
return IdCompressor.assertValidRange(range);
|
|
229
274
|
}
|
|
230
275
|
|
|
231
|
-
private static assertValidRange(range: IdCreationRange):
|
|
276
|
+
private static assertValidRange(range: IdCreationRange): IdCreationRange {
|
|
232
277
|
if (range.ids === undefined) {
|
|
233
|
-
return;
|
|
278
|
+
return range;
|
|
234
279
|
}
|
|
235
280
|
const { count, requestedClusterSize } = range.ids;
|
|
236
281
|
assert(count > 0, 0x755 /* Malformed ID Range. */);
|
|
@@ -239,6 +284,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
239
284
|
requestedClusterSize <= IdCompressor.maxClusterSize,
|
|
240
285
|
0x877 /* Clusters must not exceed max cluster size. */,
|
|
241
286
|
);
|
|
287
|
+
return range;
|
|
242
288
|
}
|
|
243
289
|
|
|
244
290
|
public finalizeCreationRange(range: IdCreationRange): void {
|
|
@@ -261,7 +307,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
261
307
|
if (lastCluster === undefined) {
|
|
262
308
|
// This is the first cluster in the session space
|
|
263
309
|
if (rangeBaseLocal !== -1) {
|
|
264
|
-
throw
|
|
310
|
+
throw rangeFinalizationError(-1, rangeBaseLocal);
|
|
265
311
|
}
|
|
266
312
|
lastCluster = this.addEmptyCluster(session, requestedClusterSize + count);
|
|
267
313
|
if (isLocal) {
|
|
@@ -274,7 +320,10 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore {
|
|
|
274
320
|
|
|
275
321
|
const remainingCapacity = lastCluster.capacity - lastCluster.count;
|
|
276
322
|
if (lastCluster.baseLocalId - lastCluster.count !== rangeBaseLocal) {
|
|
277
|
-
throw
|
|
323
|
+
throw rangeFinalizationError(
|
|
324
|
+
lastCluster.baseLocalId - lastCluster.count,
|
|
325
|
+
rangeBaseLocal,
|
|
326
|
+
);
|
|
278
327
|
}
|
|
279
328
|
|
|
280
329
|
if (remainingCapacity >= count) {
|
package/src/packageVersion.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from "../index.js";
|
|
34
34
|
import { assertIsSessionId, createSessionId, localIdFromGenCount } from "../utilities.js";
|
|
35
35
|
|
|
36
|
+
import { SessionSpaceNormalizer } from "../sessionSpaceNormalizer.js";
|
|
36
37
|
import {
|
|
37
38
|
FinalCompressedId,
|
|
38
39
|
ReadonlyIdCompressor,
|
|
@@ -286,8 +287,7 @@ export class IdCompressorTestNetwork {
|
|
|
286
287
|
* Changes the capacity request amount for a client. It will take effect immediately.
|
|
287
288
|
*/
|
|
288
289
|
public changeCapacity(client: Client, newClusterCapacity: number): void {
|
|
289
|
-
|
|
290
|
-
this.compressors.get(client)["nextRequestedClusterSize"] = newClusterCapacity;
|
|
290
|
+
changeCapacity(this.compressors.get(client), newClusterCapacity);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
private addNewId(
|
|
@@ -427,26 +427,89 @@ export class IdCompressorTestNetwork {
|
|
|
427
427
|
(client) => [this.compressors.get(client), this.getSequencedIdLog(client)] as const,
|
|
428
428
|
);
|
|
429
429
|
|
|
430
|
-
|
|
431
|
-
|
|
430
|
+
const getLocalIdsInRange = (
|
|
431
|
+
range: IdCreationRange,
|
|
432
|
+
opSpaceIds?: OpSpaceCompressedId[],
|
|
433
|
+
): Set<SessionSpaceCompressedId> => {
|
|
432
434
|
const localIdsInCreationRange = new Set<SessionSpaceCompressedId>();
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
for (
|
|
438
|
-
|
|
439
|
-
|
|
435
|
+
const ids = range.ids;
|
|
436
|
+
if (ids !== undefined) {
|
|
437
|
+
const { firstGenCount, localIdRanges } = ids;
|
|
438
|
+
for (const [genCount, count] of localIdRanges) {
|
|
439
|
+
for (let g = genCount; g < genCount + count; g++) {
|
|
440
|
+
const local = localIdFromGenCount(g);
|
|
441
|
+
if (opSpaceIds) {
|
|
440
442
|
assert.strictEqual(opSpaceIds[g - firstGenCount], local);
|
|
441
|
-
localIdsInCreationRange.add(local);
|
|
442
443
|
}
|
|
444
|
+
localIdsInCreationRange.add(local);
|
|
443
445
|
}
|
|
444
446
|
}
|
|
447
|
+
}
|
|
448
|
+
return localIdsInCreationRange;
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// Ensure creation ranges for clients we track contain the correct local ID ranges
|
|
452
|
+
this.serverOperations.forEach(([range, opSpaceIds, clientFrom]) => {
|
|
453
|
+
if (clientFrom !== OriginatingClient.Remote) {
|
|
454
|
+
const localIdsInCreationRange = getLocalIdsInRange(range, opSpaceIds);
|
|
455
|
+
let localCount = 0;
|
|
445
456
|
opSpaceIds.forEach((id) => {
|
|
446
457
|
if (isLocalId(id)) {
|
|
458
|
+
localCount++;
|
|
447
459
|
assert(localIdsInCreationRange.has(id), "Local ID not in creation range");
|
|
448
460
|
}
|
|
449
461
|
});
|
|
462
|
+
assert.strictEqual(
|
|
463
|
+
localCount,
|
|
464
|
+
localIdsInCreationRange.size,
|
|
465
|
+
"Local ID count mismatch",
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const undeliveredRanges = new Map<Client, IdCreationRange[]>();
|
|
471
|
+
this.clientProgress.forEach((progress, client) => {
|
|
472
|
+
const ranges = this.serverOperations
|
|
473
|
+
.slice(progress)
|
|
474
|
+
.filter((op) => op[2] === client)
|
|
475
|
+
.map(([range]) => range);
|
|
476
|
+
undeliveredRanges.set(client, ranges);
|
|
477
|
+
});
|
|
478
|
+
undeliveredRanges.forEach((ranges, client) => {
|
|
479
|
+
const compressor = this.compressors.get(client);
|
|
480
|
+
let firstGenCount: number | undefined;
|
|
481
|
+
let totalCount = 0;
|
|
482
|
+
const unionedLocalRanges = new SessionSpaceNormalizer();
|
|
483
|
+
ranges.forEach((range) => {
|
|
484
|
+
assert(range.sessionId === compressor.localSessionId);
|
|
485
|
+
if (range.ids !== undefined) {
|
|
486
|
+
// initialize firstGenCount if not set
|
|
487
|
+
if (firstGenCount === undefined) {
|
|
488
|
+
firstGenCount = range.ids.firstGenCount;
|
|
489
|
+
}
|
|
490
|
+
totalCount += range.ids.count;
|
|
491
|
+
range.ids.localIdRanges.forEach(([genCount, count]) => {
|
|
492
|
+
unionedLocalRanges.addLocalRange(genCount, count);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const retakenRange = compressor.takeUnfinalizedCreationRange();
|
|
498
|
+
if (retakenRange.ids !== undefined) {
|
|
499
|
+
const retakenLocalIds = new SessionSpaceNormalizer();
|
|
500
|
+
retakenRange.ids.localIdRanges.forEach(([genCount, count]) => {
|
|
501
|
+
retakenLocalIds.addLocalRange(genCount, count);
|
|
502
|
+
});
|
|
503
|
+
assert.strictEqual(
|
|
504
|
+
retakenLocalIds.equals(unionedLocalRanges),
|
|
505
|
+
true,
|
|
506
|
+
"Local ID ranges mismatch",
|
|
507
|
+
);
|
|
508
|
+
assert.strictEqual(retakenRange.ids.count, totalCount, "Count mismatch");
|
|
509
|
+
assert.strictEqual(retakenRange.ids.firstGenCount, firstGenCount, "Count mismatch");
|
|
510
|
+
} else {
|
|
511
|
+
assert.strictEqual(totalCount, 0);
|
|
512
|
+
assert.strictEqual(unionedLocalRanges.idRanges.size, 0);
|
|
450
513
|
}
|
|
451
514
|
});
|
|
452
515
|
|
|
@@ -588,6 +651,11 @@ export class IdCompressorTestNetwork {
|
|
|
588
651
|
}
|
|
589
652
|
}
|
|
590
653
|
|
|
654
|
+
function changeCapacity(compressor: IdCompressor, newClusterCapacity: number): void {
|
|
655
|
+
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
656
|
+
compressor["nextRequestedClusterSize"] = newClusterCapacity;
|
|
657
|
+
}
|
|
658
|
+
|
|
591
659
|
/**
|
|
592
660
|
* Roundtrips the supplied compressor through serialization and deserialization.
|
|
593
661
|
*/
|
|
@@ -608,13 +676,21 @@ export function roundtrip(
|
|
|
608
676
|
compressor: ReadonlyIdCompressor,
|
|
609
677
|
withSession: boolean,
|
|
610
678
|
): [SerializedIdCompressorWithOngoingSession | SerializedIdCompressorWithNoSession, IdCompressor] {
|
|
679
|
+
// preserve the capacity request as this property is normally private and resets
|
|
680
|
+
// to a default on construction (deserialization)
|
|
681
|
+
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
682
|
+
const capacity = compressor["nextRequestedClusterSize"];
|
|
611
683
|
if (withSession) {
|
|
612
684
|
const serialized = compressor.serialize(withSession);
|
|
613
|
-
|
|
685
|
+
const roundtripped = IdCompressor.deserialize(serialized);
|
|
686
|
+
changeCapacity(roundtripped, capacity);
|
|
687
|
+
return [serialized, roundtripped];
|
|
688
|
+
} else {
|
|
689
|
+
const nonLocalSerialized = compressor.serialize(withSession);
|
|
690
|
+
const roundtripped = IdCompressor.deserialize(nonLocalSerialized, createSessionId());
|
|
691
|
+
changeCapacity(roundtripped, capacity);
|
|
692
|
+
return [nonLocalSerialized, roundtripped];
|
|
614
693
|
}
|
|
615
|
-
|
|
616
|
-
const nonLocalSerialized = compressor.serialize(withSession);
|
|
617
|
-
return [nonLocalSerialized, IdCompressor.deserialize(nonLocalSerialized, createSessionId())];
|
|
618
694
|
}
|
|
619
695
|
|
|
620
696
|
/**
|
|
@@ -811,13 +887,13 @@ export function makeOpGenerator(
|
|
|
811
887
|
return { type: "reconnect", client: random.pick(activeClients) };
|
|
812
888
|
}
|
|
813
889
|
|
|
814
|
-
const allocationWeight =
|
|
890
|
+
const allocationWeight = 20;
|
|
815
891
|
return interleave(
|
|
816
892
|
createWeightedGenerator<Operation, FuzzTestState>([
|
|
817
893
|
[changeCapacityGenerator, 1],
|
|
818
894
|
[allocateIdsGenerator, Math.round(allocationWeight * (1 - outsideAllocationFraction))],
|
|
819
895
|
[allocateOutsideIdsGenerator, Math.round(allocationWeight * outsideAllocationFraction)],
|
|
820
|
-
[deliverAllOperationsGenerator,
|
|
896
|
+
[deliverAllOperationsGenerator, 1],
|
|
821
897
|
[deliverSomeOperationsGenerator, 6],
|
|
822
898
|
[reconnectGenerator, 1],
|
|
823
899
|
]),
|
|
@@ -889,7 +965,6 @@ export function performFuzzActions(
|
|
|
889
965
|
return state;
|
|
890
966
|
},
|
|
891
967
|
validate: (state) => {
|
|
892
|
-
network.deliverOperations(DestinationClient.All);
|
|
893
968
|
validator?.(network);
|
|
894
969
|
return state;
|
|
895
970
|
},
|
|
@@ -80,12 +80,23 @@ import {
|
|
|
80
80
|
export interface IIdCompressorCore {
|
|
81
81
|
/**
|
|
82
82
|
* Returns a range of IDs created by this session in a format for sending to the server for finalizing.
|
|
83
|
-
* The range will include all IDs generated via calls to `generateCompressedId` since the last time
|
|
83
|
+
* The range will include all IDs generated via calls to `generateCompressedId` since the last time a
|
|
84
|
+
* range was taken (via this method or `takeUnfinalizedCreationRange`).
|
|
84
85
|
* @returns the range of IDs, which may be empty. This range must be sent to the server for ordering before
|
|
85
86
|
* it is finalized. Ranges must be sent to the server in the order that they are taken via calls to this method.
|
|
86
87
|
*/
|
|
87
88
|
takeNextCreationRange(): IdCreationRange;
|
|
88
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Returns a range of IDs created by this session in a format for sending to the server for finalizing.
|
|
92
|
+
* The range will include all unfinalized IDs generated via calls to `generateCompressedId`.
|
|
93
|
+
* @returns the range of IDs, which may be empty. This range must be sent to the server for ordering before
|
|
94
|
+
* it is finalized. Ranges must be sent to the server in the order that they are taken via calls to this method.
|
|
95
|
+
* Note: after finalizing the range returned by this method, finalizing any ranges that had been previously taken
|
|
96
|
+
* will result in an error.
|
|
97
|
+
*/
|
|
98
|
+
takeUnfinalizedCreationRange(): IdCreationRange;
|
|
99
|
+
|
|
89
100
|
/**
|
|
90
101
|
* Finalizes the supplied range of IDs (which may be from either a remote or local session).
|
|
91
102
|
* @param range - the range of session-local IDs to finalize.
|