Repository navigation
fix(storage): correct CombinedDevice allocated() to sum inner devices - #13
Closed
detail-app[bot] wants to merge 1 commit into
Closed
detail-app[bot] wants to merge 1 commit into
detail-app[bot] wants to merge 1 commit into
Conversation
Author
|
Closing this pull request because it has had no human activity in 30 days (since 2026-09-11) and was opened 30 days ago. Detail closes its pull requests once they are more than 30 days old and have had no human activity for 14 days. If this change is still useful, reopen the pull request and Detail won't close it again. To keep a Detail pull request open, add the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Detail bug report: View on Detail
Bug
CombinedDevice::allocated()infoyer-storage/src/io/device/combined.rsmiscounted allocated space. It treatedinner.next(a device index) as a partition count by slicinginner.partitions.iter().take(inner.next), then addedself.devices[inner.next].allocated(). This assumed an invariant — thatinner.nextequals the number of partitions created by prior devices — that was never maintained:NoSpace,create_partitionadvancesinner.nextwithout recording a partition.inner.nextis not advanced per partition created.So
allocated()could under-report (a multi-partition device:take(1)sums only the first partition) or over-report (leading sub-block_sizedevices: partitions get double-counted via both thetakeslice and thedevices[next]term). Over-reporting can exceedcapacity(), causing the defaultfree()(capacity() - allocated(), unsignedusize) to underflow — panicking in debug builds, wrapping in release — and can make the block-manager loop stop one block early, silently losing oneblock_sizeof cache.Fix
Replace
allocated()with the sum of each inner device's ownallocated():Each inner device (
FileDevice,FsDevice) already tracks its own partitions accurately as the sum of partition sizes, matching the existingFileDevice/FsDevicesemantics. This removes the fragile coupling between the device index and the flat partition vector and fixes all three failure directions (under-count, over-count silent loss, over-count underflow). Theinnerlock is no longer needed for this method;capacity()andcreate_partition()account for their own state independently.Testing
Added unit tests in
combined.rsusing a self-contained in-memoryMockDevicethat mirrorsFileDevice/FsDevicesemantics (allocated()= sum of partition sizes;NoSpaceiffallocated + size > capacity), so no filesystem is required. Each test guards a distinct failure mode of the old arithmetic and would catch a regression ifallocated()were ever rewritten to reintroducetake(inner.next)logic:test_combined_device_one_leading_small_silent_loss— one leading sub-block_sizedevice (4 MiB) + a 48 MiB trailing device; asserts the loop carves all 3 blocks (not 2) via thefree() >= block_sizeloop the block manager uses.test_combined_device_two_leading_allocated_exceeds_capacity— two leading sub-block_sizedevices + a trailing device; after 3 allocations assertsallocated() <= capacity()(no underflow).test_combined_device_multi_partition_under_count— a 3-partition device + a 1-partition device; after 4 allocations assertsallocated() == 4 * block_size(not the halved value the oldtake(inner.next)produced).Verification run:
--release(the release run confirmsfree()does not wrap).cargo check,cargo clippy -p foyer-storage,cargo fmt --check, and the nightly "fastidious" format check (rustfmt.nightly.toml): all clean.engine::block::suite (13 tests, including the real-FsDevicetest_aggregated_deviceconsumer) passes — no regression in the block engine paths that consumeCombinedDevice.foyer-storagelib suite (28 tests): pass.foyerlib tests + gatedstorage_fuzzy_test(real-FsDeviceend-to-end store paths, via--features test_utils,strict_assertions) pass.cargo nextest run --all --features strict_assertions(115 pass, 1#[ignore]d skip),cargo test --doc(4 doctests pass), andcargo clippy --all-targets` all pass.The only warnings observed are pre-existing and unrelated to this change: a
dead_codewarning onstore.rs::load_throttle_switchand an MSRV-toolchainclippy::chunks_exact_to_as_chunksunknown-lint note.Automatic Fixes PRs can be configured here.