fix(xo-server/v2v): more block alignment fixes (#9293)

from #45336
This commit is contained in:
Florent BEAUCHAMP
2026-01-05 16:01:59 +01:00
committed by GitHub
parent 1d8c429532
commit 42eb8664be
6 changed files with 105 additions and 19 deletions

View File

@@ -6,6 +6,8 @@ export class DiskSmallerBlock extends DiskPassthrough {
#blockSize
#generatedDiskBlocks = 0
#blockCache?: DiskBlock
#blockRatio: number
#maxBlockIndex: number
constructor(source: Disk, blockSize: number) {
super(source)
@@ -17,9 +19,11 @@ export class DiskSmallerBlock extends DiskPassthrough {
assert.strictEqual(
source.getBlockSize() % blockSize,
0,
`target block size ${blockSize} must be a multiple of the source block size ${source.getBlockSize()} `
`source block size ${blockSize} must be a multiple of the target block size ${source.getBlockSize()} `
)
this.#blockSize = blockSize
this.#blockRatio = source.getBlockSize() / blockSize
this.#maxBlockIndex = Math.ceil(this.getVirtualSize() / this.getBlockSize())
}
openSource(): Promise<Disk> {
@@ -35,13 +39,19 @@ export class DiskSmallerBlock extends DiskPassthrough {
try {
const blockRatio = this.source.getBlockSize() / this.#blockSize
const sourceGenerator = this.source.diskBlocks()
// we don't reuse getBlockIndexes here since we want don't want to force
// a use of source.readblock that would limit the use of this class to RandomeAccessDisk
// thus we generate block based on the source block generator
for await (const { data: sourceData, index: sourceIndex } of sourceGenerator) {
for (let i = 0; i < blockRatio; i++) {
this.#generatedDiskBlocks++
const data = sourceData.subarray(i * this.#blockSize, (i + 1) * this.#blockSize)
yield {
index: sourceIndex * blockRatio + i,
data,
const index = sourceIndex * blockRatio + i
if (this.#isBlockBeforeEnd(index)) {
yield {
index,
data,
}
}
}
}
@@ -54,15 +64,22 @@ export class DiskSmallerBlock extends DiskPassthrough {
getBlockSize(): number {
return this.#blockSize
}
// changing the block size can lead
// to the last block being incomplete
// for example going for a block size of 1024 to 512
// and a virtual size of 1200 means 2 blocks in source,
// but 3 here
#isBlockBeforeEnd(blockIndex: number): boolean {
return blockIndex < this.#maxBlockIndex
}
getBlockIndexes(): Array<number> {
const blockRatio = this.source.getBlockSize() / this.getBlockSize()
const sourceIndexes = this.source.getBlockIndexes()
const indexes: Array<number> = []
const maxIndex = Math.ceil(this.getVirtualSize() / this.getBlockSize())
for (const sourceIndex of sourceIndexes) {
for (let i = 0; i < blockRatio; i++) {
const index = sourceIndex * blockRatio + i
if (index < maxIndex) {
if (this.#isBlockBeforeEnd(index)) {
indexes.push(index)
}
}
@@ -71,13 +88,28 @@ export class DiskSmallerBlock extends DiskPassthrough {
}
hasBlock(index: number): boolean {
// asking for a block after the end of the smaller disk => false
if (!this.#isBlockBeforeEnd(index)) {
return false
}
const blockRatio = this.source.getBlockSize() / this.getBlockSize()
const sourceIndex = Math.floor(index / blockRatio)
return this.source.hasBlock(sourceIndex)
}
/**
* handle gracefully readblock if the source is a RandomAccessDisk
* @param index
* @returns Promise<DiskBlock>
*/
async readBlock(index: number): Promise<DiskBlock> {
if (this.source instanceof RandomAccessDisk) {
if (!this.#isBlockBeforeEnd(index)) {
throw new Error(
`Read after the end, asking for ${index}, last block is ${this.#maxBlockIndex - 1}, vitual size is ${this.getVirtualSize()}, blockSize : ${this.getBlockSize()}`
)
}
const blockRatio = this.source.getBlockSize() / this.getBlockSize()
const sourceBlockIndex = Math.floor(index / blockRatio)
const indexInblock = index % blockRatio

View File

@@ -160,8 +160,8 @@ test('hasBlock behavior', async () => {
test('getBlockIndexes', async () => {
const source = new MockDisk(512, 2048, [
[0, Buffer.alloc(512)],
[2, Buffer.alloc(512)],
[0, Buffer.alloc(2048)],
[2, Buffer.alloc(2048)],
])
await source.init()
@@ -182,3 +182,28 @@ test('close propagation', async () => {
await disk.close()
assert(source.closed)
})
test('unaligned END ', async () => {
const source = new MockDisk(1024, 2048 + 256, [
[0, Buffer.alloc(1024)],
[2, Buffer.alloc(1024)],
])
await source.init()
const disk = new DiskSmallerBlock(source, 256)
await disk.init()
assert.strictEqual(source.getVirtualSize(), disk.getVirtualSize())
const indexes = disk.getBlockIndexes()
const expecteBlockIndex = [0, 1, 2, 3, 8]
assert.deepStrictEqual(indexes, expecteBlockIndex)
assert.strictEqual(disk.hasBlock(8), true)
assert.strictEqual(disk.hasBlock(9), false) //After the end of small block disk
assert.strictEqual(disk.hasBlock(12), false) //After the end of source disk
// ensure disk blocks also handle the unaligned part
for await (const { index, data } of disk.diskBlocks()) {
assert.ok(expecteBlockIndex.includes(index))
assert.equal(data.length, 256)
}
})