Skip to content

Commit 2ece490

Browse files
committed
Fix crashes when object refreshing is performed with persistency
Calling EpochList.GetCurrentEpochState() from within the read code path is problematic, because we might not even have an epoch. You could also argue that refreshing doesn't necessarily need to be based on the state of epochs, but more on the offset tracked by LocationBlobMap. LocationBlobMap's offset could be far ahead of that of the epoch lists. Because LocationBlobMap is unaware of a 'minimum location', invert the logic we have right now to calculate backwards.
1 parent 174b37b commit 2ece490

6 files changed

Lines changed: 31 additions & 22 deletions

File tree

pkg/storage/object/local/block_device_backed_location_blob_map.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type blockDeviceBackedLocationBlobMap struct {
1111
sectorSizeBytes int
1212
sectorCount int64
1313

14-
lock sync.Mutex
14+
lock sync.RWMutex
1515
nextLocation uint64
1616
sharedSector *sharedSector
1717
}
@@ -156,6 +156,12 @@ func (lbm *blockDeviceBackedLocationBlobMap) Put(data []byte) (uint64, error) {
156156
return location, nil
157157
}
158158

159+
func (lbm *blockDeviceBackedLocationBlobMap) GetNextPutLocation() uint64 {
160+
lbm.lock.RLock()
161+
defer lbm.lock.RUnlock()
162+
return lbm.nextLocation
163+
}
164+
159165
// sharedSector contains the bookkeeping of a single sector of storage
160166
// that stores data belonging to more than one object. Calls to
161167
// WriteAt() against such a sector must be synchronized, so that

pkg/storage/object/local/configuration.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,15 +188,15 @@ func NewStoreFromConfiguration(terminationGroup program.Group, configuration *co
188188
return nil, status.Error(codes.InvalidArgument, "New region size ratio must be positive")
189189
}
190190
totalRatio := uint64(oldRegionSizeRatio + currentRegionSizeRatio + newRegionSizeRatio)
191-
oldRegionSizeBytes := maximumLocationSpan * uint64(oldRegionSizeRatio) / totalRatio
192191
currentRegionSizeBytes := maximumLocationSpan * uint64(currentRegionSizeRatio) / totalRatio
192+
newRegionSizeBytes := maximumLocationSpan * uint64(newRegionSizeRatio) / totalRatio
193193

194194
return NewStore(
195195
&globalLock,
196196
referenceLocationMap,
197197
locationBlobMap,
198198
epochList,
199-
oldRegionSizeBytes,
200199
currentRegionSizeBytes,
200+
newRegionSizeBytes,
201201
), nil
202202
}

pkg/storage/object/local/in_memory_location_blob_map.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,7 @@ func (lbm *inMemoryLocationBlobMap) Put(data []byte) (uint64, error) {
6262
}
6363
return location, nil
6464
}
65+
66+
func (lbm *inMemoryLocationBlobMap) GetNextPutLocation() uint64 {
67+
return lbm.nextLocation.Load()
68+
}

pkg/storage/object/local/location_blob_map.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
type LocationBlobMap interface {
1414
Get(location uint64, sizeBytes int) ([]byte, error)
1515
Put([]byte) (uint64, error)
16+
GetNextPutLocation() uint64
1617
}
1718

1819
// allocateLocation is a helper function for allocating a location for

pkg/storage/object/local/store.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import (
1212
)
1313

1414
type store struct {
15-
oldRegionSizeBytes uint64
1615
currentRegionSizeBytes uint64
16+
newRegionSizeBytes uint64
1717

1818
lock *sync.RWMutex
1919
referenceLocationMap ReferenceLocationMap
@@ -30,25 +30,24 @@ func NewStore(
3030
referenceLocationMap ReferenceLocationMap,
3131
locationBlobMap LocationBlobMap,
3232
epochList EpochList,
33-
oldRegionSizeBytes uint64,
3433
currentRegionSizeBytes uint64,
34+
newRegionSizeBytes uint64,
3535
) object.Store[object.FlatReference, struct{}] {
3636
return &store{
3737
lock: lock,
3838
referenceLocationMap: referenceLocationMap,
3939
locationBlobMap: locationBlobMap,
4040
epochList: epochList,
4141

42-
oldRegionSizeBytes: oldRegionSizeBytes,
4342
currentRegionSizeBytes: currentRegionSizeBytes,
43+
newRegionSizeBytes: newRegionSizeBytes,
4444
}
4545
}
4646

4747
func (s *store) getObjectLocation(reference object.FlatReference) (uint64, bool, error) {
4848
s.lock.RLock()
49-
defer s.lock.RUnlock()
50-
5149
location, err := s.referenceLocationMap.Get(reference, s.epochList)
50+
s.lock.RUnlock()
5251
if err != nil {
5352
return 0, false, err
5453
}
@@ -58,17 +57,16 @@ func (s *store) getObjectLocation(reference object.FlatReference) (uint64, bool,
5857
// deterministic refresh threshold within the current region,
5958
// derived from the object's reference. This spreads refresh
6059
// operations evenly across all objects.
61-
epochState, _ := s.epochList.GetCurrentEpochState()
62-
distanceFromMinimum := location - epochState.MinimumLocation
60+
distanceFromMaximum := int64(s.locationBlobMap.GetNextPutLocation() - location)
6361

6462
// Compute a deterministic threshold for this object within the
65-
// current region. Objects whose distance from minimum falls
66-
// below (oldRegion + threshold) need to be refreshed.
63+
// current region. Objects whose distance from minimum goes
64+
// above (newRegionSizeBytes + threshold) need to be refreshed.
6765
// XOR with location ensures the threshold changes each time the
6866
// object is relocated, preventing the same objects from always
6967
// being refreshed earlier than others.
7068
threshold := (location ^ binary.LittleEndian.Uint64(reference.GetRawFlatReference())) % s.currentRegionSizeBytes
71-
needsRefresh := distanceFromMinimum < s.oldRegionSizeBytes+threshold
69+
needsRefresh := distanceFromMaximum > int64(s.newRegionSizeBytes+threshold)
7270
return location, needsRefresh, nil
7371
}
7472

pkg/storage/object/local/store_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
)
1717

1818
// newTestStore creates a store with real in-memory implementations for testing.
19-
func newTestStore(t *testing.T, bufferSize, oldRegionSize, currentRegionSize uint64) (object.Store[object.FlatReference, struct{}], object_local.EpochList) {
19+
func newTestStore(t *testing.T, bufferSize, currentRegionSizeBytes, newRegionSizeBytes uint64) (object.Store[object.FlatReference, struct{}], object_local.EpochList) {
2020
var lock sync.RWMutex
2121
randomNumberGenerator := random.NewFastSingleThreadedGenerator()
2222
epochList := object_local.NewVolatileEpochList(bufferSize, randomNumberGenerator)
@@ -47,8 +47,8 @@ func newTestStore(t *testing.T, bufferSize, oldRegionSize, currentRegionSize uin
4747
referenceLocationMap,
4848
locationBlobMap,
4949
epochList,
50-
oldRegionSize,
51-
currentRegionSize,
50+
currentRegionSizeBytes,
51+
newRegionSizeBytes,
5252
)
5353

5454
return store, epochList
@@ -58,12 +58,12 @@ func TestStoreRefreshObjectInOldRegion(t *testing.T) {
5858
// This test verifies that objects in the "old" region get refreshed
5959
// (copied to the end of the buffer) when accessed, preventing them
6060
// from being overwritten by the write cursor.
61-
62-
const bufferSize = 1000
63-
const oldRegionSize = 200
64-
const currentRegionSize = 300
65-
66-
store, epochList := newTestStore(t, bufferSize, oldRegionSize, currentRegionSize)
61+
store, epochList := newTestStore(
62+
t,
63+
/* bufferSizeBytes = */ 1000,
64+
/* currentRegionSize = */ 300,
65+
/* newRegionSizeBytes = */ 200,
66+
)
6767
ctx := context.Background()
6868

6969
// SHA256("Hello") = 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969

0 commit comments

Comments
 (0)