@fluidframework/id-compressor
Advanced tools
Comparing version 2.0.0-rc.3.0.3 to 2.0.0-rc.3.0.4
@@ -8,3 +8,3 @@ ## API Report File for "@fluidframework/id-compressor" | ||
import { ITelemetryBaseLogger } from '@fluidframework/core-interfaces'; | ||
import { ITelemetryLoggerExt } from '@fluidframework/telemetry-utils'; | ||
import { ITelemetryLoggerExt } from '@fluidframework/telemetry-utils/internal'; | ||
@@ -64,2 +64,3 @@ // @internal | ||
takeNextCreationRange(): IdCreationRange; | ||
takeUnfinalizedCreationRange(): IdCreationRange; | ||
} | ||
@@ -66,0 +67,0 @@ |
@@ -6,3 +6,3 @@ /*! | ||
import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces"; | ||
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils"; | ||
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal"; | ||
import { Index } from "./persistanceUtilities.js"; | ||
@@ -51,2 +51,4 @@ import { Sessions } from "./sessions.js"; | ||
takeNextCreationRange(): IdCreationRange; | ||
takeUnfinalizedCreationRange(): IdCreationRange; | ||
private updateToRange; | ||
private static assertValidRange; | ||
@@ -53,0 +55,0 @@ finalizeCreationRange(range: IdCreationRange): void; |
@@ -22,2 +22,8 @@ "use strict"; | ||
const currentWrittenVersion = 2.0; | ||
function rangeFinalizationError(expectedStart, actualStart) { | ||
return new internal_2.LoggingError("Ranges finalized out of order", { | ||
expectedStart, | ||
actualStart, | ||
}); | ||
} | ||
/** | ||
@@ -151,9 +157,39 @@ * See {@link IIdCompressor} and {@link IIdCompressorCore} | ||
}; | ||
return this.updateToRange(range); | ||
} | ||
takeUnfinalizedCreationRange() { | ||
const lastLocalCluster = this.localSession.getLastCluster(); | ||
let count; | ||
let firstGenCount; | ||
if (lastLocalCluster === undefined) { | ||
firstGenCount = 1; | ||
count = this.localGenCount; | ||
} | ||
else { | ||
firstGenCount = (0, utilities_js_1.genCountFromLocalId)((lastLocalCluster.baseLocalId - lastLocalCluster.count)); | ||
count = this.localGenCount - firstGenCount + 1; | ||
} | ||
if (count === 0) { | ||
return { | ||
sessionId: this.localSessionId, | ||
}; | ||
} | ||
const range = { | ||
ids: { | ||
count, | ||
firstGenCount, | ||
localIdRanges: this.normalizer.getRangesBetween(firstGenCount, this.localGenCount), | ||
requestedClusterSize: this.nextRequestedClusterSize, | ||
}, | ||
sessionId: this.localSessionId, | ||
}; | ||
return this.updateToRange(range); | ||
} | ||
updateToRange(range) { | ||
this.nextRangeBaseGenCount = this.localGenCount + 1; | ||
IdCompressor.assertValidRange(range); | ||
return range; | ||
return IdCompressor.assertValidRange(range); | ||
} | ||
static assertValidRange(range) { | ||
if (range.ids === undefined) { | ||
return; | ||
return range; | ||
} | ||
@@ -164,2 +200,3 @@ const { count, requestedClusterSize } = range.ids; | ||
(0, internal_1.assert)(requestedClusterSize <= IdCompressor.maxClusterSize, 0x877 /* Clusters must not exceed max cluster size. */); | ||
return range; | ||
} | ||
@@ -182,3 +219,3 @@ finalizeCreationRange(range) { | ||
if (rangeBaseLocal !== -1) { | ||
throw new Error("Ranges finalized out of order."); | ||
throw rangeFinalizationError(-1, rangeBaseLocal); | ||
} | ||
@@ -195,3 +232,3 @@ lastCluster = this.addEmptyCluster(session, requestedClusterSize + count); | ||
if (lastCluster.baseLocalId - lastCluster.count !== rangeBaseLocal) { | ||
throw new Error("Ranges finalized out of order."); | ||
throw rangeFinalizationError(lastCluster.baseLocalId - lastCluster.count, rangeBaseLocal); | ||
} | ||
@@ -198,0 +235,0 @@ if (remainingCapacity >= count) { |
@@ -8,3 +8,3 @@ /*! | ||
export declare const pkgName = "@fluidframework/id-compressor"; | ||
export declare const pkgVersion = "2.0.0-rc.3.0.3"; | ||
export declare const pkgVersion = "2.0.0-rc.3.0.4"; | ||
//# sourceMappingURL=packageVersion.d.ts.map |
@@ -11,3 +11,3 @@ "use strict"; | ||
exports.pkgName = "@fluidframework/id-compressor"; | ||
exports.pkgVersion = "2.0.0-rc.3.0.3"; | ||
exports.pkgVersion = "2.0.0-rc.3.0.4"; | ||
//# sourceMappingURL=packageVersion.js.map |
@@ -13,2 +13,3 @@ "use strict"; | ||
const utilities_js_1 = require("../utilities.js"); | ||
const sessionSpaceNormalizer_js_1 = require("../sessionSpaceNormalizer.js"); | ||
const testCommon_js_1 = require("./testCommon.js"); | ||
@@ -185,4 +186,3 @@ /** Identifies a compressor in a network */ | ||
changeCapacity(client, newClusterCapacity) { | ||
// eslint-disable-next-line @typescript-eslint/dot-notation | ||
this.compressors.get(client)["nextRequestedClusterSize"] = newClusterCapacity; | ||
changeCapacity(this.compressors.get(client), newClusterCapacity); | ||
} | ||
@@ -290,24 +290,74 @@ addNewId(client, id, originatingClient, sessionIdFrom, isSequenced) { | ||
const sequencedLogs = Object.values(Client).map((client) => [this.compressors.get(client), this.getSequencedIdLog(client)]); | ||
// Ensure creation ranges for clients we track contain the correct local ID ranges | ||
this.serverOperations.forEach(([range, opSpaceIds, clientFrom]) => { | ||
const getLocalIdsInRange = (range, opSpaceIds) => { | ||
const localIdsInCreationRange = new Set(); | ||
if (clientFrom !== exports.OriginatingClient.Remote) { | ||
const ids = range.ids; | ||
if (ids !== undefined) { | ||
const { firstGenCount, localIdRanges } = ids; | ||
for (const [genCount, count] of localIdRanges) { | ||
for (let g = genCount; g < genCount + count; g++) { | ||
const local = (0, utilities_js_1.localIdFromGenCount)(g); | ||
const ids = range.ids; | ||
if (ids !== undefined) { | ||
const { firstGenCount, localIdRanges } = ids; | ||
for (const [genCount, count] of localIdRanges) { | ||
for (let g = genCount; g < genCount + count; g++) { | ||
const local = (0, utilities_js_1.localIdFromGenCount)(g); | ||
if (opSpaceIds) { | ||
assert_1.strict.strictEqual(opSpaceIds[g - firstGenCount], local); | ||
localIdsInCreationRange.add(local); | ||
} | ||
localIdsInCreationRange.add(local); | ||
} | ||
} | ||
} | ||
return localIdsInCreationRange; | ||
}; | ||
// Ensure creation ranges for clients we track contain the correct local ID ranges | ||
this.serverOperations.forEach(([range, opSpaceIds, clientFrom]) => { | ||
if (clientFrom !== exports.OriginatingClient.Remote) { | ||
const localIdsInCreationRange = getLocalIdsInRange(range, opSpaceIds); | ||
let localCount = 0; | ||
opSpaceIds.forEach((id) => { | ||
if ((0, testCommon_js_1.isLocalId)(id)) { | ||
localCount++; | ||
(0, assert_1.strict)(localIdsInCreationRange.has(id), "Local ID not in creation range"); | ||
} | ||
}); | ||
assert_1.strict.strictEqual(localCount, localIdsInCreationRange.size, "Local ID count mismatch"); | ||
} | ||
}); | ||
const undeliveredRanges = new Map(); | ||
this.clientProgress.forEach((progress, client) => { | ||
const ranges = this.serverOperations | ||
.slice(progress) | ||
.filter((op) => op[2] === client) | ||
.map(([range]) => range); | ||
undeliveredRanges.set(client, ranges); | ||
}); | ||
undeliveredRanges.forEach((ranges, client) => { | ||
const compressor = this.compressors.get(client); | ||
let firstGenCount; | ||
let totalCount = 0; | ||
const unionedLocalRanges = new sessionSpaceNormalizer_js_1.SessionSpaceNormalizer(); | ||
ranges.forEach((range) => { | ||
(0, assert_1.strict)(range.sessionId === compressor.localSessionId); | ||
if (range.ids !== undefined) { | ||
// initialize firstGenCount if not set | ||
if (firstGenCount === undefined) { | ||
firstGenCount = range.ids.firstGenCount; | ||
} | ||
totalCount += range.ids.count; | ||
range.ids.localIdRanges.forEach(([genCount, count]) => { | ||
unionedLocalRanges.addLocalRange(genCount, count); | ||
}); | ||
} | ||
}); | ||
const retakenRange = compressor.takeUnfinalizedCreationRange(); | ||
if (retakenRange.ids !== undefined) { | ||
const retakenLocalIds = new sessionSpaceNormalizer_js_1.SessionSpaceNormalizer(); | ||
retakenRange.ids.localIdRanges.forEach(([genCount, count]) => { | ||
retakenLocalIds.addLocalRange(genCount, count); | ||
}); | ||
assert_1.strict.strictEqual(retakenLocalIds.equals(unionedLocalRanges), true, "Local ID ranges mismatch"); | ||
assert_1.strict.strictEqual(retakenRange.ids.count, totalCount, "Count mismatch"); | ||
assert_1.strict.strictEqual(retakenRange.ids.firstGenCount, firstGenCount, "Count mismatch"); | ||
} | ||
else { | ||
assert_1.strict.strictEqual(totalCount, 0); | ||
assert_1.strict.strictEqual(unionedLocalRanges.idRanges.size, 0); | ||
} | ||
}); | ||
// First, ensure all clients each generated a unique ID for each of their own calls to generate. | ||
@@ -417,9 +467,23 @@ for (const [compressor, ids] of sequencedLogs) { | ||
exports.IdCompressorTestNetwork = IdCompressorTestNetwork; | ||
function changeCapacity(compressor, newClusterCapacity) { | ||
// eslint-disable-next-line @typescript-eslint/dot-notation | ||
compressor["nextRequestedClusterSize"] = newClusterCapacity; | ||
} | ||
function roundtrip(compressor, withSession) { | ||
// preserve the capacity request as this property is normally private and resets | ||
// to a default on construction (deserialization) | ||
// eslint-disable-next-line @typescript-eslint/dot-notation | ||
const capacity = compressor["nextRequestedClusterSize"]; | ||
if (withSession) { | ||
const serialized = compressor.serialize(withSession); | ||
return [serialized, idCompressor_js_1.IdCompressor.deserialize(serialized)]; | ||
const roundtripped = idCompressor_js_1.IdCompressor.deserialize(serialized); | ||
changeCapacity(roundtripped, capacity); | ||
return [serialized, roundtripped]; | ||
} | ||
const nonLocalSerialized = compressor.serialize(withSession); | ||
return [nonLocalSerialized, idCompressor_js_1.IdCompressor.deserialize(nonLocalSerialized, (0, utilities_js_1.createSessionId)())]; | ||
else { | ||
const nonLocalSerialized = compressor.serialize(withSession); | ||
const roundtripped = idCompressor_js_1.IdCompressor.deserialize(nonLocalSerialized, (0, utilities_js_1.createSessionId)()); | ||
changeCapacity(roundtripped, capacity); | ||
return [nonLocalSerialized, roundtripped]; | ||
} | ||
} | ||
@@ -526,3 +590,3 @@ exports.roundtrip = roundtrip; | ||
} | ||
const allocationWeight = 16; | ||
const allocationWeight = 20; | ||
return (0, stochastic_test_utils_1.interleave)((0, stochastic_test_utils_1.createWeightedGenerator)([ | ||
@@ -532,3 +596,3 @@ [changeCapacityGenerator, 1], | ||
[allocateOutsideIdsGenerator, Math.round(allocationWeight * outsideAllocationFraction)], | ||
[deliverAllOperationsGenerator, 2], | ||
[deliverAllOperationsGenerator, 1], | ||
[deliverSomeOperationsGenerator, 6], | ||
@@ -586,3 +650,2 @@ [reconnectGenerator, 1], | ||
validate: (state) => { | ||
network.deliverOperations(exports.DestinationClient.All); | ||
validator?.(network); | ||
@@ -589,0 +652,0 @@ return state; |
@@ -72,3 +72,4 @@ /*! | ||
* Returns a range of IDs created by this session in a format for sending to the server for finalizing. | ||
* The range will include all IDs generated via calls to `generateCompressedId` since the last time this method was called. | ||
* The range will include all IDs generated via calls to `generateCompressedId` since the last time a | ||
* range was taken (via this method or `takeUnfinalizedCreationRange`). | ||
* @returns the range of IDs, which may be empty. This range must be sent to the server for ordering before | ||
@@ -79,2 +80,11 @@ * it is finalized. Ranges must be sent to the server in the order that they are taken via calls to this method. | ||
/** | ||
* Returns a range of IDs created by this session in a format for sending to the server for finalizing. | ||
* The range will include all unfinalized IDs generated via calls to `generateCompressedId`. | ||
* @returns the range of IDs, which may be empty. This range must be sent to the server for ordering before | ||
* it is finalized. Ranges must be sent to the server in the order that they are taken via calls to this method. | ||
* Note: after finalizing the range returned by this method, finalizing any ranges that had been previously taken | ||
* will result in an error. | ||
*/ | ||
takeUnfinalizedCreationRange(): IdCreationRange; | ||
/** | ||
* Finalizes the supplied range of IDs (which may be from either a remote or local session). | ||
@@ -81,0 +91,0 @@ * @param range - the range of session-local IDs to finalize. |
@@ -6,3 +6,3 @@ /*! | ||
import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces"; | ||
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils"; | ||
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils/internal"; | ||
import { Index } from "./persistanceUtilities.js"; | ||
@@ -51,2 +51,4 @@ import { Sessions } from "./sessions.js"; | ||
takeNextCreationRange(): IdCreationRange; | ||
takeUnfinalizedCreationRange(): IdCreationRange; | ||
private updateToRange; | ||
private static assertValidRange; | ||
@@ -53,0 +55,0 @@ finalizeCreationRange(range: IdCreationRange): void; |
@@ -7,3 +7,3 @@ /*! | ||
import { assert } from "@fluidframework/core-utils/internal"; | ||
import { createChildLogger } from "@fluidframework/telemetry-utils/internal"; | ||
import { LoggingError, createChildLogger, } from "@fluidframework/telemetry-utils/internal"; | ||
import { FinalSpace } from "./finalSpace.js"; | ||
@@ -20,2 +20,8 @@ import { isFinalId } from "./identifiers.js"; | ||
const currentWrittenVersion = 2.0; | ||
function rangeFinalizationError(expectedStart, actualStart) { | ||
return new LoggingError("Ranges finalized out of order", { | ||
expectedStart, | ||
actualStart, | ||
}); | ||
} | ||
/** | ||
@@ -149,9 +155,39 @@ * See {@link IIdCompressor} and {@link IIdCompressorCore} | ||
}; | ||
return this.updateToRange(range); | ||
} | ||
takeUnfinalizedCreationRange() { | ||
const lastLocalCluster = this.localSession.getLastCluster(); | ||
let count; | ||
let firstGenCount; | ||
if (lastLocalCluster === undefined) { | ||
firstGenCount = 1; | ||
count = this.localGenCount; | ||
} | ||
else { | ||
firstGenCount = genCountFromLocalId((lastLocalCluster.baseLocalId - lastLocalCluster.count)); | ||
count = this.localGenCount - firstGenCount + 1; | ||
} | ||
if (count === 0) { | ||
return { | ||
sessionId: this.localSessionId, | ||
}; | ||
} | ||
const range = { | ||
ids: { | ||
count, | ||
firstGenCount, | ||
localIdRanges: this.normalizer.getRangesBetween(firstGenCount, this.localGenCount), | ||
requestedClusterSize: this.nextRequestedClusterSize, | ||
}, | ||
sessionId: this.localSessionId, | ||
}; | ||
return this.updateToRange(range); | ||
} | ||
updateToRange(range) { | ||
this.nextRangeBaseGenCount = this.localGenCount + 1; | ||
IdCompressor.assertValidRange(range); | ||
return range; | ||
return IdCompressor.assertValidRange(range); | ||
} | ||
static assertValidRange(range) { | ||
if (range.ids === undefined) { | ||
return; | ||
return range; | ||
} | ||
@@ -162,2 +198,3 @@ const { count, requestedClusterSize } = range.ids; | ||
assert(requestedClusterSize <= IdCompressor.maxClusterSize, 0x877 /* Clusters must not exceed max cluster size. */); | ||
return range; | ||
} | ||
@@ -180,3 +217,3 @@ finalizeCreationRange(range) { | ||
if (rangeBaseLocal !== -1) { | ||
throw new Error("Ranges finalized out of order."); | ||
throw rangeFinalizationError(-1, rangeBaseLocal); | ||
} | ||
@@ -193,3 +230,3 @@ lastCluster = this.addEmptyCluster(session, requestedClusterSize + count); | ||
if (lastCluster.baseLocalId - lastCluster.count !== rangeBaseLocal) { | ||
throw new Error("Ranges finalized out of order."); | ||
throw rangeFinalizationError(lastCluster.baseLocalId - lastCluster.count, rangeBaseLocal); | ||
} | ||
@@ -196,0 +233,0 @@ if (remainingCapacity >= count) { |
@@ -8,3 +8,3 @@ /*! | ||
export declare const pkgName = "@fluidframework/id-compressor"; | ||
export declare const pkgVersion = "2.0.0-rc.3.0.3"; | ||
export declare const pkgVersion = "2.0.0-rc.3.0.4"; | ||
//# sourceMappingURL=packageVersion.d.ts.map |
@@ -8,3 +8,3 @@ /*! | ||
export const pkgName = "@fluidframework/id-compressor"; | ||
export const pkgVersion = "2.0.0-rc.3.0.3"; | ||
export const pkgVersion = "2.0.0-rc.3.0.4"; | ||
//# sourceMappingURL=packageVersion.js.map |
@@ -10,2 +10,3 @@ /*! | ||
import { assertIsSessionId, createSessionId, localIdFromGenCount } from "../utilities.js"; | ||
import { SessionSpaceNormalizer } from "../sessionSpaceNormalizer.js"; | ||
import { fail, getOrCreate, incrementStableId, isFinalId, isLocalId, } from "./testCommon.js"; | ||
@@ -180,4 +181,3 @@ /** Identifies a compressor in a network */ | ||
changeCapacity(client, newClusterCapacity) { | ||
// eslint-disable-next-line @typescript-eslint/dot-notation | ||
this.compressors.get(client)["nextRequestedClusterSize"] = newClusterCapacity; | ||
changeCapacity(this.compressors.get(client), newClusterCapacity); | ||
} | ||
@@ -285,24 +285,74 @@ addNewId(client, id, originatingClient, sessionIdFrom, isSequenced) { | ||
const sequencedLogs = Object.values(Client).map((client) => [this.compressors.get(client), this.getSequencedIdLog(client)]); | ||
// Ensure creation ranges for clients we track contain the correct local ID ranges | ||
this.serverOperations.forEach(([range, opSpaceIds, clientFrom]) => { | ||
const getLocalIdsInRange = (range, opSpaceIds) => { | ||
const localIdsInCreationRange = new Set(); | ||
if (clientFrom !== OriginatingClient.Remote) { | ||
const ids = range.ids; | ||
if (ids !== undefined) { | ||
const { firstGenCount, localIdRanges } = ids; | ||
for (const [genCount, count] of localIdRanges) { | ||
for (let g = genCount; g < genCount + count; g++) { | ||
const local = localIdFromGenCount(g); | ||
const ids = range.ids; | ||
if (ids !== undefined) { | ||
const { firstGenCount, localIdRanges } = ids; | ||
for (const [genCount, count] of localIdRanges) { | ||
for (let g = genCount; g < genCount + count; g++) { | ||
const local = localIdFromGenCount(g); | ||
if (opSpaceIds) { | ||
assert.strictEqual(opSpaceIds[g - firstGenCount], local); | ||
localIdsInCreationRange.add(local); | ||
} | ||
localIdsInCreationRange.add(local); | ||
} | ||
} | ||
} | ||
return localIdsInCreationRange; | ||
}; | ||
// Ensure creation ranges for clients we track contain the correct local ID ranges | ||
this.serverOperations.forEach(([range, opSpaceIds, clientFrom]) => { | ||
if (clientFrom !== OriginatingClient.Remote) { | ||
const localIdsInCreationRange = getLocalIdsInRange(range, opSpaceIds); | ||
let localCount = 0; | ||
opSpaceIds.forEach((id) => { | ||
if (isLocalId(id)) { | ||
localCount++; | ||
assert(localIdsInCreationRange.has(id), "Local ID not in creation range"); | ||
} | ||
}); | ||
assert.strictEqual(localCount, localIdsInCreationRange.size, "Local ID count mismatch"); | ||
} | ||
}); | ||
const undeliveredRanges = new Map(); | ||
this.clientProgress.forEach((progress, client) => { | ||
const ranges = this.serverOperations | ||
.slice(progress) | ||
.filter((op) => op[2] === client) | ||
.map(([range]) => range); | ||
undeliveredRanges.set(client, ranges); | ||
}); | ||
undeliveredRanges.forEach((ranges, client) => { | ||
const compressor = this.compressors.get(client); | ||
let firstGenCount; | ||
let totalCount = 0; | ||
const unionedLocalRanges = new SessionSpaceNormalizer(); | ||
ranges.forEach((range) => { | ||
assert(range.sessionId === compressor.localSessionId); | ||
if (range.ids !== undefined) { | ||
// initialize firstGenCount if not set | ||
if (firstGenCount === undefined) { | ||
firstGenCount = range.ids.firstGenCount; | ||
} | ||
totalCount += range.ids.count; | ||
range.ids.localIdRanges.forEach(([genCount, count]) => { | ||
unionedLocalRanges.addLocalRange(genCount, count); | ||
}); | ||
} | ||
}); | ||
const retakenRange = compressor.takeUnfinalizedCreationRange(); | ||
if (retakenRange.ids !== undefined) { | ||
const retakenLocalIds = new SessionSpaceNormalizer(); | ||
retakenRange.ids.localIdRanges.forEach(([genCount, count]) => { | ||
retakenLocalIds.addLocalRange(genCount, count); | ||
}); | ||
assert.strictEqual(retakenLocalIds.equals(unionedLocalRanges), true, "Local ID ranges mismatch"); | ||
assert.strictEqual(retakenRange.ids.count, totalCount, "Count mismatch"); | ||
assert.strictEqual(retakenRange.ids.firstGenCount, firstGenCount, "Count mismatch"); | ||
} | ||
else { | ||
assert.strictEqual(totalCount, 0); | ||
assert.strictEqual(unionedLocalRanges.idRanges.size, 0); | ||
} | ||
}); | ||
// First, ensure all clients each generated a unique ID for each of their own calls to generate. | ||
@@ -411,9 +461,23 @@ for (const [compressor, ids] of sequencedLogs) { | ||
} | ||
function changeCapacity(compressor, newClusterCapacity) { | ||
// eslint-disable-next-line @typescript-eslint/dot-notation | ||
compressor["nextRequestedClusterSize"] = newClusterCapacity; | ||
} | ||
export function roundtrip(compressor, withSession) { | ||
// preserve the capacity request as this property is normally private and resets | ||
// to a default on construction (deserialization) | ||
// eslint-disable-next-line @typescript-eslint/dot-notation | ||
const capacity = compressor["nextRequestedClusterSize"]; | ||
if (withSession) { | ||
const serialized = compressor.serialize(withSession); | ||
return [serialized, IdCompressor.deserialize(serialized)]; | ||
const roundtripped = IdCompressor.deserialize(serialized); | ||
changeCapacity(roundtripped, capacity); | ||
return [serialized, roundtripped]; | ||
} | ||
const nonLocalSerialized = compressor.serialize(withSession); | ||
return [nonLocalSerialized, IdCompressor.deserialize(nonLocalSerialized, createSessionId())]; | ||
else { | ||
const nonLocalSerialized = compressor.serialize(withSession); | ||
const roundtripped = IdCompressor.deserialize(nonLocalSerialized, createSessionId()); | ||
changeCapacity(roundtripped, capacity); | ||
return [nonLocalSerialized, roundtripped]; | ||
} | ||
} | ||
@@ -517,3 +581,3 @@ /** | ||
} | ||
const allocationWeight = 16; | ||
const allocationWeight = 20; | ||
return interleave(createWeightedGenerator([ | ||
@@ -523,3 +587,3 @@ [changeCapacityGenerator, 1], | ||
[allocateOutsideIdsGenerator, Math.round(allocationWeight * outsideAllocationFraction)], | ||
[deliverAllOperationsGenerator, 2], | ||
[deliverAllOperationsGenerator, 1], | ||
[deliverSomeOperationsGenerator, 6], | ||
@@ -576,3 +640,2 @@ [reconnectGenerator, 1], | ||
validate: (state) => { | ||
network.deliverOperations(DestinationClient.All); | ||
validator?.(network); | ||
@@ -579,0 +642,0 @@ return state; |
@@ -72,3 +72,4 @@ /*! | ||
* Returns a range of IDs created by this session in a format for sending to the server for finalizing. | ||
* The range will include all IDs generated via calls to `generateCompressedId` since the last time this method was called. | ||
* The range will include all IDs generated via calls to `generateCompressedId` since the last time a | ||
* range was taken (via this method or `takeUnfinalizedCreationRange`). | ||
* @returns the range of IDs, which may be empty. This range must be sent to the server for ordering before | ||
@@ -79,2 +80,11 @@ * it is finalized. Ranges must be sent to the server in the order that they are taken via calls to this method. | ||
/** | ||
* Returns a range of IDs created by this session in a format for sending to the server for finalizing. | ||
* The range will include all unfinalized IDs generated via calls to `generateCompressedId`. | ||
* @returns the range of IDs, which may be empty. This range must be sent to the server for ordering before | ||
* it is finalized. Ranges must be sent to the server in the order that they are taken via calls to this method. | ||
* Note: after finalizing the range returned by this method, finalizing any ranges that had been previously taken | ||
* will result in an error. | ||
*/ | ||
takeUnfinalizedCreationRange(): IdCreationRange; | ||
/** | ||
* Finalizes the supplied range of IDs (which may be from either a remote or local session). | ||
@@ -81,0 +91,0 @@ * @param range - the range of session-local IDs to finalize. |
{ | ||
"name": "@fluidframework/id-compressor", | ||
"version": "2.0.0-rc.3.0.3", | ||
"version": "2.0.0-rc.3.0.4", | ||
"description": "ID compressor", | ||
@@ -82,6 +82,6 @@ "homepage": "https://fluidframework.com", | ||
"dependencies": { | ||
"@fluid-internal/client-utils": ">=2.0.0-rc.3.0.3 <2.0.0-rc.3.1.0", | ||
"@fluidframework/core-interfaces": ">=2.0.0-rc.3.0.3 <2.0.0-rc.3.1.0", | ||
"@fluidframework/core-utils": ">=2.0.0-rc.3.0.3 <2.0.0-rc.3.1.0", | ||
"@fluidframework/telemetry-utils": ">=2.0.0-rc.3.0.3 <2.0.0-rc.3.1.0", | ||
"@fluid-internal/client-utils": ">=2.0.0-rc.3.0.4 <2.0.0-rc.3.1.0", | ||
"@fluidframework/core-interfaces": ">=2.0.0-rc.3.0.4 <2.0.0-rc.3.1.0", | ||
"@fluidframework/core-utils": ">=2.0.0-rc.3.0.4 <2.0.0-rc.3.1.0", | ||
"@fluidframework/telemetry-utils": ">=2.0.0-rc.3.0.4 <2.0.0-rc.3.1.0", | ||
"@tylerbu/sorted-btree-es6": "^1.8.0", | ||
@@ -93,4 +93,4 @@ "uuid": "^9.0.0" | ||
"@biomejs/biome": "^1.6.2", | ||
"@fluid-internal/mocha-test-setup": ">=2.0.0-rc.3.0.3 <2.0.0-rc.3.1.0", | ||
"@fluid-private/stochastic-test-utils": ">=2.0.0-rc.3.0.3 <2.0.0-rc.3.1.0", | ||
"@fluid-internal/mocha-test-setup": ">=2.0.0-rc.3.0.4 <2.0.0-rc.3.1.0", | ||
"@fluid-private/stochastic-test-utils": ">=2.0.0-rc.3.0.4 <2.0.0-rc.3.1.0", | ||
"@fluid-tools/benchmark": "^0.48.0", | ||
@@ -97,0 +97,0 @@ "@fluid-tools/build-cli": "^0.37.0", |
@@ -9,4 +9,7 @@ /*! | ||
import { assert } from "@fluidframework/core-utils/internal"; | ||
import { ITelemetryLoggerExt } from "@fluidframework/telemetry-utils"; | ||
import { createChildLogger } from "@fluidframework/telemetry-utils/internal"; | ||
import { | ||
ITelemetryLoggerExt, | ||
LoggingError, | ||
createChildLogger, | ||
} from "@fluidframework/telemetry-utils/internal"; | ||
@@ -62,2 +65,9 @@ import { FinalSpace } from "./finalSpace.js"; | ||
function rangeFinalizationError(expectedStart: number, actualStart: number): LoggingError { | ||
return new LoggingError("Ranges finalized out of order", { | ||
expectedStart, | ||
actualStart, | ||
}); | ||
} | ||
/** | ||
@@ -228,10 +238,45 @@ * See {@link IIdCompressor} and {@link IIdCompressorCore} | ||
}; | ||
return this.updateToRange(range); | ||
} | ||
public takeUnfinalizedCreationRange(): IdCreationRange { | ||
const lastLocalCluster = this.localSession.getLastCluster(); | ||
let count: number; | ||
let firstGenCount: number; | ||
if (lastLocalCluster === undefined) { | ||
firstGenCount = 1; | ||
count = this.localGenCount; | ||
} else { | ||
firstGenCount = genCountFromLocalId( | ||
(lastLocalCluster.baseLocalId - lastLocalCluster.count) as LocalCompressedId, | ||
); | ||
count = this.localGenCount - firstGenCount + 1; | ||
} | ||
if (count === 0) { | ||
return { | ||
sessionId: this.localSessionId, | ||
}; | ||
} | ||
const range: IdCreationRange = { | ||
ids: { | ||
count, | ||
firstGenCount, | ||
localIdRanges: this.normalizer.getRangesBetween(firstGenCount, this.localGenCount), | ||
requestedClusterSize: this.nextRequestedClusterSize, | ||
}, | ||
sessionId: this.localSessionId, | ||
}; | ||
return this.updateToRange(range); | ||
} | ||
private updateToRange(range: IdCreationRange): IdCreationRange { | ||
this.nextRangeBaseGenCount = this.localGenCount + 1; | ||
IdCompressor.assertValidRange(range); | ||
return range; | ||
return IdCompressor.assertValidRange(range); | ||
} | ||
private static assertValidRange(range: IdCreationRange): void { | ||
private static assertValidRange(range: IdCreationRange): IdCreationRange { | ||
if (range.ids === undefined) { | ||
return; | ||
return range; | ||
} | ||
@@ -245,2 +290,3 @@ const { count, requestedClusterSize } = range.ids; | ||
); | ||
return range; | ||
} | ||
@@ -268,3 +314,3 @@ | ||
if (rangeBaseLocal !== -1) { | ||
throw new Error("Ranges finalized out of order."); | ||
throw rangeFinalizationError(-1, rangeBaseLocal); | ||
} | ||
@@ -282,3 +328,6 @@ lastCluster = this.addEmptyCluster(session, requestedClusterSize + count); | ||
if (lastCluster.baseLocalId - lastCluster.count !== rangeBaseLocal) { | ||
throw new Error("Ranges finalized out of order."); | ||
throw rangeFinalizationError( | ||
lastCluster.baseLocalId - lastCluster.count, | ||
rangeBaseLocal, | ||
); | ||
} | ||
@@ -285,0 +334,0 @@ |
@@ -9,2 +9,2 @@ /*! | ||
export const pkgName = "@fluidframework/id-compressor"; | ||
export const pkgVersion = "2.0.0-rc.3.0.3"; | ||
export const pkgVersion = "2.0.0-rc.3.0.4"; |
@@ -36,2 +36,3 @@ /*! | ||
import { SessionSpaceNormalizer } from "../sessionSpaceNormalizer.js"; | ||
import { | ||
@@ -290,4 +291,3 @@ FinalCompressedId, | ||
public changeCapacity(client: Client, newClusterCapacity: number): void { | ||
// eslint-disable-next-line @typescript-eslint/dot-notation | ||
this.compressors.get(client)["nextRequestedClusterSize"] = newClusterCapacity; | ||
changeCapacity(this.compressors.get(client), newClusterCapacity); | ||
} | ||
@@ -432,25 +432,88 @@ | ||
// Ensure creation ranges for clients we track contain the correct local ID ranges | ||
this.serverOperations.forEach(([range, opSpaceIds, clientFrom]) => { | ||
const getLocalIdsInRange = ( | ||
range: IdCreationRange, | ||
opSpaceIds?: OpSpaceCompressedId[], | ||
): Set<SessionSpaceCompressedId> => { | ||
const localIdsInCreationRange = new Set<SessionSpaceCompressedId>(); | ||
if (clientFrom !== OriginatingClient.Remote) { | ||
const ids = range.ids; | ||
if (ids !== undefined) { | ||
const { firstGenCount, localIdRanges } = ids; | ||
for (const [genCount, count] of localIdRanges) { | ||
for (let g = genCount; g < genCount + count; g++) { | ||
const local = localIdFromGenCount(g); | ||
const ids = range.ids; | ||
if (ids !== undefined) { | ||
const { firstGenCount, localIdRanges } = ids; | ||
for (const [genCount, count] of localIdRanges) { | ||
for (let g = genCount; g < genCount + count; g++) { | ||
const local = localIdFromGenCount(g); | ||
if (opSpaceIds) { | ||
assert.strictEqual(opSpaceIds[g - firstGenCount], local); | ||
localIdsInCreationRange.add(local); | ||
} | ||
localIdsInCreationRange.add(local); | ||
} | ||
} | ||
} | ||
return localIdsInCreationRange; | ||
}; | ||
// Ensure creation ranges for clients we track contain the correct local ID ranges | ||
this.serverOperations.forEach(([range, opSpaceIds, clientFrom]) => { | ||
if (clientFrom !== OriginatingClient.Remote) { | ||
const localIdsInCreationRange = getLocalIdsInRange(range, opSpaceIds); | ||
let localCount = 0; | ||
opSpaceIds.forEach((id) => { | ||
if (isLocalId(id)) { | ||
localCount++; | ||
assert(localIdsInCreationRange.has(id), "Local ID not in creation range"); | ||
} | ||
}); | ||
assert.strictEqual( | ||
localCount, | ||
localIdsInCreationRange.size, | ||
"Local ID count mismatch", | ||
); | ||
} | ||
}); | ||
const undeliveredRanges = new Map<Client, IdCreationRange[]>(); | ||
this.clientProgress.forEach((progress, client) => { | ||
const ranges = this.serverOperations | ||
.slice(progress) | ||
.filter((op) => op[2] === client) | ||
.map(([range]) => range); | ||
undeliveredRanges.set(client, ranges); | ||
}); | ||
undeliveredRanges.forEach((ranges, client) => { | ||
const compressor = this.compressors.get(client); | ||
let firstGenCount: number | undefined; | ||
let totalCount = 0; | ||
const unionedLocalRanges = new SessionSpaceNormalizer(); | ||
ranges.forEach((range) => { | ||
assert(range.sessionId === compressor.localSessionId); | ||
if (range.ids !== undefined) { | ||
// initialize firstGenCount if not set | ||
if (firstGenCount === undefined) { | ||
firstGenCount = range.ids.firstGenCount; | ||
} | ||
totalCount += range.ids.count; | ||
range.ids.localIdRanges.forEach(([genCount, count]) => { | ||
unionedLocalRanges.addLocalRange(genCount, count); | ||
}); | ||
} | ||
}); | ||
const retakenRange = compressor.takeUnfinalizedCreationRange(); | ||
if (retakenRange.ids !== undefined) { | ||
const retakenLocalIds = new SessionSpaceNormalizer(); | ||
retakenRange.ids.localIdRanges.forEach(([genCount, count]) => { | ||
retakenLocalIds.addLocalRange(genCount, count); | ||
}); | ||
assert.strictEqual( | ||
retakenLocalIds.equals(unionedLocalRanges), | ||
true, | ||
"Local ID ranges mismatch", | ||
); | ||
assert.strictEqual(retakenRange.ids.count, totalCount, "Count mismatch"); | ||
assert.strictEqual(retakenRange.ids.firstGenCount, firstGenCount, "Count mismatch"); | ||
} else { | ||
assert.strictEqual(totalCount, 0); | ||
assert.strictEqual(unionedLocalRanges.idRanges.size, 0); | ||
} | ||
}); | ||
// First, ensure all clients each generated a unique ID for each of their own calls to generate. | ||
@@ -594,2 +657,7 @@ for (const [compressor, ids] of sequencedLogs) { | ||
function changeCapacity(compressor: IdCompressor, newClusterCapacity: number): void { | ||
// eslint-disable-next-line @typescript-eslint/dot-notation | ||
compressor["nextRequestedClusterSize"] = newClusterCapacity; | ||
} | ||
/** | ||
@@ -615,9 +683,17 @@ * Roundtrips the supplied compressor through serialization and deserialization. | ||
): [SerializedIdCompressorWithOngoingSession | SerializedIdCompressorWithNoSession, IdCompressor] { | ||
// preserve the capacity request as this property is normally private and resets | ||
// to a default on construction (deserialization) | ||
// eslint-disable-next-line @typescript-eslint/dot-notation | ||
const capacity = compressor["nextRequestedClusterSize"]; | ||
if (withSession) { | ||
const serialized = compressor.serialize(withSession); | ||
return [serialized, IdCompressor.deserialize(serialized)]; | ||
const roundtripped = IdCompressor.deserialize(serialized); | ||
changeCapacity(roundtripped, capacity); | ||
return [serialized, roundtripped]; | ||
} else { | ||
const nonLocalSerialized = compressor.serialize(withSession); | ||
const roundtripped = IdCompressor.deserialize(nonLocalSerialized, createSessionId()); | ||
changeCapacity(roundtripped, capacity); | ||
return [nonLocalSerialized, roundtripped]; | ||
} | ||
const nonLocalSerialized = compressor.serialize(withSession); | ||
return [nonLocalSerialized, IdCompressor.deserialize(nonLocalSerialized, createSessionId())]; | ||
} | ||
@@ -819,3 +895,3 @@ | ||
const allocationWeight = 16; | ||
const allocationWeight = 20; | ||
return interleave( | ||
@@ -826,3 +902,3 @@ createWeightedGenerator<Operation, FuzzTestState>([ | ||
[allocateOutsideIdsGenerator, Math.round(allocationWeight * outsideAllocationFraction)], | ||
[deliverAllOperationsGenerator, 2], | ||
[deliverAllOperationsGenerator, 1], | ||
[deliverSomeOperationsGenerator, 6], | ||
@@ -899,3 +975,2 @@ [reconnectGenerator, 1], | ||
validate: (state) => { | ||
network.deliverOperations(DestinationClient.All); | ||
validator?.(network); | ||
@@ -902,0 +977,0 @@ return state; |
@@ -83,3 +83,4 @@ /*! | ||
* Returns a range of IDs created by this session in a format for sending to the server for finalizing. | ||
* The range will include all IDs generated via calls to `generateCompressedId` since the last time this method was called. | ||
* The range will include all IDs generated via calls to `generateCompressedId` since the last time a | ||
* range was taken (via this method or `takeUnfinalizedCreationRange`). | ||
* @returns the range of IDs, which may be empty. This range must be sent to the server for ordering before | ||
@@ -91,2 +92,12 @@ * it is finalized. Ranges must be sent to the server in the order that they are taken via calls to this method. | ||
/** | ||
* Returns a range of IDs created by this session in a format for sending to the server for finalizing. | ||
* The range will include all unfinalized IDs generated via calls to `generateCompressedId`. | ||
* @returns the range of IDs, which may be empty. This range must be sent to the server for ordering before | ||
* it is finalized. Ranges must be sent to the server in the order that they are taken via calls to this method. | ||
* Note: after finalizing the range returned by this method, finalizing any ranges that had been previously taken | ||
* will result in an error. | ||
*/ | ||
takeUnfinalizedCreationRange(): IdCreationRange; | ||
/** | ||
* Finalizes the supplied range of IDs (which may be from either a remote or local session). | ||
@@ -93,0 +104,0 @@ * @param range - the range of session-local IDs to finalize. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1286473
10884