From 36f0119ed00c6f68b1dd914292a00e2b6235303f Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 5 May 2026 07:56:04 -0700 Subject: [PATCH 1/3] fix(cache): use 512-byte units for stat.Blocks in FileSize stat.Blocks is reported in fixed 512-byte units per POSIX, not in the filesystem's optimal I/O block size. Multiplying by statfs Bsize (4096 on ext4) inflated the on-disk size by ~8x. Drop the Statfs call and multiply by the constant 512. Verified locally that stat.Blocks * 512 matches the actual on-disk allocation while stat.Blocks * statfs.Bsize does not on ext4. --- packages/orchestrator/pkg/sandbox/block/cache.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/orchestrator/pkg/sandbox/block/cache.go b/packages/orchestrator/pkg/sandbox/block/cache.go index 5563c7e30e..81ea4b977c 100644 --- a/packages/orchestrator/pkg/sandbox/block/cache.go +++ b/packages/orchestrator/pkg/sandbox/block/cache.go @@ -661,18 +661,15 @@ func (c *Cache) WriteZeroesAt(off, length int64) (int, error) { // The size might differ from the dirty size, as it may not be fully on disk. func (c *Cache) FileSize(_ context.Context) (int64, error) { var stat syscall.Stat_t - err := syscall.Stat(c.filePath, &stat) - if err != nil { + if err := syscall.Stat(c.filePath, &stat); err != nil { return 0, fmt.Errorf("failed to get file stats: %w", err) } - var fsStat syscall.Statfs_t - err = syscall.Statfs(c.filePath, &fsStat) - if err != nil { - return 0, fmt.Errorf("failed to get disk stats for path %s: %w", c.filePath, err) - } + // Per POSIX (and Linux man 2 stat), stat.Blocks is always reported in + // 512-byte units, regardless of the underlying filesystem's block size. + const stBlockSize = 512 - return stat.Blocks * fsStat.Bsize, nil + return stat.Blocks * stBlockSize, nil } func (c *Cache) address(off int64) (*byte, error) { From 2b3402508e2aabdf472f2af4589c6b17b7cf34ba Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 9 Jun 2026 10:05:02 -0700 Subject: [PATCH 2/3] test(cache): FileSize reports actual on-disk allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Behavioral assertion: writing N full blocks through Cache makes FileSize return N*blockSize. Catches the stat.Blocks * statfs.Bsize regression directly — under that bug, FileSize would return 8x the truth on ext4 and the test fails loudly. --- .../pkg/sandbox/block/cache_test.go | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/orchestrator/pkg/sandbox/block/cache_test.go b/packages/orchestrator/pkg/sandbox/block/cache_test.go index e72e5a35b7..7de98b7a20 100644 --- a/packages/orchestrator/pkg/sandbox/block/cache_test.go +++ b/packages/orchestrator/pkg/sandbox/block/cache_test.go @@ -1140,3 +1140,43 @@ func TestCacheDedup_BestEffortPerPageCacheCheck(t *testing.T) { require.True(t, meta.Dirty.Contains(1)) require.True(t, meta.Dirty.Contains(3)) } + +// TestCache_FileSize_MatchesActualAllocation writes a known number of full +// blocks through Cache and verifies that FileSize reports the on-disk +// allocation in bytes — i.e. one block written ⇒ one block on disk. +// +// This is a regression test for using the wrong block-size constant in the +// stat.Blocks math. Before the fix, FileSize multiplied stat.Blocks (POSIX +// 512-byte units) by statfs.Bsize (4096 on ext4), inflating the reported +// usage by 8× and stalling the build-cache disk-pressure eviction loop. +func TestCache_FileSize_MatchesActualAllocation(t *testing.T) { + t.Parallel() + + const ( + blockSize = int64(4096) + nBlocks = int64(4) + fileSize = blockSize * 64 // sparse, only nBlocks actually allocated + ) + + cache, err := NewCache(fileSize, blockSize, t.TempDir()+"/cache", false) + require.NoError(t, err) + t.Cleanup(func() { _ = cache.Close() }) + + buf := make([]byte, blockSize) + _, err = rand.Read(buf) + require.NoError(t, err) + for i := int64(0); i < nBlocks; i++ { + _, err = cache.WriteAt(buf, i*blockSize) + require.NoError(t, err) + } + + got, err := cache.FileSize(t.Context()) + require.NoError(t, err) + + expected := nBlocks * blockSize + t.Logf("wrote %d blocks of %d B; FileSize=%d B (expected %d B)", nBlocks, blockSize, got, expected) + + require.Equal(t, expected, got, + "FileSize must report on-disk allocation in bytes; a value ~%d× expected suggests stat.Blocks was multiplied by statfs.Bsize instead of the POSIX 512", + int64(4096)/int64(512)) +} From 4f0fa6633899d36b60938467b52e7a4a07b3c924 Mon Sep 17 00:00:00 2001 From: Lev Brouk Date: Tue, 9 Jun 2026 10:08:16 -0700 Subject: [PATCH 3/3] lint --- packages/orchestrator/pkg/sandbox/block/cache_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestrator/pkg/sandbox/block/cache_test.go b/packages/orchestrator/pkg/sandbox/block/cache_test.go index 7de98b7a20..9cc8441e01 100644 --- a/packages/orchestrator/pkg/sandbox/block/cache_test.go +++ b/packages/orchestrator/pkg/sandbox/block/cache_test.go @@ -1165,7 +1165,7 @@ func TestCache_FileSize_MatchesActualAllocation(t *testing.T) { buf := make([]byte, blockSize) _, err = rand.Read(buf) require.NoError(t, err) - for i := int64(0); i < nBlocks; i++ { + for i := range nBlocks { _, err = cache.WriteAt(buf, i*blockSize) require.NoError(t, err) }