Skip to content
Snippets Groups Projects
Commit 71e370da authored by Josh Brooks's avatar Josh Brooks
Browse files

made node states into current.Activities and addes states as

round states
parents
Branches
Tags
No related merge requests found
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2020 Privategrity Corporation /
// /
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package ring
/*
* The RingBuffer data structure is used to store information on rounds and updates
* It functions like a typical Circluar buffer, with some slight modifications
* First, it is made generic by using interface{} instead of a defined type
* Second, it requires an id function to be passed in which gets an ID from whatever the underlying object is
* Finally, it allows for manipulation of data using both normal indeces and ID values as counters
*/
import (
"github.com/pkg/errors"
"math"
"sync"
)
// These function types are passed into ringbuff, allowing us to make it semi-generic
type idFunc func(interface{}) int
type compFunc func(interface{}, interface{}) bool
// A circular buffer with the ability to use IDs as position and locks built in
type Buff struct {
buff []interface{}
count, old, new int
id idFunc
sync.RWMutex
}
// Initialize a new ring buffer with length n
func NewBuff(n int, id idFunc) *Buff {
rb := &Buff{
buff: make([]interface{}, n),
count: n,
old: -1,
new: 0,
id: id,
}
return rb
}
// Get the ID of the newest item in the buffer
func (rb *Buff) GetNewestId() int {
rb.RLock()
defer rb.RUnlock()
return rb.getNewestId()
}
// Get the IDof the oldest item in the buffer
func (rb *Buff) GetOldestId() int {
rb.RLock()
defer rb.RUnlock()
return rb.getOldestId()
}
// Get the index of the oldest item in the buffer, not including nil values inserted by UpsertById
func (rb *Buff) GetOldestIndex() int {
rb.RLock()
defer rb.RUnlock()
return rb.getOldestIndex()
}
// Push a round to the buffer
func (rb *Buff) Push(val interface{}) {
rb.Lock()
defer rb.Unlock()
rb.push(val)
}
// push a round to a relative index in the buffer
func (rb *Buff) UpsertById(val interface{}, comp compFunc) error {
rb.Lock()
defer rb.Unlock()
// Make sure the id isn't too old
newId := rb.id(val)
if rb.old != -1 && rb.id(rb.buff[rb.old]) > newId {
return errors.Errorf("Did not upsert value %+v; id is older than first tracked", val)
}
// Get most recent ID so we can figure out where to put this
mostRecentIndex := (rb.new + rb.count - 1) % rb.count
mostRecentId := rb.id(rb.buff[mostRecentIndex])
if mostRecentId+1 == newId {
// last id is the previous one; we can just push
rb.push(val)
} else if (mostRecentId + 1) < newId {
// there are id's between the last and the current; increment using dummy entries
for i := mostRecentId + 1; i < newId; i++ {
rb.push(nil)
}
rb.push(val)
} else if mostRecentId+1 > newId {
// this is an old ID, check the comp function and insert if true
i := rb.getIndex(newId - (mostRecentId + 1))
if comp(rb.buff[i], val) {
rb.buff[i] = val
} else {
return errors.Errorf("Did not upsert value %+v; comp function returned false", val)
}
}
return nil
}
// Retreive the most recent entry
func (rb *Buff) Get() interface{} {
rb.RLock()
defer rb.RUnlock()
mostRecentIndex := (rb.new + rb.count - 1) % rb.count
return rb.buff[mostRecentIndex]
}
// Retrieve an entry with the given ID
func (rb *Buff) GetById(id int) (interface{}, error) {
rb.RLock()
defer rb.RUnlock()
// Check it's not before our first known id
firstId := rb.id(rb.buff[rb.getOldestIndex()])
if id < firstId {
return nil, errors.Errorf("requested ID %d is lower than oldest id %d", id, firstId)
}
// Check it's not after our last known id
lastId := rb.id(rb.Get())
if id > lastId {
return nil, errors.Errorf("requested id %d is higher than most recent id %d", id, lastId)
}
index := rb.getIndex(id - lastId - 1)
return rb.buff[index], nil
}
// Retrieve an entry at the given index
func (rb *Buff) GetByIndex(i int) (interface{}, error) {
rb.RLock()
defer rb.RUnlock()
if math.Abs(float64(i)) > float64(rb.Len()) { // FIXME: this shouldn't be float but we don't have a package where it's not float
return nil, errors.Errorf("Could not get item at index %d: index out of bounds", i)
}
return rb.buff[rb.getIndex(i)], nil
}
//retrieve all entries newer than the passed one
func (rb *Buff) GetNewerById(id int) ([]interface{}, error) {
rb.RLock()
defer rb.RUnlock()
new := rb.getNewestId()
old := rb.getOldestId()
if id < old {
id = old - 1
}
if id > new {
return nil, errors.Errorf("requested ID %d is higher than the"+
" newest id %d", id, new)
}
numNewer := new - id
list := make([]interface{}, numNewer)
for i := 0; i < numNewer; i++ {
//error is suppressed because it only occurs when out of bounds,
//but bounds are already assured in this function
list[i], _ = rb.GetById(id + i + 1)
}
return list, nil
}
// Return length of the structure
func (rb *Buff) Len() int {
rb.RLock()
defer rb.RUnlock()
return rb.count
}
// next is a helper function for ringbuff
// it handles incrementing the old & new markers
func (rb *Buff) next() {
rb.new = (rb.new + 1) % rb.count
if rb.new-1 == rb.old {
rb.old = (rb.old + 1) % rb.count
}
if rb.old == -1 {
rb.old = 0
}
}
// getIndex is a helper function for ringbuff
// it returns an index relative to the old/new position of the buffer
func (rb *Buff) getIndex(i int) int {
var index int
if i < 0 {
index = (rb.new + rb.count + i) % rb.count
} else {
index = (rb.old + i) % rb.count
}
return index
}
// Push a round to the buffer
func (rb *Buff) push(val interface{}) {
rb.buff[rb.new] = val
rb.next()
}
// Get the ID of the newest item in the buffer
func (rb *Buff) getNewestId() int {
mostRecentIndex := (rb.new + rb.count - 1) % rb.count
if rb.buff[mostRecentIndex] == nil {
return -1
}
return rb.id(rb.Get())
}
// Get the IDof the oldest item in the buffer
func (rb *Buff) getOldestId() int {
if rb.old == -1 {
return -1
}
return rb.id(rb.buff[rb.getOldestIndex()])
}
// Get the index of the oldest item in the buffer, not including nil values inserted by UpsertById
func (rb *Buff) getOldestIndex() int {
if rb.old == -1 {
return -1
}
var last = rb.old
for ; rb.buff[last] == nil; last = (last + 1) % rb.count {
}
return last
}
package ring
import (
"strings"
"testing"
)
// Basic interface to use for testing
type Tester struct {
Id int
}
// ID func for tester object
func id(val interface{}) int {
return val.(*Tester).Id
}
func comp(old, new interface{}) bool {
if old != nil {
return false
}
return true
}
func getTester(id int) *Tester {
return &Tester{Id: id}
}
// Setup func for tests
func setup() *Buff {
rb := NewBuff(5, id)
for i := 1; i <= 5; i++ {
rb.Push(&Tester{
Id: i,
})
}
return rb
}
func TestBuff_GetNewestId(t *testing.T) {
rb := NewBuff(5, id)
id := rb.GetNewestId()
if id != -1 {
t.Error("Should have returned -1 for empty buff")
}
rb = setup()
_ = rb.UpsertById(getTester(7), comp)
id = rb.GetNewestId()
if id != 7 {
t.Error("Should have returned last pushed id")
}
}
func TestBuff_GetOldestId(t *testing.T) {
rb := NewBuff(5, id)
id := rb.GetOldestId()
if id != -1 {
t.Error("Should have returned -1 for empty buff")
}
rb = setup()
rb.Push(getTester(6))
id = rb.GetOldestId()
if id != 2 {
t.Errorf("Should have returned 2, instead got: %d", id)
}
_ = rb.UpsertById(getTester(22), comp)
id = rb.GetOldestId()
if id != 22 {
t.Errorf("Should have returned 22, instead got %d", id)
}
}
func TestBuff_GetOldestIndex(t *testing.T) {
rb := NewBuff(5, id)
i := rb.GetOldestIndex()
if i != -1 {
t.Error("Should have returned -1 for empty buff")
}
rb = setup()
rb.Push(getTester(6))
i = rb.GetOldestIndex()
if i != 1 {
t.Errorf("Should have returned 1, instead got: %d", i)
}
}
// Test the Get function on ringbuff
func TestBuff_Get(t *testing.T) {
rb := setup()
val := rb.Get().(*Tester)
if val.Id != 5 {
t.Errorf("Did not get most recent ID")
}
}
// Test the GetById function of ringbuff
func TestBuff_GetById(t *testing.T) {
rb := setup()
val, err := rb.GetById(3)
if err != nil {
t.Error("Failed to get id 3")
}
val = val.(*Tester).Id
if val != 3 {
t.Error("Got the wrong id")
}
}
// Test the basic push function on RingBuff
func TestBuff_Push(t *testing.T) {
rb := setup()
oldFirst := rb.old
rb.Push(&Tester{
Id: 6,
})
if rb.old != oldFirst+1 {
t.Error("Didn't increment old properly")
}
val := rb.Get().(*Tester)
if val.Id != 6 {
t.Error("Did not get newest id")
}
}
// Test ID upsert on ringbuff (bulk of cases)
func TestBuff_UpsertById(t *testing.T) {
rb := setup()
err := rb.UpsertById(&Tester{
Id: 8,
}, comp)
if err != nil {
t.Errorf("Error on initial upsert: %+v", err)
}
if rb.Get().(*Tester).Id != 8 {
t.Error("Failed to get correct ID")
}
val, _ := rb.GetById(7)
if val != nil {
t.Errorf("Should have gotten nil value for id 7")
}
err = rb.UpsertById(&Tester{
Id: 7,
}, comp)
if err != nil {
t.Errorf("Failed to upsert old ID: %+v", err)
}
err = rb.UpsertById(&Tester{
Id: 7,
}, comp)
if err == nil {
t.Errorf("Should have received error for failed comp function")
}
val, _ = rb.GetById(7)
if val.(*Tester).Id != 7 {
t.Errorf("Should have gotten id 7")
}
_, err = rb.GetById(20)
if err != nil && !strings.Contains(err.Error(), "is higher than most recent id") {
t.Error("Did not get proper error for id too high")
}
_, err = rb.GetById(0)
if err != nil && !strings.Contains(err.Error(), "is lower than oldest id") {
t.Error("Did not get proper error for id too high")
}
}
// Test upserting by id on ringbuff
func TestBuff_UpsertById2(t *testing.T) {
comp := func(old, new interface{}) bool {
if old != nil {
return false
}
return true
}
rb := setup()
err := rb.UpsertById(&Tester{
Id: -5,
}, comp)
if err == nil {
t.Error("This should have errored: id was too low")
}
err = rb.UpsertById(&Tester{
Id: 6,
}, comp)
if err != nil {
t.Errorf("Should have inserted using first case: %+v", err)
}
}
// test the length function of ring buff
func TestBuff_Len(t *testing.T) {
rb := setup()
if rb.Len() != 5 {
t.Errorf("Got wrong count")
}
}
// Test GetByIndex in ringbuff
func TestBuff_GetByIndex(t *testing.T) {
rb := setup()
val, _ := rb.GetByIndex(0)
if val.(*Tester).Id != 1 {
t.Error("Didn't get correct ID")
}
rb.Push(&Tester{
Id: 6,
})
val, _ = rb.GetByIndex(0)
if val.(*Tester).Id != 2 {
t.Error("Didn't get correct ID after pushing")
}
_, err := rb.GetByIndex(25)
if err == nil {
t.Errorf("Should have received index out of bounds err")
}
}
// Test the GetById function of ringbuff
func TestBuff_GetNewerById(t *testing.T) {
rb := setup()
list, err := rb.GetNewerById(3)
if err != nil {
t.Error("Failed to get newer than id 3")
}
if len(list) != 2 {
t.Errorf("list has wrong number of entrees: %s", list)
}
if list[0].(*Tester).Id != 4 {
t.Error("list has wrong number first element")
}
if list[1].(*Tester).Id != 5 {
t.Error("list has wrong number second element")
}
//test you get all when the id is less than the oldest id
list, err = rb.GetNewerById(-1)
if len(list) != 5 {
t.Errorf("list has wrong number of entrees: %s", list)
}
//test you get an error when you ask for something newer than the newest
list, err = rb.GetNewerById(42)
if list != nil {
t.Errorf("list should be nil")
}
if err == nil {
t.Errorf("error should have been returned")
} else if !strings.Contains(err.Error(), "is higher than the newest id") {
t.Errorf("wrong error returned: %s", err.Error())
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment