Add element type param T to SlicedBacked[T]. Require T satisfy

constraints.Ordered to make BisectRight() statically type-safe.
This commit is contained in:
Jonathan Moody 2022-08-30 14:16:35 -05:00
parent 9d9c73f97f
commit 8ac89195db
5 changed files with 55 additions and 42 deletions

View file

@ -48,10 +48,10 @@ type ReadOnlyDBColumnFamily struct {
DB *grocksdb.DB DB *grocksdb.DB
Handles map[string]*grocksdb.ColumnFamilyHandle Handles map[string]*grocksdb.ColumnFamilyHandle
Opts *grocksdb.ReadOptions Opts *grocksdb.ReadOptions
TxCounts *stack.SliceBacked TxCounts *stack.SliceBacked[uint32]
Height uint32 Height uint32
LastState *prefixes.DBStateValue LastState *prefixes.DBStateValue
Headers *stack.SliceBacked Headers *stack.SliceBacked[[]byte]
BlockingChannelHashes [][]byte BlockingChannelHashes [][]byte
FilteringChannelHashes [][]byte FilteringChannelHashes [][]byte
BlockedStreams map[string][]byte BlockedStreams map[string][]byte
@ -556,7 +556,7 @@ func (db *ReadOnlyDBColumnFamily) Advance(height uint32) {
} }
txCount := txCountObj.TxCount txCount := txCountObj.TxCount
if db.TxCounts.GetTip().(uint32) >= txCount { if db.TxCounts.GetTip() >= txCount {
log.Error("current tip should be less than new txCount", log.Error("current tip should be less than new txCount",
"tx count tip:", db.TxCounts.GetTip(), "tx count:", txCount) "tx count tip:", db.TxCounts.GetTip(), "tx count:", txCount)
} }
@ -636,11 +636,10 @@ func (db *ReadOnlyDBColumnFamily) detectChanges(notifCh chan *internal.HeightHas
if err != nil { if err != nil {
return err return err
} }
curHeaderObj := db.Headers.GetTip() curHeader := db.Headers.GetTip()
if curHeaderObj == nil { if curHeader == nil {
break break
} }
curHeader := curHeaderObj.([]byte)
log.Debugln("lastHeightHeader: ", hex.EncodeToString(lastHeightHeader)) log.Debugln("lastHeightHeader: ", hex.EncodeToString(lastHeightHeader))
log.Debugln("curHeader: ", hex.EncodeToString(curHeader)) log.Debugln("curHeader: ", hex.EncodeToString(curHeader))
if bytes.Equal(curHeader, lastHeightHeader) { if bytes.Equal(curHeader, lastHeightHeader) {
@ -716,7 +715,7 @@ func (db *ReadOnlyDBColumnFamily) InitHeaders() error {
} }
//TODO: figure out a reasonable default and make it a constant //TODO: figure out a reasonable default and make it a constant
db.Headers = stack.NewSliceBacked(12000) db.Headers = stack.NewSliceBacked[[]byte](12000)
startKey := prefixes.NewHeaderKey(0) startKey := prefixes.NewHeaderKey(0)
// endKey := prefixes.NewHeaderKey(db.LastState.Height) // endKey := prefixes.NewHeaderKey(db.LastState.Height)
@ -743,7 +742,7 @@ func (db *ReadOnlyDBColumnFamily) InitTxCounts() error {
return err return err
} }
db.TxCounts = stack.NewSliceBacked(InitialTxCountSize) db.TxCounts = stack.NewSliceBacked[uint32](InitialTxCountSize)
options := NewIterateOptions().WithPrefix([]byte{prefixes.TxCount}).WithCfHandle(handle) options := NewIterateOptions().WithPrefix([]byte{prefixes.TxCount}).WithCfHandle(handle)
options = options.WithIncludeKey(false).WithIncludeValue(true).WithIncludeStop(true) options = options.WithIncludeKey(false).WithIncludeValue(true).WithIncludeStop(true)

View file

@ -11,6 +11,7 @@ import (
"strings" "strings"
"github.com/lbryio/herald.go/db/prefixes" "github.com/lbryio/herald.go/db/prefixes"
"github.com/lbryio/herald.go/db/stack"
"github.com/lbryio/herald.go/internal" "github.com/lbryio/herald.go/internal"
pb "github.com/lbryio/herald.go/protobuf/go" pb "github.com/lbryio/herald.go/protobuf/go"
lbryurl "github.com/lbryio/lbry.go/v3/url" lbryurl "github.com/lbryio/lbry.go/v3/url"
@ -40,7 +41,8 @@ func PrepareResolveResult(
return nil, err return nil, err
} }
height, createdHeight := db.TxCounts.TxCountsBisectRight(txNum, rootTxNum) heights := stack.BisectRight(db.TxCounts, []uint32{txNum, rootTxNum})
height, createdHeight := heights[0], heights[1]
lastTakeoverHeight := controllingClaim.Height lastTakeoverHeight := controllingClaim.Height
expirationHeight := GetExpirationHeight(height) expirationHeight := GetExpirationHeight(height)
@ -86,7 +88,7 @@ func PrepareResolveResult(
return nil, err return nil, err
} }
repostTxPostition = repostTxo.Position repostTxPostition = repostTxo.Position
repostHeight, _ = db.TxCounts.TxCountsBisectRight(repostTxo.TxNum, rootTxNum) repostHeight = stack.BisectRight(db.TxCounts, []uint32{repostTxo.TxNum})[0]
} }
} }
@ -122,7 +124,7 @@ func PrepareResolveResult(
return nil, err return nil, err
} }
channelTxPostition = channelVals.Position channelTxPostition = channelVals.Position
channelHeight, _ = db.TxCounts.TxCountsBisectRight(channelVals.TxNum, rootTxNum) channelHeight = stack.BisectRight(db.TxCounts, []uint32{channelVals.TxNum})[0]
} }
} }

View file

@ -7,23 +7,24 @@ import (
"sync" "sync"
"github.com/lbryio/herald.go/internal" "github.com/lbryio/herald.go/internal"
"golang.org/x/exp/constraints"
) )
type SliceBacked struct { type SliceBacked[T any] struct {
slice []interface{} slice []T
len uint32 len uint32
mut sync.RWMutex mut sync.RWMutex
} }
func NewSliceBacked(size int) *SliceBacked { func NewSliceBacked[T any](size int) *SliceBacked[T] {
return &SliceBacked{ return &SliceBacked[T]{
slice: make([]interface{}, size), slice: make([]T, size),
len: 0, len: 0,
mut: sync.RWMutex{}, mut: sync.RWMutex{},
} }
} }
func (s *SliceBacked) Push(v interface{}) { func (s *SliceBacked[T]) Push(v T) {
s.mut.Lock() s.mut.Lock()
defer s.mut.Unlock() defer s.mut.Unlock()
@ -35,64 +36,67 @@ func (s *SliceBacked) Push(v interface{}) {
s.len++ s.len++
} }
func (s *SliceBacked) Pop() interface{} { func (s *SliceBacked[T]) Pop() T {
s.mut.Lock() s.mut.Lock()
defer s.mut.Unlock() defer s.mut.Unlock()
if s.len == 0 { if s.len == 0 {
return nil var null T
return null
} }
s.len-- s.len--
return s.slice[s.len] return s.slice[s.len]
} }
func (s *SliceBacked) Get(i uint32) interface{} { func (s *SliceBacked[T]) Get(i uint32) T {
s.mut.RLock() s.mut.RLock()
defer s.mut.RUnlock() defer s.mut.RUnlock()
if i >= s.len { if i >= s.len {
return nil var null T
return null
} }
return s.slice[i] return s.slice[i]
} }
func (s *SliceBacked) GetTip() interface{} { func (s *SliceBacked[T]) GetTip() T {
s.mut.RLock() s.mut.RLock()
defer s.mut.RUnlock() defer s.mut.RUnlock()
if s.len == 0 { if s.len == 0 {
return nil var null T
return null
} }
return s.slice[s.len-1] return s.slice[s.len-1]
} }
func (s *SliceBacked) Len() uint32 { func (s *SliceBacked[T]) Len() uint32 {
s.mut.RLock() s.mut.RLock()
defer s.mut.RUnlock() defer s.mut.RUnlock()
return s.len return s.len
} }
func (s *SliceBacked) Cap() int { func (s *SliceBacked[T]) Cap() int {
s.mut.RLock() s.mut.RLock()
defer s.mut.RUnlock() defer s.mut.RUnlock()
return cap(s.slice) return cap(s.slice)
} }
func (s *SliceBacked) GetSlice() []interface{} { func (s *SliceBacked[T]) GetSlice() []T {
// This is not thread safe so I won't bother with locking // This is not thread safe so I won't bother with locking
return s.slice return s.slice
} }
// This function is dangerous because it assumes underlying types func BisectRight[T constraints.Ordered](s *SliceBacked[T], searchKeys []T) []uint32 {
func (s *SliceBacked) TxCountsBisectRight(txNum, rootTxNum uint32) (uint32, uint32) {
s.mut.RLock() s.mut.RLock()
defer s.mut.RUnlock() defer s.mut.RUnlock()
txCounts := s.slice[:s.Len()] found := make([]uint32, len(searchKeys))
height := internal.BisectRight(txCounts, txNum) for i, k := range searchKeys {
createdHeight := internal.BisectRight(txCounts, rootTxNum) found[i] = internal.BisectRight(s.slice[:s.Len()], k)
}
return height, createdHeight
return found
} }

View file

@ -10,7 +10,7 @@ import (
func TestPush(t *testing.T) { func TestPush(t *testing.T) {
var want uint32 = 3 var want uint32 = 3
stack := stack.NewSliceBacked(10) stack := stack.NewSliceBacked[int](10)
stack.Push(0) stack.Push(0)
stack.Push(1) stack.Push(1)
@ -22,7 +22,7 @@ func TestPush(t *testing.T) {
} }
func TestPushPop(t *testing.T) { func TestPushPop(t *testing.T) {
stack := stack.NewSliceBacked(10) stack := stack.NewSliceBacked[int](10)
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
stack.Push(i) stack.Push(i)
@ -46,20 +46,20 @@ func TestPushPop(t *testing.T) {
} }
} }
func doPushes(stack *stack.SliceBacked, numPushes int) { func doPushes(stack *stack.SliceBacked[int], numPushes int) {
for i := 0; i < numPushes; i++ { for i := 0; i < numPushes; i++ {
stack.Push(i) stack.Push(i)
} }
} }
func doPops(stack *stack.SliceBacked, numPops int) { func doPops(stack *stack.SliceBacked[int], numPops int) {
for i := 0; i < numPops; i++ { for i := 0; i < numPops; i++ {
stack.Pop() stack.Pop()
} }
} }
func TestMultiThreaded(t *testing.T) { func TestMultiThreaded(t *testing.T) {
stack := stack.NewSliceBacked(100000) stack := stack.NewSliceBacked[int](100000)
go doPushes(stack, 100000) go doPushes(stack, 100000)
go doPushes(stack, 100000) go doPushes(stack, 100000)
@ -83,7 +83,7 @@ func TestMultiThreaded(t *testing.T) {
} }
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
stack := stack.NewSliceBacked(10) stack := stack.NewSliceBacked[int](10)
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
stack.Push(i) stack.Push(i)
@ -99,6 +99,10 @@ func TestGet(t *testing.T) {
} }
} }
if got := stack.Get(5); got != 0 {
t.Errorf("got %v, want %v", got, 0)
}
slice := stack.GetSlice() slice := stack.GetSlice()
if len(slice) != 10 { if len(slice) != 10 {
@ -107,7 +111,7 @@ func TestGet(t *testing.T) {
} }
func TestLenCap(t *testing.T) { func TestLenCap(t *testing.T) {
stack := stack.NewSliceBacked(10) stack := stack.NewSliceBacked[int](10)
if got := stack.Len(); got != 0 { if got := stack.Len(); got != 0 {
t.Errorf("got %v, want %v", got, 0) t.Errorf("got %v, want %v", got, 0)

View file

@ -1,10 +1,14 @@
package internal package internal
import "sort" import (
"sort"
"golang.org/x/exp/constraints"
)
// BisectRight returns the index of the first element in the list that is greater than or equal to the value. // BisectRight returns the index of the first element in the list that is greater than or equal to the value.
// https://stackoverflow.com/questions/29959506/is-there-a-go-analog-of-pythons-bisect-module // https://stackoverflow.com/questions/29959506/is-there-a-go-analog-of-pythons-bisect-module
func BisectRight(arr []interface{}, val uint32) uint32 { func BisectRight[T constraints.Ordered](arr []T, val T) uint32 {
i := sort.Search(len(arr), func(i int) bool { return arr[i].(uint32) >= val }) i := sort.Search(len(arr), func(i int) bool { return arr[i] >= val })
return uint32(i) return uint32(i)
} }