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-
259251func (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.
281263func (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.
334294func (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