diff --git a/key/stateVector.go b/key/stateVector.go index a0132c8750a4ed2fcd63c088bc835f067487b23a..939b6742f4eb30008d245d59767271b58a4c2012 100644 --- a/key/stateVector.go +++ b/key/stateVector.go @@ -12,19 +12,24 @@ type stateVector struct { ctx *context key string + // Bitfield for key states + // If a key is clean, its bit will be 0 + // Otherwise, it's dirty/used/not available, and its bit will be 1 vect []uint64 firstAvailable uint32 numkeys uint32 - numAvalible uint32 + numAvailable uint32 mux sync.RWMutex } +// Fields must be exported for json marshal to serialize them type stateVectorDisk struct { - vect []uint64 - firstAvailable uint32 - numkeys uint32 + Vect []uint64 + FirstAvailable uint32 + NumAvailable uint32 + Numkeys uint32 } func newStateVector(ctx *context, key string, numkeys uint32) *stateVector { @@ -35,7 +40,7 @@ func newStateVector(ctx *context, key string, numkeys uint32) *stateVector { vect: make([]uint64, numBlocks), key: key, firstAvailable: 0, - numAvalible: numkeys, + numAvailable: numkeys, numkeys: numkeys, } @@ -94,7 +99,7 @@ func (sv *stateVector) Use(keynum uint32) error { sv.nextAvailable() } - sv.numAvalible-- + sv.numAvailable-- return sv.save() } @@ -102,7 +107,7 @@ func (sv *stateVector) Use(keynum uint32) error { func (sv *stateVector) GetNumAvailable() uint32 { sv.mux.RLock() defer sv.mux.RUnlock() - return sv.numAvalible + return sv.numAvailable } func (sv *stateVector) Used(keynum uint32) bool { @@ -116,14 +121,12 @@ func (sv *stateVector) used(keynum uint32) bool { block := keynum / 64 pos := keynum % 64 - sv.vect[block] &= 1 << pos - return (sv.vect[block]>>pos)&1 == 1 } func (sv *stateVector) Next() (uint32, error) { sv.mux.Lock() - defer sv.mux.Lock() + defer sv.mux.Unlock() if sv.firstAvailable >= sv.numkeys { return sv.numkeys, errors.New("No keys remaining") @@ -132,7 +135,7 @@ func (sv *stateVector) Next() (uint32, error) { next := sv.firstAvailable sv.nextAvailable() - sv.numAvalible-- + sv.numAvailable-- return next, sv.save() @@ -147,11 +150,11 @@ func (sv *stateVector) GetUnusedKeyNums() []uint32 { sv.mux.RLock() defer sv.mux.RUnlock() - keyNums := make([]uint32, sv.numAvalible) + keyNums := make([]uint32, 0, sv.numAvailable) for keyNum := sv.firstAvailable; keyNum < sv.numkeys; keyNum++ { if !sv.used(keyNum) { - keyNums[keyNum-sv.firstAvailable] = keyNum + keyNums = append(keyNums, keyNum) } } @@ -163,11 +166,11 @@ func (sv *stateVector) GetUsedKeyNums() []uint32 { sv.mux.RLock() defer sv.mux.RUnlock() - keyNums := make([]uint32, sv.numkeys-sv.numAvalible) + keyNums := make([]uint32, 0, sv.numkeys-sv.numAvailable) for keyNum := sv.firstAvailable; keyNum < sv.numkeys; keyNum++ { if sv.used(keyNum) { - keyNums[keyNum-sv.firstAvailable] = keyNum + keyNums = append(keyNums, keyNum) } } @@ -181,23 +184,24 @@ func (sv *stateVector) nextAvailable() { block := (sv.firstAvailable + 1) / 64 pos := (sv.firstAvailable + 1) % 64 - for ; block < uint32(len(sv.vect)) && sv.vect[block]>>pos&1 == 1; pos++ { - if pos == 64 { + for ; block < uint32(len(sv.vect)) && (sv.vect[block]>>pos)&1 == 1; pos++ { + if pos == 63 { pos = 0 block++ } } - sv.firstAvailable = pos + sv.firstAvailable = block*64 + pos } //ekv functions func (sv *stateVector) marshal() ([]byte, error) { svd := stateVectorDisk{} - svd.firstAvailable = sv.firstAvailable - svd.numkeys = sv.numkeys - svd.vect = sv.vect + svd.FirstAvailable = sv.firstAvailable + svd.Numkeys = sv.numkeys + svd.NumAvailable = sv.numAvailable + svd.Vect = sv.vect return json.Marshal(&svd) } @@ -212,9 +216,10 @@ func (sv *stateVector) unmarshal(b []byte) error { return err } - sv.firstAvailable = svd.firstAvailable - sv.numkeys = svd.numkeys - sv.vect = svd.vect + sv.firstAvailable = svd.FirstAvailable + sv.numkeys = svd.Numkeys + sv.numAvailable = svd.NumAvailable + sv.vect = svd.Vect return nil } diff --git a/key/stateVector_test.go b/key/stateVector_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6f069b4307aefb0bf428a1e8c43d48858cbca5e9 --- /dev/null +++ b/key/stateVector_test.go @@ -0,0 +1,224 @@ +package key + +import ( + "fmt" + "gitlab.com/elixxir/client/storage" + "math/bits" + "reflect" + "testing" +) + +// GetNumAvailable gets the number of slots left in the state vector +func TestStateVector_GetNumAvailable(t *testing.T) { + const numAvailable = 23 + sv := &stateVector{ + numAvailable: numAvailable, + } + // At the start, NumAvailable should be the same as numKeys + // as none of the keys have been used + if sv.GetNumAvailable() != numAvailable { + t.Errorf("expected %v available, actually %v available", numAvailable, sv.GetNumAvailable()) + } +} + +func TestStateVector_GetNumKeys(t *testing.T) { + ctx := context{ + kv: storage.InitMem(t), + } + const numKeys = 32 + sv := newStateVector(&ctx, "key", numKeys) + + // GetNumKeys should always be the same as numKeys + if sv.GetNumKeys() != numKeys { + t.Errorf("expected %v available, actually %v available", numKeys, sv.GetNumAvailable()) + } +} + +// Shows that Next mutates vector state as expected +// Shows that Next can find key indexes all throughout the bitfield +func TestStateVector_Next(t *testing.T) { + // Expected results: all keynums, and beyond the last key + expectedFirstAvail := []uint32{139, 145, 300, 360, 420, 761, 868, 875, 893, 995} + + ctx := context{ + kv: storage.InitMem(t), + } + const numKeys = 1000 + sv := newStateVector(&ctx, "key", numKeys) + + // Set all bits to dirty to start + for i := range sv.vect { + sv.vect[i] = 0xffffffffffffffff + } + + // Set a few clean bits randomly + const numBitsSet = 10 + for i := 0; i < numBitsSet; i++ { + keyNum := expectedFirstAvail[i] + // Set a bit clean in the state vector + vectIndex := keyNum / 64 + bitIndex := keyNum % 64 + sv.vect[vectIndex] &= ^bits.RotateLeft64(uint64(1), int(bitIndex)) + } + + sv.numAvailable = numBitsSet + sv.nextAvailable() + + // Calling Next ten times should give all of the keyNums we set + // It should change firstAvailable, but doesn't mutate the bit field itself + // (that should be done with Use) + for numCalls := 0; numCalls < numBitsSet; numCalls++ { + keyNum, err := sv.Next() + if err != nil { + t.Fatal(err) + } + if keyNum != expectedFirstAvail[numCalls] { + t.Errorf("keynum %v didn't match expected %v at index %v", keyNum, expectedFirstAvail[numCalls], numCalls) + } + } + + // One more call should cause an error + _, err := sv.Next() + if err == nil { + t.Error("Calling Next() after all keys have been found should result in error, as firstAvailable is more than numKeys") + } + // firstAvailable should now be beyond the end of the bitfield + if sv.firstAvailable < numKeys { + t.Error("Last Next() call should have set firstAvailable beyond numKeys") + } +} + +// Shows that Use() mutates the state vector itself +func TestStateVector_Use(t *testing.T) { + // These keyNums will be set to dirty with Use + keyNums := []uint32{139, 145, 300, 360, 420, 761, 868, 875, 893, 995} + + ctx := context{ + kv: storage.InitMem(t), + } + const numKeys = 1000 + sv := newStateVector(&ctx, "key", numKeys) + + // Expected vector states as bits are set + var expectedVect [][]uint64 + expectedVect = append(expectedVect, []uint64{0, 0, 0x800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) + expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) + expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) + expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) + expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0, 0, 0, 0, 0}) + expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0, 0, 0}) + expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0x1000000000, 0, 0}) + expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0x81000000000, 0, 0}) + expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0x2000081000000000, 0, 0}) + expectedVect = append(expectedVect, []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0x2000081000000000, 0, 0x800000000}) + + for numCalls := range keyNums { + // These calls to Use won't set nextAvailable, because the first keyNum set + err := sv.Use(keyNums[numCalls]) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(expectedVect[numCalls], sv.vect) { + t.Errorf("sv.vect differed from expected at index %v", numCalls) + fmt.Println(sv.vect) + } + } +} + +func TestStateVector_Used(t *testing.T) { + // These keyNums should be used + keyNums := []uint32{139, 145, 300, 360, 420, 761, 868, 875, 893, 995} + + ctx := context{ + kv: storage.InitMem(t), + } + const numKeys = 1000 + sv := newStateVector(&ctx, "key", numKeys) + sv.vect = []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0x2000081000000000, 0, 0x800000000} + + for i := uint32(0); i < numKeys; i++ { + // if i is in keyNums, Used should be true + // otherwise, it should be false + found := false + for j := range keyNums { + if i == keyNums[j] { + found = true + break + } + } + if sv.Used(i) != found { + t.Errorf("at keynum %v Used should have been %v but was %v", i, found, sv.Used(i)) + } + } +} + +// Shows that the GetUsedKeyNums method returns the correct keynums +func TestStateVector_GetUsedKeyNums(t *testing.T) { + // These keyNums should be used + keyNums := []uint32{139, 145, 300, 360, 420, 761, 868, 875, 893, 995} + + ctx := context{ + kv: storage.InitMem(t), + } + const numKeys = 1000 + sv := newStateVector(&ctx, "key", numKeys) + sv.vect = []uint64{0, 0, 0x20800, 0, 0x100000000000, 0x10000000000, 0x1000000000, 0, 0, 0, 0, 0x200000000000000, 0, 0x2000081000000000, 0, 0x800000000} + sv.numAvailable = uint32(numKeys - len(keyNums)) + + usedKeyNums := sv.GetUsedKeyNums() + for i := range keyNums { + if usedKeyNums[i] != keyNums[i] { + t.Errorf("used keynums at %v: expected %v, got %v", i, keyNums[i], usedKeyNums[i]) + } + } +} + +// Shows that GetUnusedKeyNums gets all clean keynums +func TestStateVector_GetUnusedKeyNums(t *testing.T) { + // These keyNums should not be used + keyNums := []uint32{139, 145, 300, 360, 420, 761, 868, 875, 893, 995} + + ctx := context{ + kv: storage.InitMem(t), + } + const numKeys = 1000 + sv := newStateVector(&ctx, "key", numKeys) + sv.vect = []uint64{0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffffdf7ff, 0xffffffffffffffff, 0xffffefffffffffff, 0xfffffeffffffffff, 0xffffffefffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfdffffffffffffff, 0xffffffffffffffff, 0xdffff7efffffffff, 0xffffffffffffffff, 0xfffffff7ffffffff} + sv.numAvailable = uint32(len(keyNums)) + sv.firstAvailable = keyNums[0] + + unusedKeyNums := sv.GetUnusedKeyNums() + for i := range keyNums { + if unusedKeyNums[i] != keyNums[i] { + t.Errorf("unused keynums at %v: expected %v, got %v", i, keyNums[i], unusedKeyNums[i]) + } + } + if len(keyNums) != len(unusedKeyNums) { + t.Error("array lengths differed, so arrays must be different") + } +} + +// Serializing and deserializing should result in the same state vector +func TestLoadStateVector(t *testing.T) { + keyNums := []uint32{139, 145, 300, 360, 420, 761, 868, 875, 893, 995} + const numKeys = 1000 + ctx := context{ + kv: storage.InitMem(t), + } + sv := newStateVector(&ctx, "key", numKeys) + sv.vect = []uint64{0xffffffffffffffff, 0xffffffffffffffff, 0xfffffffffffdf7ff, 0xffffffffffffffff, 0xffffefffffffffff, 0xfffffeffffffffff, 0xffffffefffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfdffffffffffffff, 0xffffffffffffffff, 0xdffff7efffffffff, 0xffffffffffffffff, 0xfffffff7ffffffff} + sv.numAvailable = uint32(len(keyNums)) + sv.firstAvailable = keyNums[0] + + err := sv.save() + if err != nil { + t.Fatal(err) + } + sv2, err := loadStateVector(&ctx, "key") + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(sv.vect, sv2.vect) { + t.Error("state vectors different after deserialization") + } +}