Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(store) Added concurrency-safe data structures #105

Merged
merged 4 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions tools/rwstore/rwlist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package rwstore

import "sync"

type RWList[V any] struct {
a []V
sync.RWMutex
}

func NewRWList[V any]() *RWList[V] {
return &RWList[V]{
a: make([]V, 0),
}
}

func (rw *RWList[V]) Get(index int) (V, bool) {
rw.RLock()
defer rw.RUnlock()
if index < 0 || index >= len(rw.a) {
var zero V
return zero, false
}
return rw.a[index], true
}

func (rw *RWList[V]) Replace(newList []V) {
rw.Lock()
rw.a = newList
rw.Unlock()
}

func (rw *RWList[V]) Append(value V) {
rw.Lock()
rw.a = append(rw.a, value)
rw.Unlock()
}

func (rw *RWList[V]) Copy() []V {
rw.RLock()
defer rw.RUnlock()
return append([]V(nil), rw.a...)
}
63 changes: 63 additions & 0 deletions tools/rwstore/rwlist_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package rwstore

import (
"testing"
)

func TestNewRWList(t *testing.T) {
list := NewRWList[int]()
if list == nil {
t.Error("NewRWList did not create a new list.")
}
}

func TestGet(t *testing.T) {
list := NewRWList[int]()
list.Append(10)
value, ok := list.Get(0)
if !ok || value != 10 {
t.Errorf("Get failed, expected 10 got %v", value)
}
_, ok = list.Get(-1)
if ok {
t.Error("Get should return false for negative index")
}
_, ok = list.Get(1)
if ok {
t.Error("Get should return false for out-of-range index")
}
}

func TestReplace(t *testing.T) {
list := NewRWList[int]()
list.Append(10)
list.Replace([]int{20, 30})
if len(list.Copy()) != 2 {
t.Error("Replace did not work as expected")
}
}

func TestAppend(t *testing.T) {
list := NewRWList[int]()
list.Append(10)
if len(list.Copy()) != 1 {
t.Error("Append did not increase the length of the list")
}
if list.a[0] != 10 {
t.Errorf("Append did not append the correct value, got %v", list.a[0])
}
}

func TestCopy(t *testing.T) {
list := NewRWList[int]()
list.Append(10)
copiedList := list.Copy()
if len(copiedList) != 1 || copiedList[0] != 10 {
t.Error("Copy did not copy the list correctly")
}
// Modify original list and ensure copy is unaffected
list.Append(20)
if len(copiedList) != 1 {
t.Error("Copy was affected by changes to the original list")
}
}
59 changes: 59 additions & 0 deletions tools/rwstore/rwmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package rwstore

import (
"sync"
)

type RWMap[K comparable, V any] struct {
m map[K]V
sync.RWMutex
}

func NewRWMap[K comparable, V any]() *RWMap[K, V] {
return &RWMap[K, V]{
m: make(map[K]V),
}
}

func (rw *RWMap[K, V]) Get(key K) (V, bool) {
rw.RLock()
value, ok := rw.m[key]
rw.RUnlock()
return value, ok
}

func (rw *RWMap[K, V]) Set(key K, value V) {
rw.Lock()
rw.m[key] = value
rw.Unlock()
}

func (rw *RWMap[K, V]) Delete(key K) {
rw.Lock()
delete(rw.m, key)
rw.Unlock()
}

func (rw *RWMap[K, V]) Exists(key K) bool {
rw.RLock()
_, exists := rw.m[key]
rw.RUnlock()
return exists
}

func (rw *RWMap[K, V]) Len() int {
rw.RLock()
length := len(rw.m)
rw.RUnlock()
return length
}

func (rw *RWMap[K, V]) Iterate(iter func(K, V) bool) {
rw.RLock()
defer rw.RUnlock()
for k, v := range rw.m {
if !iter(k, v) {
break
}
}
}
76 changes: 76 additions & 0 deletions tools/rwstore/rwmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package rwstore

import (
"sync"
"testing"
)

func TestRWMap(t *testing.T) {
rwMap := NewRWMap[int, string]()

// Test Set and Get
rwMap.Set(1, "one")
if v, ok := rwMap.Get(1); !ok || v != "one" {
t.Errorf("Set or Get is not working, expected 'one', got %v", v)
}

// Test Exists
if !rwMap.Exists(1) {
t.Errorf("Exists is not working, expected true, got false")
}

// Test Len
if rwMap.Len() != 1 {
t.Errorf("Len is not working, expected 1, got %d", rwMap.Len())
}

// Test Delete
rwMap.Delete(1)
if rwMap.Exists(1) {
t.Errorf("Delete is not working, key 1 still exists")
}

// Test Iterate
rwMap.Set(2, "two")
rwMap.Set(3, "three")
visited := make(map[int]bool)
rwMap.Iterate(func(k int, _ string) bool {
visited[k] = true
return true
})
if len(visited) != 2 || !visited[2] || !visited[3] {
t.Errorf("Iterate is not working correctly, visited map: %+v", visited)
}
}

func TestRWMapConcurrentAccess(t *testing.T) {
rwMap := NewRWMap[int, int]()
var wg sync.WaitGroup

// Perform concurrent writes
for i := 0; i < 100; i++ {
wg.Add(1)
go func(val int) {
defer wg.Done()
rwMap.Set(val, val)
}(i)
}

// Perform concurrent reads
for i := 0; i < 100; i++ {
wg.Add(1)
go func(val int) {
defer wg.Done()
if v, ok := rwMap.Get(val); ok && v != val {
t.Errorf("Concurrent access failed, expected %d, got %d", val, v)
}
}(i)
}

wg.Wait()

// Check final map length
if l := rwMap.Len(); l != 100 {
t.Errorf("Concurrent writes failed, expected map length 100, got %d", l)
}
}
Loading