Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
256 commits
Select commit Hold shift + click to select a range
9d7f5e5
[WIP] Multicopy
ValentaTomas Aug 12, 2025
66a1bbe
Remove experiments
ValentaTomas Aug 19, 2025
e774a92
Add the page alignment check (only in method)
ValentaTomas Aug 19, 2025
f240542
Remove commented out part
ValentaTomas Aug 19, 2025
f31cb47
Remove unused return field
ValentaTomas Aug 19, 2025
bfc5935
Cleanup
ValentaTomas Aug 19, 2025
f732d5a
Remove WP parts
ValentaTomas Aug 19, 2025
855369b
Cleanup
ValentaTomas Aug 19, 2025
f981d76
Fix compile errors
ValentaTomas Oct 3, 2025
ccd8396
Cleanup
ValentaTomas Oct 4, 2025
fbf258a
Simplify offset check
ValentaTomas Oct 4, 2025
67b1bfb
Clarify comment
ValentaTomas Oct 4, 2025
28c48a7
Add test constant
ValentaTomas Oct 4, 2025
1ecd0a1
Fix incorrect log
ValentaTomas Oct 4, 2025
4fe40a0
Fix format
ValentaTomas Oct 4, 2025
bd8dc0b
Remove conversion
ValentaTomas Oct 4, 2025
f0d17ef
Make method public
ValentaTomas Oct 4, 2025
c4691b5
Add command context
ValentaTomas Oct 4, 2025
9581305
Fix error formatting
ValentaTomas Oct 4, 2025
fa72b74
Fix error formatting
ValentaTomas Oct 4, 2025
8230e4f
Improve error comparison
ValentaTomas Oct 4, 2025
1454794
Fix error formatting
ValentaTomas Oct 4, 2025
ac484d2
Change process exit
ValentaTomas Oct 4, 2025
f2d01de
Enable unpriviledged uffd mode in GH PR tests
ValentaTomas Oct 4, 2025
d27b2d4
Trigger build
ValentaTomas Oct 4, 2025
5e83359
Merge branch 'uffd-extract' of https://github.com/e2b-dev/infra into …
ValentaTomas Oct 4, 2025
05ae2e9
Fix uffd unpriviledged enable
ValentaTomas Oct 4, 2025
512fd04
Use 4k pages in uffd cross process test
ValentaTomas Oct 4, 2025
1ecc810
Add access checks to tests
ValentaTomas Oct 5, 2025
fd77d22
Cleanup
ValentaTomas Oct 5, 2025
b1a6d53
Remove unused constants for now
ValentaTomas Oct 5, 2025
2a329c8
Fix lint issue
ValentaTomas Oct 5, 2025
44984ea
Clarify naming
ValentaTomas Oct 5, 2025
c5e55e3
Clarify comment
ValentaTomas Oct 5, 2025
b04e552
Remove yet non-relevant diagram
ValentaTomas Oct 5, 2025
9607180
Remove comment
ValentaTomas Oct 5, 2025
3cf3bec
Remove write protection field
ValentaTomas Oct 5, 2025
ac9bfdf
Add explicit mmap cleanup
ValentaTomas Oct 6, 2025
3ab7848
Merge branch 'main' into uffd-extract
ValentaTomas Oct 6, 2025
69954c5
Improve test names
ValentaTomas Oct 6, 2025
4ce39ab
Fix test error message
ValentaTomas Oct 6, 2025
e8b9330
Cleanup tests
ValentaTomas Oct 6, 2025
e366753
Merge branch 'main' into uffd-extract
ValentaTomas Oct 6, 2025
5ef4373
Fix lint error
ValentaTomas Oct 6, 2025
bb951ba
Add uffd write protect constants
ValentaTomas Oct 5, 2025
8906575
Add UFFD write protection event diagram
ValentaTomas Oct 5, 2025
d89b81f
Add userfaultfd write protection methods
ValentaTomas Oct 5, 2025
f2a012a
Add tests todo
ValentaTomas Oct 5, 2025
e522612
[WIP] Add dirty tracking via WP
ValentaTomas Oct 6, 2025
64e2b13
Clarify WP flow
ValentaTomas Oct 7, 2025
bd8988e
[WIP] Add WP to the serve loop
ValentaTomas Oct 7, 2025
0dadb58
Wrap uffd fd in a struct with corresponding methods; Move the low lev…
ValentaTomas Aug 7, 2025
34ccfc5
Unify receiver name
ValentaTomas Aug 7, 2025
7312bca
[WIP] Add uffd tests
ValentaTomas Aug 7, 2025
3c75576
Add disabled wp
ValentaTomas Aug 7, 2025
b67f0f2
[WIP] WP
ValentaTomas Aug 8, 2025
9f1708c
[WIP] Check multicopy
ValentaTomas Aug 10, 2025
b07e13d
[WIP] Multicopy
ValentaTomas Aug 12, 2025
ff23f18
Remove experiments
ValentaTomas Aug 19, 2025
389862e
Add the page alignment check (only in method)
ValentaTomas Aug 19, 2025
a8ec1bf
Remove commented out part
ValentaTomas Aug 19, 2025
5632f63
Remove unused return field
ValentaTomas Aug 19, 2025
ef33099
Cleanup
ValentaTomas Aug 19, 2025
5384bec
Remove WP parts
ValentaTomas Aug 19, 2025
f26cc5c
Cleanup
ValentaTomas Aug 19, 2025
47e14b0
Fix compile errors
ValentaTomas Oct 3, 2025
7ba06e7
Cleanup
ValentaTomas Oct 4, 2025
e91945f
Simplify offset check
ValentaTomas Oct 4, 2025
d4c2ea0
Clarify comment
ValentaTomas Oct 4, 2025
091607e
Add test constant
ValentaTomas Oct 4, 2025
7ae5f70
Fix incorrect log
ValentaTomas Oct 4, 2025
f9f95c5
Fix format
ValentaTomas Oct 4, 2025
708d54b
Remove conversion
ValentaTomas Oct 4, 2025
2aa9449
Make method public
ValentaTomas Oct 4, 2025
dc7dc60
Add command context
ValentaTomas Oct 4, 2025
6331bd2
Fix error formatting
ValentaTomas Oct 4, 2025
97ee756
Fix error formatting
ValentaTomas Oct 4, 2025
03b0c46
Improve error comparison
ValentaTomas Oct 4, 2025
a331214
Fix error formatting
ValentaTomas Oct 4, 2025
6855c56
Change process exit
ValentaTomas Oct 4, 2025
359c17d
Trigger build
ValentaTomas Oct 4, 2025
835a32d
Enable unpriviledged uffd mode in GH PR tests
ValentaTomas Oct 4, 2025
76c3bf3
Fix uffd unpriviledged enable
ValentaTomas Oct 4, 2025
0ba2a14
Use 4k pages in uffd cross process test
ValentaTomas Oct 4, 2025
52d2fc7
Add access checks to tests
ValentaTomas Oct 5, 2025
f092687
Cleanup
ValentaTomas Oct 5, 2025
3f7216a
Remove unused constants for now
ValentaTomas Oct 5, 2025
7fb669e
Fix lint issue
ValentaTomas Oct 5, 2025
052b09f
Clarify naming
ValentaTomas Oct 5, 2025
1066947
Clarify comment
ValentaTomas Oct 5, 2025
d06985c
Remove yet non-relevant diagram
ValentaTomas Oct 5, 2025
179ba74
Remove comment
ValentaTomas Oct 5, 2025
bacedce
Remove write protection field
ValentaTomas Oct 5, 2025
5d4c576
Add explicit mmap cleanup
ValentaTomas Oct 6, 2025
fe1f562
Improve test names
ValentaTomas Oct 6, 2025
70015bd
Fix test error message
ValentaTomas Oct 6, 2025
6920876
Cleanup tests
ValentaTomas Oct 6, 2025
cb5cfdb
Fix lint error
ValentaTomas Oct 6, 2025
ac77fe5
Put back offset log on uffd panic
ValentaTomas Oct 7, 2025
c11e5ef
Fix lint errors
ValentaTomas Oct 8, 2025
dcea5f1
Merge branch 'main' into uffd-extract
djeebus Oct 10, 2025
43f7daa
Merge branch 'main' into uffd-extract
ValentaTomas Oct 11, 2025
6e6e073
Fix diagram flags
ValentaTomas Oct 13, 2025
1ec8b01
Add uffd write handling
ValentaTomas Oct 14, 2025
f0247a0
Fix test errors
ValentaTomas Oct 14, 2025
131b801
Merge branch 'uffd-extract' into use-uffd-wp-to-only-save-dirty-pages…
ValentaTomas Oct 14, 2025
c283d0c
Make offsetmap thread safe
ValentaTomas Oct 14, 2025
7e78473
Fix merge problems; [WIP] Add triggerable uffd copy
ValentaTomas Oct 14, 2025
bc50b15
Fix refactor bug
ValentaTomas Oct 16, 2025
96f546c
Fix compile error
ValentaTomas Oct 16, 2025
edb1c4c
[WIP] Refactor memory access
ValentaTomas Oct 17, 2025
ec4421f
Fix test after refactor
ValentaTomas Oct 17, 2025
f916358
Cleanup mapping
ValentaTomas Oct 17, 2025
6838b66
Improve naming
ValentaTomas Oct 17, 2025
0d4dc64
[WIP] Cleanup
ValentaTomas Oct 18, 2025
65cf118
Fix test bugs
ValentaTomas Oct 18, 2025
0505ab3
Add testing case
ValentaTomas Oct 18, 2025
a388c74
Cleanup
ValentaTomas Oct 18, 2025
4f89a59
[WIP] Add overlay layer
ValentaTomas Oct 20, 2025
2051bd6
Configure mise
ValentaTomas Oct 20, 2025
8fc2164
Exclude mise tools from git
ValentaTomas Oct 20, 2025
8995e6d
Extend tracker
ValentaTomas Oct 20, 2025
7ac927b
[WIP] Add masked overlay
ValentaTomas Oct 20, 2025
64be117
[WIP] Update masked overlay
ValentaTomas Oct 20, 2025
20c0766
Remove files that will be added later
ValentaTomas Oct 20, 2025
ae57cb4
Cleanup
ValentaTomas Oct 20, 2025
9783690
Remove files that will be added later
ValentaTomas Oct 20, 2025
aea1db4
Remove unnecessary change
ValentaTomas Oct 20, 2025
d9db61a
Cleanup
ValentaTomas Oct 20, 2025
12911de
Remove files that will be added later
ValentaTomas Oct 20, 2025
3b9249c
Add mapping tests
ValentaTomas Oct 20, 2025
06a82ba
Add invalid event log
ValentaTomas Oct 20, 2025
446c935
Remove files that will be added later
ValentaTomas Oct 20, 2025
1bd8700
Remove methods that will be added later
ValentaTomas Oct 20, 2025
345b335
Merge branch 'main' into refactor-uffd-add-wp
ValentaTomas Oct 20, 2025
51ccb5e
Minimize changes
ValentaTomas Oct 20, 2025
bc3e626
Add settle counter tests
ValentaTomas Oct 20, 2025
ae02a32
Minimize changes
ValentaTomas Oct 20, 2025
38e57f7
Cleanup
ValentaTomas Oct 20, 2025
2055343
Fix lint issues
ValentaTomas Oct 20, 2025
4d44340
Revert noop memory change
ValentaTomas Oct 21, 2025
71103ec
Fix formatting
ValentaTomas Oct 21, 2025
c7db6b3
Merge branch 'main' into refactor-uffd-add-wp
ValentaTomas Oct 21, 2025
98dfa4f
Fix uninitialized handler
ValentaTomas Oct 21, 2025
b5a632f
Fix lint errors
ValentaTomas Oct 21, 2025
8614b7b
Test disabling dedup
ValentaTomas Oct 21, 2025
031b7c9
Synchronize test
ValentaTomas Oct 21, 2025
83566b0
Add wp dedup
ValentaTomas Oct 21, 2025
a716af2
Test removing only wp dedup
ValentaTomas Oct 21, 2025
7c3cb44
Cleanup
ValentaTomas Oct 21, 2025
41908a0
Fix linter errors
ValentaTomas Oct 21, 2025
2fa17bc
Clarify uffd behavior
ValentaTomas Oct 21, 2025
0237c08
Add tests for parallel uffd; [WIP] testing non-ready uffd
ValentaTomas Oct 22, 2025
23836ca
[WIP] Add parallel test
ValentaTomas Oct 22, 2025
de1219d
Fix settle counter test
ValentaTomas Oct 23, 2025
8018841
Cleanup
ValentaTomas Oct 24, 2025
4b0ce69
Minimize changes
ValentaTomas Oct 27, 2025
32fdc01
Minimize changes
ValentaTomas Oct 27, 2025
db1543a
Minimize changes
ValentaTomas Oct 27, 2025
57c4f53
Minimize changes
ValentaTomas Oct 27, 2025
450f278
Merge branch 'main' into uffd-refactor
ValentaTomas Oct 27, 2025
2150035
Fix fmt
ValentaTomas Oct 27, 2025
29dbcd3
Tweak tests
ValentaTomas Oct 27, 2025
7ed0a49
Fix lint
ValentaTomas Oct 27, 2025
680dcf5
Minimize changes
ValentaTomas Oct 27, 2025
6bdfaa6
Remove log file
ValentaTomas Oct 27, 2025
81238c0
[WIP] Add tests to current implementation
ValentaTomas Oct 29, 2025
5b60d98
Refactor tracking; Add unregister on pause
ValentaTomas Nov 3, 2025
087e05b
Fix lint errors
ValentaTomas Nov 3, 2025
007420d
Cleanup
ValentaTomas Nov 3, 2025
169a666
Fix test race
ValentaTomas Nov 3, 2025
81fe1c7
Merge branch 'main' into uffd-tests
ValentaTomas Nov 3, 2025
20f7757
Fix test operation
ValentaTomas Nov 3, 2025
e2b11e6
Cleanup tests
ValentaTomas Nov 3, 2025
bb5d630
Merge branch 'uffd-tests' into uffd-refactor
ValentaTomas Nov 4, 2025
1bc2b21
Merge
ValentaTomas Nov 4, 2025
ed4516f
Fix map passing
ValentaTomas Nov 4, 2025
4bf8fbd
Merge branch 'uffd-refactor' into add-tracker
ValentaTomas Nov 4, 2025
6c46c07
Fix serve
ValentaTomas Nov 4, 2025
d08a2f9
Put back eagain increase
ValentaTomas Nov 4, 2025
026471d
Remove log
ValentaTomas Nov 4, 2025
c1f8966
Remove unused var
ValentaTomas Nov 4, 2025
1789c88
Merge branch 'main' into uffd-refactor
ValentaTomas Nov 4, 2025
be8c768
Merge
ValentaTomas Nov 4, 2025
30fbc35
Fix map passing
ValentaTomas Nov 4, 2025
1ff8982
Merge branch 'uffd-refactor' into add-tracker
ValentaTomas Nov 4, 2025
116062b
Cleanup
ValentaTomas Nov 4, 2025
bdc15de
Merge branch 'main' into uffd-refactor
ValentaTomas Nov 7, 2025
d6dbdd5
Merge branch 'main' into uffd-refactor
ValentaTomas Nov 7, 2025
e33692c
Merge
ValentaTomas Nov 7, 2025
f04dcfa
Fix tests
ValentaTomas Nov 7, 2025
73be91e
Cleanup
ValentaTomas Nov 7, 2025
888bc66
Cleanup
ValentaTomas Nov 7, 2025
c505ed7
Cleanup
ValentaTomas Nov 7, 2025
9ec00dd
Merge branch 'uffd-refactor' into add-tracker
ValentaTomas Nov 7, 2025
21ff568
Cleanup
ValentaTomas Nov 7, 2025
e80a760
Remove unused sync.Map function from cross_process_helpers_test.go
ValentaTomas Nov 7, 2025
fe4f0a0
Refactor constant formatting in userfaultfd fd.go for improved readab…
ValentaTomas Nov 7, 2025
0782fc4
Refactor Dirty method in Userfaultfd to accept context and update rel…
ValentaTomas Nov 7, 2025
e2b4ea4
Enhance SettleCounter functionality by improving context handling in …
ValentaTomas Nov 7, 2025
9c6c60f
Refactor Wait method in SettleCounter to improve atomic checks and re…
ValentaTomas Nov 7, 2025
40e4d5f
Refactor Dirty method in Userfaultfd to remove context parameter and …
ValentaTomas Nov 8, 2025
642bf68
Refactor Pause method in Sandbox to improve error handling by reorder…
ValentaTomas Nov 8, 2025
b225692
Add Disable and update comments in Userfaultfd for improved clarity o…
ValentaTomas Nov 8, 2025
6a49d5a
Refactor Userfaultfd to replace settle struct with sync.RWMutex for i…
ValentaTomas Nov 8, 2025
520574f
Refactor Tracker's Add method to remove return value and simplify log…
ValentaTomas Nov 8, 2025
df33f90
Add write protection handling in Userfaultfd and enhance memory regio…
ValentaTomas Nov 8, 2025
4fc3aa2
[WIP] Add WP tests
ValentaTomas Nov 8, 2025
b55be74
Refactor Userfaultfd methods to use uintptr for size parameters and e…
ValentaTomas Nov 8, 2025
121c00b
Use constructor
ValentaTomas Nov 10, 2025
05fb2ca
Refactor slice
ValentaTomas Nov 10, 2025
896d177
Cleanup comments
ValentaTomas Nov 10, 2025
320b26c
Add comments
ValentaTomas Nov 10, 2025
e630223
Improve tests
ValentaTomas Nov 10, 2025
5897b18
Improve tracker tests
ValentaTomas Nov 10, 2025
cdfb109
Cleanup
ValentaTomas Nov 10, 2025
4429aaf
Tweak the lock
ValentaTomas Nov 10, 2025
070ddb6
Merge branch 'main' into add-tracker
ValentaTomas Nov 10, 2025
c43a4e0
Merge branch 'add-tracker' into refactor-uffd-add-wp
ValentaTomas Nov 11, 2025
5899e59
Refactor newUffdioWriteProtect to use newUffdioRange constructor for …
ValentaTomas Nov 11, 2025
febd7e4
Update comments in userfaultfd.go for clarity on memory handling and …
ValentaTomas Nov 11, 2025
050b187
Rename handleWriteProtection to handleWriteProtected for consistency …
ValentaTomas Nov 11, 2025
b7b5329
Add explicit dirty reset
ValentaTomas Nov 11, 2025
2ea1261
Cleanup
ValentaTomas Nov 11, 2025
4f13b7c
Merge branch 'add-tracker' into refactor-uffd-add-wp
ValentaTomas Nov 11, 2025
d282652
WIP: Add tests
ValentaTomas Nov 11, 2025
b6a0c3a
Merge branch 'main' into add-tracker
ValentaTomas Nov 11, 2025
41da606
Refactor Userfaultfd to register memory regions with write protection…
ValentaTomas Nov 11, 2025
b5ffe46
Update memory mapping tests to use uintptr for expected size and repl…
ValentaTomas Nov 11, 2025
7da216e
Remove outdated comments in uffd.go regarding the Disable method and …
ValentaTomas Nov 11, 2025
c9127db
Add tests for various read/write operations in userfaultfd, including…
ValentaTomas Nov 12, 2025
7e6cd7f
[WIP] Refactor tests to allow testing locking
ValentaTomas Nov 12, 2025
2f3cd2d
Add uffd event handler tests
ValentaTomas Nov 12, 2025
e9d6311
Fix event testing
ValentaTomas Nov 12, 2025
185b242
Rename channel in TestUffdSettleRequests to better reflect its purpos…
ValentaTomas Nov 12, 2025
a5d81f7
Improve tests
ValentaTomas Nov 12, 2025
e5e5666
Merge branch 'add-tracker' into refactor-uffd-add-wp
ValentaTomas Nov 12, 2025
40029a2
Fix incorrect test label
ValentaTomas Nov 12, 2025
90259f8
Improve comments
ValentaTomas Nov 13, 2025
9705772
Enhance error handling in NewUserfaultfdFromFd by including the origi…
ValentaTomas Nov 13, 2025
8d5195b
Clarify comment
ValentaTomas Nov 13, 2025
518d5da
Update comment to clarify that passing 0 as the mode removes write pr…
ValentaTomas Nov 13, 2025
b11d70b
Merge branch 'main' into refactor-uffd-add-wp
ValentaTomas Dec 2, 2025
734a9e2
Fix merge bugs
ValentaTomas Dec 2, 2025
f00ce48
Merge branch 'main' into add-tracker
ValentaTomas Dec 3, 2025
16eaf89
Merge branch 'main' into add-tracker
ValentaTomas Dec 3, 2025
0f2ad5c
Add comment
ValentaTomas Dec 3, 2025
8dc51d0
Merge branch 'add-tracker' of https://github.com/e2b-dev/infra into a…
ValentaTomas Dec 3, 2025
770c3a3
Merge branch 'main' into refactor-uffd-add-wp
ValentaTomas Dec 3, 2025
baa5ec3
Merge branch 'add-tracker' into refactor-uffd-add-wp
ValentaTomas Dec 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/orchestrator/internal/sandbox/block/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func (BytesNotAvailableError) Error() string {

type Slicer interface {
Slice(ctx context.Context, off, length int64) ([]byte, error)
BlockSize() int64
}

type ReadonlyDevice interface {
Expand Down
93 changes: 55 additions & 38 deletions packages/orchestrator/internal/sandbox/block/tracker.go
Original file line number Diff line number Diff line change
@@ -1,66 +1,83 @@
package block

import (
"context"
"fmt"
"iter"
"sync"
"sync/atomic"

"github.com/bits-and-blooms/bitset"

"github.com/e2b-dev/infra/packages/shared/pkg/storage/header"
"github.com/e2b-dev/infra/packages/shared/pkg/utils"
)

type TrackedSliceDevice struct {
data ReadonlyDevice
blockSize int64
type Tracker struct {
b *bitset.BitSet
mu sync.RWMutex

nilTracking atomic.Bool
dirty *bitset.BitSet
dirtyMu sync.Mutex
empty []byte
blockSize int64
}

func NewTrackedSliceDevice(blockSize int64, device ReadonlyDevice) (*TrackedSliceDevice, error) {
return &TrackedSliceDevice{
data: device,
empty: make([]byte, blockSize),
func NewTracker(blockSize int64) *Tracker {
return &Tracker{
// The bitset resizes automatically based on the maximum set bit.
b: bitset.New(0),
blockSize: blockSize,
}, nil
}
}

func (t *TrackedSliceDevice) Disable() error {
size, err := t.data.Size()
if err != nil {
return fmt.Errorf("failed to get device size: %w", err)
func NewTrackerFromBitset(b *bitset.BitSet, blockSize int64) *Tracker {
return &Tracker{
b: b,
blockSize: blockSize,
}
}

func (t *Tracker) Has(off int64) bool {
t.mu.RLock()
defer t.mu.RUnlock()

t.dirty = bitset.New(uint(header.TotalBlocks(size, t.blockSize)))
// We are starting with all being dirty.
t.dirty.FlipRange(0, t.dirty.Len())
return t.b.Test(uint(header.BlockIdx(off, t.blockSize)))
}

t.nilTracking.Store(true)
func (t *Tracker) Add(off int64) {
t.mu.Lock()
defer t.mu.Unlock()

return nil
t.b.Set(uint(header.BlockIdx(off, t.blockSize)))
}

func (t *TrackedSliceDevice) Slice(ctx context.Context, off int64, length int64) ([]byte, error) {
if t.nilTracking.Load() {
t.dirtyMu.Lock()
t.dirty.Clear(uint(header.BlockIdx(off, t.blockSize)))
t.dirtyMu.Unlock()
func (t *Tracker) Reset() {
t.mu.Lock()
defer t.mu.Unlock()

return t.empty, nil
}
t.b.ClearAll()
}

// BitSet returns a clone of the bitset and the block size.
func (t *Tracker) BitSet() *bitset.BitSet {
t.mu.RLock()
defer t.mu.RUnlock()

return t.b.Clone()
}

return t.data.Slice(ctx, off, length)
func (t *Tracker) BlockSize() int64 {
return t.blockSize
}

// Return which bytes were not read since Disable.
// This effectively returns the bytes that have been requested after paused vm and are not dirty.
func (t *TrackedSliceDevice) Dirty() *bitset.BitSet {
t.dirtyMu.Lock()
defer t.dirtyMu.Unlock()
func (t *Tracker) Clone() *Tracker {
return &Tracker{
b: t.BitSet(),
blockSize: t.BlockSize(),
}
}

func (t *Tracker) Offsets() iter.Seq[int64] {
return bitsetOffsets(t.BitSet(), t.BlockSize())
}

return t.dirty.Clone()
func bitsetOffsets(b *bitset.BitSet, blockSize int64) iter.Seq[int64] {
return utils.TransformTo(b.EachSet(), func(idx uint) int64 {
return header.BlockOffset(int64(idx), blockSize)
})
}
167 changes: 167 additions & 0 deletions packages/orchestrator/internal/sandbox/block/tracker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package block

import (
"maps"
"math/rand"
"slices"
"testing"

"github.com/stretchr/testify/assert"
)

func TestTracker_AddAndHas(t *testing.T) {
const pageSize = 4096
tr := NewTracker(pageSize)

offset := int64(pageSize * 4)

// Initially should not be marked
assert.False(t, tr.Has(offset), "Expected offset %d not to be marked initially", offset)

// After adding, should be marked
tr.Add(offset)
assert.True(t, tr.Has(offset), "Expected offset %d to be marked after Add", offset)

// Other offsets should not be marked
otherOffsets := []int64{
0, pageSize, 2 * pageSize, 3 * pageSize, 5 * pageSize, 10 * pageSize,
}
for _, other := range otherOffsets {
if other == offset {
continue
}
assert.False(t, tr.Has(other), "Did not expect offset %d to be marked (only %d should be marked)", other, offset)
}
}

func TestTracker_Reset(t *testing.T) {
const pageSize = 4096
tr := NewTracker(pageSize)

offset := int64(pageSize * 4)

// Add offset and verify it's marked
tr.Add(offset)
assert.True(t, tr.Has(offset), "Expected offset %d to be marked after Add", offset)

// After reset, should not be marked
tr.Reset()
assert.False(t, tr.Has(offset), "Expected offset %d to be cleared after Reset", offset)

// Offsets that were never set should also remain unset
otherOffsets := []int64{0, pageSize, 2 * pageSize, pageSize * 10}
for _, other := range otherOffsets {
assert.False(t, tr.Has(other), "Expected offset %d to not be marked after Reset", other)
}
}

func TestTracker_MultipleOffsets(t *testing.T) {
const pageSize = 4096
tr := NewTracker(pageSize)

offsets := []int64{0, pageSize, 2 * pageSize, 10 * pageSize}

// Add multiple offsets
for _, o := range offsets {
tr.Add(o)
}

// Verify all offsets are marked
for _, o := range offsets {
assert.True(t, tr.Has(o), "Expected offset %d to be marked", o)
}

// Check offsets in between added offsets are not set
// (Offsets that aren't inside any marked block should not be marked)
nonSetOffsets := []int64{
3 * pageSize,
4 * pageSize,
5 * pageSize,
6 * pageSize,
7 * pageSize,
8 * pageSize,
9 * pageSize,
11 * pageSize,
}
for _, off := range nonSetOffsets {
assert.False(t, tr.Has(off), "Expected offset %d to not be marked (only explicit blocks added)", off)
}
}

func TestTracker_ResetClearsAll(t *testing.T) {
const pageSize = 4096
tr := NewTracker(pageSize)

offsets := []int64{0, pageSize, 2 * pageSize, 10 * pageSize}

// Add multiple offsets
for _, o := range offsets {
tr.Add(o)
}

// Reset should clear all
tr.Reset()

// Verify all offsets are cleared
for _, o := range offsets {
assert.False(t, tr.Has(o), "Expected offset %d to be cleared after Reset", o)
}
// Check unrelated offsets also not marked
moreOffsets := []int64{3 * pageSize, 7 * pageSize, 100, 4095}
for _, o := range moreOffsets {
assert.False(t, tr.Has(o), "Expected offset %d to not be marked after Reset", o)
}
}

func TestTracker_MisalignedOffset(t *testing.T) {
const pageSize = 4096
tr := NewTracker(pageSize)

// Test with misaligned offset
misalignedOffset := int64(123)
tr.Add(misalignedOffset)

// Should be set for the block containing the offset—that is, block 0 (0..4095)
assert.True(t, tr.Has(misalignedOffset), "Expected misaligned offset %d to be marked (should mark its containing block)", misalignedOffset)

// Now check that any offset in the same block is also considered marked
anotherOffsetInSameBlock := int64(1000)
assert.True(t, tr.Has(anotherOffsetInSameBlock), "Expected offset %d to be marked as in same block as %d", anotherOffsetInSameBlock, misalignedOffset)

// But not for a different block
offsetInNextBlock := int64(pageSize)
assert.False(t, tr.Has(offsetInNextBlock), "Did not expect offset %d to be marked", offsetInNextBlock)

// And not far outside any set block
offsetFar := int64(2 * pageSize)
assert.False(t, tr.Has(offsetFar), "Did not expect offset %d to be marked", offsetFar)
}

func TestTracker_Offsets(t *testing.T) {
const pageSize = 4096
tr := NewTracker(pageSize)

numOffsets := 300

offsetsMap := map[int64]struct{}{}

for range numOffsets {
select {
case <-t.Context().Done():
t.FailNow()
default:
}

base := int64(rand.Intn(121)) // 0..120
offset := base * pageSize

offsetsMap[offset] = struct{}{}
tr.Add(offset)
}

expectedOffsets := slices.Collect(maps.Keys(offsetsMap))
actualOffsets := slices.Collect(tr.Offsets())

assert.Len(t, actualOffsets, len(expectedOffsets))
assert.ElementsMatch(t, expectedOffsets, actualOffsets)
}
4 changes: 4 additions & 0 deletions packages/orchestrator/internal/sandbox/build/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,7 @@ func (n *NoDiff) CacheKey() DiffStoreKey {
func (n *NoDiff) Init(context.Context) error {
return NoDiffError{}
}

func (n *NoDiff) BlockSize() int64 {
return 0
}
4 changes: 4 additions & 0 deletions packages/orchestrator/internal/sandbox/build/local_diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,7 @@ func (b *localDiff) CacheKey() DiffStoreKey {
func (b *localDiff) Init(context.Context) error {
return nil
}

func (b *localDiff) BlockSize() int64 {
return b.blockSize
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,7 @@ func (b *StorageDiff) FileSize() (int64, error) {

return c.FileSize()
}

func (b *StorageDiff) BlockSize() int64 {
return b.blockSize
}
14 changes: 9 additions & 5 deletions packages/orchestrator/internal/sandbox/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ func (s *Sandbox) Shutdown(ctx context.Context) error {
return fmt.Errorf("failed to pause VM: %w", err)
}

if err := s.memory.Disable(); err != nil {
if _, err := s.memory.Disable(ctx); err != nil {
return fmt.Errorf("failed to disable uffd: %w", err)
}

Expand Down Expand Up @@ -750,8 +750,12 @@ func (s *Sandbox) Pause(
return nil, fmt.Errorf("failed to pause VM: %w", err)
}

if err := s.memory.Disable(); err != nil {
return nil, fmt.Errorf("failed to disable uffd: %w", err)
// This disables the uffd and returns the dirty pages.
// With FC async io engine, there can be some further writes to the memory during the actual create snapshot process,
// but as we are still including even read pages as dirty so this should not introduce more bugs right now.
dirty, err := s.memory.Disable(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get dirty pages: %w", err)
}

// Snapfile is not closed as it's returned and cached for later use (like resume)
Expand Down Expand Up @@ -798,7 +802,7 @@ func (s *Sandbox) Pause(
originalMemfile.Header(),
&MemoryDiffCreator{
memfile: memfile,
dirtyPages: s.memory.Dirty(),
dirtyPages: dirty.BitSet(),
blockSize: originalMemfile.BlockSize(),
doneHook: func(context.Context) error {
return memfile.Close()
Expand Down Expand Up @@ -979,7 +983,7 @@ func serveMemory(
ctx, span := tracer.Start(ctx, "serve-memory")
defer span.End()

fcUffd, err := uffd.New(memfile, socketPath, memfile.BlockSize())
fcUffd, err := uffd.New(memfile, socketPath)
if err != nil {
return nil, fmt.Errorf("failed to create uffd: %w", err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"sync"
)

var ErrFdExit = errors.New("fd exit signal")

// FdExit is a wrapper around a pipe that allows to signal the exit of the uffd.
type FdExit struct {
r *os.File
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ func NewMapping(regions []Region) *Mapping {
}

// GetOffset returns the relative offset and the page size of the mapped range for a given address.
func (m *Mapping) GetOffset(hostVirtAddr uintptr) (int64, uint64, error) {
func (m *Mapping) GetOffset(hostVirtAddr uintptr) (int64, uintptr, error) {
for _, r := range m.Regions {
if hostVirtAddr >= r.BaseHostVirtAddr && hostVirtAddr < r.endHostVirtAddr() {
return r.shiftedOffset(hostVirtAddr), uint64(r.PageSize), nil
return r.shiftedOffset(hostVirtAddr), r.PageSize, nil
}
}

Expand Down
Loading