Skip to content

Commit 779e033

Browse files
levbclaude
andcommitted
refactor(block): extract atomic bitset to shared utility package
Move the []atomic.Uint64 bitmap from block.Cache into a standalone atomicbitset.Bitset in packages/shared/pkg/atomicbitset. Word-level masking in HasRange reduces atomic loads on the chunker hot path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent be976c0 commit 779e033

File tree

4 files changed

+367
-433
lines changed

4 files changed

+367
-433
lines changed

packages/orchestrator/pkg/sandbox/block/cache.go

Lines changed: 12 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"fmt"
77
"io"
88
"math"
9-
"math/bits"
109
"math/rand"
1110
"os"
1211
"sync"
@@ -19,6 +18,7 @@ import (
1918
"go.opentelemetry.io/otel"
2019
"golang.org/x/sys/unix"
2120

21+
"github.com/e2b-dev/infra/packages/shared/pkg/atomicbitset"
2222
"github.com/e2b-dev/infra/packages/shared/pkg/storage/header"
2323
)
2424

@@ -49,7 +49,7 @@ type Cache struct {
4949
blockSize int64
5050
mmap *mmap.MMap
5151
mu sync.RWMutex
52-
dirty []atomic.Uint64 // bitset indexed by off/blockSize — bit is set when block is present
52+
dirty atomicbitset.Bitset
5353
dirtyFile bool
5454
closed atomic.Bool
5555
}
@@ -95,7 +95,7 @@ func NewCache(size, blockSize int64, filePath string, dirtyFile bool) (*Cache, e
9595
size: size,
9696
blockSize: blockSize,
9797
dirtyFile: dirtyFile,
98-
dirty: make([]atomic.Uint64, (numBlocks+63)/64),
98+
dirty: atomicbitset.New(uint(numBlocks)),
9999
}, nil
100100
}
101101

@@ -248,66 +248,27 @@ func (c *Cache) Slice(off, length int64) ([]byte, error) {
248248
return nil, BytesNotAvailableError{}
249249
}
250250

251-
func (c *Cache) isBlockCached(blockIdx int64) bool {
252-
if blockIdx < 0 || blockIdx >= int64(len(c.dirty))*64 {
253-
return false
254-
}
255-
256-
return c.dirty[blockIdx/64].Load()&(1<<uint(blockIdx%64)) != 0
257-
}
258-
259251
func (c *Cache) isCached(off, length int64) bool {
260-
// Make sure the offset is within the cache size
261252
if off >= c.size {
262253
return false
263254
}
264255

265-
// Cap if the length goes beyond the cache size, so we don't check for blocks that are out of bounds.
266256
end := min(off+length, c.size)
267-
start := off / c.blockSize
268-
n := (end + c.blockSize - 1) / c.blockSize
257+
start := uint(off / c.blockSize)
258+
endBlock := uint((end + c.blockSize - 1) / c.blockSize)
269259

270-
for i := start; i < n; i++ {
271-
if !c.isBlockCached(i) {
272-
return false
273-
}
274-
}
275-
276-
return true
260+
return c.dirty.HasRange(start, endBlock)
277261
}
278262

279-
// setIsCached marks all blocks in [off, off+length) as cached.
280-
// Uses atomic OR so concurrent callers for disjoint ranges are safe.
281263
func (c *Cache) setIsCached(off, length int64) {
282264
if length <= 0 {
283265
return
284266
}
285267

286-
start := off / c.blockSize
287-
n := (off + length + c.blockSize - 1) / c.blockSize
268+
start := uint(off / c.blockSize)
269+
endBlock := uint((off + length + c.blockSize - 1) / c.blockSize)
288270

289-
// Cap to the actual bitmap size so callers that pass a range extending
290-
// past c.size (e.g. a partial last segment) don't panic on index OOB.
291-
if maxBlock := int64(len(c.dirty)) * 64; n > maxBlock {
292-
n = maxBlock
293-
}
294-
295-
for i := start; i < n; {
296-
w := i / 64
297-
lo := i % 64
298-
hi := min(n-w*64, 64)
299-
300-
var mask uint64
301-
if hi-lo == 64 {
302-
mask = math.MaxUint64
303-
} else {
304-
mask = ((1 << uint(hi-lo)) - 1) << uint(lo)
305-
}
306-
307-
c.dirty[w].Or(mask)
308-
309-
i = (w + 1) * 64
310-
}
271+
c.dirty.SetRange(start, endBlock)
311272
}
312273

313274
// When using WriteAtWithoutLock you must ensure thread safety, ideally by only writing to the same block once and the exposing the slice.
@@ -329,20 +290,12 @@ func (c *Cache) WriteAtWithoutLock(b []byte, off int64) (int, error) {
329290
return n, nil
330291
}
331292

332-
// dirtySortedKeys returns a sorted list of dirty keys.
333-
// Key represents a block offset.
293+
// dirtySortedKeys returns a sorted list of dirty block offsets.
334294
func (c *Cache) dirtySortedKeys() []int64 {
335295
var keys []int64
336296

337-
for wi := range c.dirty {
338-
word := c.dirty[wi].Load()
339-
base := int64(wi) * 64
340-
341-
for word != 0 {
342-
bit := bits.TrailingZeros64(word)
343-
keys = append(keys, (base+int64(bit))*c.blockSize)
344-
word &= word - 1
345-
}
297+
for i := range c.dirty.Iterator() {
298+
keys = append(keys, int64(i)*c.blockSize)
346299
}
347300

348301
return keys

0 commit comments

Comments
 (0)