mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-10-06 19:07:50 +00:00
fix remote available shard races (#3)
This commit is contained in:
parent
046b98bdf9
commit
26b49cca21
2 changed files with 65 additions and 63 deletions
114
field.go
114
field.go
|
|
@ -108,7 +108,8 @@ type Field struct {
|
|||
bsiGroups []*bsiGroup
|
||||
|
||||
// Shards with data on any node in the cluster, according to this node.
|
||||
remoteAvailableShards *roaring.Bitmap
|
||||
remoteAvailableShardsMu sync.Mutex
|
||||
remoteAvailableShards *roaring.Bitmap
|
||||
|
||||
translateStore TranslateStore
|
||||
|
||||
|
|
@ -126,8 +127,7 @@ type Field struct {
|
|||
|
||||
// Synchronization primitives needed for async writing of
|
||||
// the remoteAvailableShards
|
||||
availableShardChan chan *roaring.Bitmap
|
||||
doneChan chan struct{}
|
||||
availableShardChan chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
|
|
@ -429,6 +429,8 @@ func (f *Field) RowAttrStore() AttrStore { return f.rowAttrStore }
|
|||
func (f *Field) AvailableShards(localOnly bool) *roaring.Bitmap {
|
||||
f.mu.RLock()
|
||||
defer f.mu.RUnlock()
|
||||
f.remoteAvailableShardsMu.Lock()
|
||||
defer f.remoteAvailableShardsMu.Unlock()
|
||||
|
||||
var b *roaring.Bitmap
|
||||
if localOnly {
|
||||
|
|
@ -466,8 +468,8 @@ func (f *Field) AddRemoteAvailableShards(b *roaring.Bitmap) error {
|
|||
|
||||
// mergeRemoteAvailableShards merges the set of available shards into the current known set.
|
||||
func (f *Field) mergeRemoteAvailableShards(b *roaring.Bitmap) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.remoteAvailableShardsMu.Lock()
|
||||
defer f.remoteAvailableShardsMu.Unlock()
|
||||
f.remoteAvailableShards = f.remoteAvailableShards.Union(b)
|
||||
}
|
||||
|
||||
|
|
@ -486,14 +488,10 @@ func (f *Field) loadAvailableShards() error {
|
|||
|
||||
// saveAvailableShards writes remoteAvailableShards data for the field.
|
||||
func (f *Field) saveAvailableShards() error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return f.unprotectedSaveAvailableShards()
|
||||
}
|
||||
|
||||
func (f *Field) unprotectedSaveAvailableShards() error {
|
||||
f.remoteAvailableShards.Optimize()
|
||||
f.availableShardChan <- f.remoteAvailableShards
|
||||
select {
|
||||
case f.availableShardChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -501,8 +499,8 @@ func (f *Field) unprotectedSaveAvailableShards() error {
|
|||
//
|
||||
// NOTE: This can be overridden on the next sync so all nodes should be updated.
|
||||
func (f *Field) RemoveAvailableShard(v uint64) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.remoteAvailableShardsMu.Lock()
|
||||
defer f.remoteAvailableShardsMu.Unlock()
|
||||
|
||||
b := f.remoteAvailableShards.Clone()
|
||||
if _, err := b.Remove(v); err != nil {
|
||||
|
|
@ -510,7 +508,7 @@ func (f *Field) RemoveAvailableShard(v uint64) error {
|
|||
}
|
||||
f.remoteAvailableShards = b
|
||||
|
||||
return f.unprotectedSaveAvailableShards()
|
||||
return f.saveAvailableShards()
|
||||
}
|
||||
|
||||
// Type returns the field type.
|
||||
|
|
@ -579,8 +577,7 @@ func (f *Field) Open() error {
|
|||
}
|
||||
}
|
||||
|
||||
f.availableShardChan = make(chan *roaring.Bitmap)
|
||||
f.doneChan = make(chan struct{})
|
||||
f.availableShardChan = make(chan struct{}, 1)
|
||||
f.wg.Add(1)
|
||||
go f.writeAvailableShards()
|
||||
return nil
|
||||
|
|
@ -594,55 +591,56 @@ func (f *Field) Open() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (f *Field) blockingWriteAvailableShards(availableShards *roaring.Bitmap) {
|
||||
err := f.holder.sharder.SetShards(context.Background(), f.index, f.name, availableShards)
|
||||
func (f *Field) protectedRemoteAvailableShards() *roaring.Bitmap {
|
||||
f.remoteAvailableShardsMu.Lock()
|
||||
defer f.remoteAvailableShardsMu.Unlock()
|
||||
|
||||
f.remoteAvailableShards.Optimize()
|
||||
return f.remoteAvailableShards.Clone()
|
||||
}
|
||||
|
||||
func (f *Field) flushAvailableShards(ctx context.Context) {
|
||||
shards := f.protectedRemoteAvailableShards()
|
||||
err := f.holder.sharder.SetShards(ctx, f.index, f.name, shards)
|
||||
if err != nil {
|
||||
f.holder.Logger.Errorf("writting available shards: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Field) nonBlockingWriteAvailableShards(availableShards *roaring.Bitmap, done chan bool) {
|
||||
if availableShards == nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
f.blockingWriteAvailableShards(availableShards)
|
||||
done <- true
|
||||
}()
|
||||
}
|
||||
|
||||
func (f *Field) writeAvailableShards() {
|
||||
defer f.wg.Done()
|
||||
ticker := time.NewTicker(availableShardFileFlushDuration.Get())
|
||||
var data *roaring.Bitmap
|
||||
tracker := make(chan bool)
|
||||
writing := false
|
||||
|
||||
for alive := true; alive; {
|
||||
select {
|
||||
case newdata := <-f.availableShardChan:
|
||||
data = newdata
|
||||
case <-ticker.C:
|
||||
if data != nil {
|
||||
if !writing {
|
||||
writing = true
|
||||
f.nonBlockingWriteAvailableShards(data, tracker)
|
||||
data = nil
|
||||
interval := availableShardFileFlushDuration.Get()
|
||||
timer := time.NewTimer(interval)
|
||||
defer timer.Stop()
|
||||
|
||||
for range f.availableShardChan {
|
||||
// Available shards have been updated.
|
||||
|
||||
// Wait a bit so that we batch writes.
|
||||
timerWait:
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-f.availableShardChan:
|
||||
if !ok {
|
||||
// The server is shutting down.
|
||||
// Do the write immediately.
|
||||
timer.Stop()
|
||||
break timerWait
|
||||
}
|
||||
|
||||
case <-timer.C:
|
||||
// We have waited long enough.
|
||||
break timerWait
|
||||
}
|
||||
case <-tracker:
|
||||
writing = false
|
||||
case <-f.doneChan:
|
||||
if writing { //wait to writing is complete
|
||||
<-tracker
|
||||
}
|
||||
if data != nil {
|
||||
f.blockingWriteAvailableShards(data)
|
||||
}
|
||||
alive = false
|
||||
}
|
||||
|
||||
// Set the timer for the next flush.
|
||||
timer.Reset(interval)
|
||||
|
||||
// Actually write the shards.
|
||||
f.flushAvailableShards(context.Background())
|
||||
}
|
||||
ticker.Stop()
|
||||
}
|
||||
|
||||
// applyTranslateStore opens the configured translate store.
|
||||
|
|
@ -848,12 +846,10 @@ func (f *Field) Close() error {
|
|||
_ = testhook.Closed(f.holder.Auditor, f, nil)
|
||||
}()
|
||||
// Shutdown the available shards writer
|
||||
if f.doneChan != nil {
|
||||
close(f.doneChan)
|
||||
f.wg.Wait()
|
||||
if f.availableShardChan != nil {
|
||||
close(f.availableShardChan)
|
||||
f.wg.Wait()
|
||||
f.availableShardChan = nil
|
||||
f.doneChan = nil
|
||||
}
|
||||
// Close the attribute store.
|
||||
if f.rowAttrStore != nil {
|
||||
|
|
|
|||
|
|
@ -389,12 +389,14 @@ func TestField_PersistAvailableShards(t *testing.T) {
|
|||
// Reload field and verify that shard data is persisted.
|
||||
if err := f.Reopen(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !reflect.DeepEqual(f.remoteAvailableShards.Slice(), bm.Slice()) {
|
||||
t.Fatalf("unexpected available shards (reopen). expected: %v, but got: %v", bm.Slice(), f.remoteAvailableShards.Slice())
|
||||
} else if !reflect.DeepEqual(f.protectedRemoteAvailableShards().Slice(), bm.Slice()) {
|
||||
t.Fatalf("unexpected available shards (reopen). expected: %v, but got: %v", bm.Slice(), f.protectedRemoteAvailableShards().Slice())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
// This test is wrong. I don't understand what this is supposed to be doing.
|
||||
func TestField_TruncatedAvailableShards(t *testing.T) {
|
||||
availableShardFileFlushDuration.Set(200 * time.Millisecond) //shorten the default time to force a file write
|
||||
f := OpenField(t, OptFieldTypeDefault())
|
||||
|
|
@ -409,11 +411,14 @@ func TestField_TruncatedAvailableShards(t *testing.T) {
|
|||
time.Sleep(2 * availableShardFileFlushDuration.Get())
|
||||
f.remoteAvailableShards = roaring.NewBitmap()
|
||||
|
||||
if !reflect.DeepEqual(f.remoteAvailableShards.Slice(), []uint64(nil)) {
|
||||
t.Fatalf("unexpected available shards (reopen). expected: %#v, but got: %#v", []uint64{}, f.remoteAvailableShards.Slice())
|
||||
if !reflect.DeepEqual(f.protectedRemoteAvailableShards().Slice(), []uint64(nil)) {
|
||||
t.Fatalf("unexpected available shards (reopen). expected: %#v, but got: %#v", []uint64{}, f.protectedRemoteAvailableShards().Slice())
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// This test is also wrong. It just unions the thing instead of testing anything.
|
||||
// Ensure that persisting available shards having a smaller footprint (for example,
|
||||
// when going from a bitmap to a smaller, RLE representation) succeeds.
|
||||
func TestField_PersistAvailableShardsFootprint(t *testing.T) {
|
||||
|
|
@ -462,6 +467,7 @@ func TestField_PersistAvailableShardsFootprint(t *testing.T) {
|
|||
t.Fatalf("unexpected available shards (reload). expected: %v, but got: %v", bm.Slice(), f.remoteAvailableShards.Slice())
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Ensure that FieldOptions.Base defaults to the correct value.
|
||||
func TestBSIGroup_BaseDefaultValue(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue