├── hashtable ├── hashtable.go └── hashtable_test.go └── linkedlist ├── list.go └── list_test.go /hashtable/hashtable.go: -------------------------------------------------------------------------------- 1 | package hashtable 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | "unsafe" 8 | 9 | "github.com/gouthamve/mvcc_array/linkedlist" 10 | ) 11 | 12 | // Hashtable implements a cuckoo hashing based table 13 | type Hashtable struct { 14 | size int 15 | maxReach int 16 | 17 | // TODO: Make pointer. The comparision takes time?? 18 | values []*linkedlist.LinkedList 19 | 20 | txnCtr uint64 21 | 22 | // Last Successful Txn 23 | lsTxn uint64 24 | // TODO: For mulitple writers 25 | //txnTable map[uint64]bool 26 | 27 | sync.Mutex 28 | } 29 | 30 | // KeyType is the key 31 | type KeyType int 32 | 33 | // ValType is the val 34 | type ValType int 35 | 36 | // KVType is the keyvalue type 37 | type KVType struct { 38 | Key KeyType 39 | Val ValType 40 | } 41 | 42 | func newHT(size, maxReach int) *Hashtable { 43 | h := Hashtable{ 44 | size: size, 45 | maxReach: maxReach, 46 | } 47 | 48 | h.values = make([]*linkedlist.LinkedList, size) 49 | for i := range h.values { 50 | h.values[i] = &linkedlist.LinkedList{} 51 | 52 | h.insert(0, &KVType{}, i) 53 | } 54 | 55 | h.lsTxn = 0 56 | h.txnCtr = 0 57 | return &h 58 | } 59 | 60 | // NewDefaultHT returns a new Hashtable with sensible defaults 61 | func NewDefaultHT() *Hashtable { 62 | return newHT(512, 16) 63 | } 64 | 65 | // TODO: EDIT THIS 66 | func (h *Hashtable) hash1(i KeyType) int { 67 | return int(i) % h.size 68 | } 69 | 70 | // TODO: EDIT THIS 71 | func (h *Hashtable) hash2(i KeyType) int { 72 | return (int(i) / 11) % h.size 73 | } 74 | 75 | // TODO: Do we need a KeyType 76 | func (h *Hashtable) insert(txn uint64, kv *KVType, idx int) error { 77 | h.values[idx].Insert(txn, unsafe.Pointer(kv)) 78 | return nil 79 | } 80 | 81 | // Put puts the kv into the hashtable 82 | // ANNY: This is the key part. See how the rollback happens 83 | func (h *Hashtable) Put(kv KVType) error { 84 | h.Lock() 85 | defer h.Unlock() 86 | // NOTE: The rollback will be via the abandoning of the txn 87 | txn := atomic.AddUint64(&h.txnCtr, 1) 88 | current := &kv 89 | idxMod := make([]int, h.maxReach) 90 | 91 | for i := 0; i < h.maxReach; i++ { 92 | idx := h.hash1(current.Key) 93 | temp := (*KVType)(h.values[idx].Head()) 94 | if (*temp == KVType{}) { 95 | h.insert(txn, current, idx) 96 | h.lsTxn = txn 97 | return nil 98 | } 99 | 100 | idx = h.hash2(current.Key) 101 | temp = (*KVType)(h.values[idx].Head()) 102 | if (*temp == KVType{}) { 103 | h.insert(txn, current, idx) 104 | h.lsTxn = txn 105 | return nil 106 | } 107 | 108 | // Take the key from the second slot 109 | h.insert(txn, current, idx) 110 | idxMod[i] = idx 111 | current = temp 112 | } 113 | // Abandon the txn 114 | // Delete the elements of the current txn 115 | for i := 0; i < h.maxReach; i++ { 116 | h.values[idxMod[i]].Delete(txn) 117 | } 118 | 119 | return fmt.Errorf("Key %d couldn't be inserted due to a tight table", kv.Key) 120 | } 121 | 122 | // Get gets the keyvalue pair back 123 | func (h *Hashtable) Get(k KeyType) (bool, ValType) { 124 | // NOTE: Make this serialised when we deal with mulitple 125 | // writers 126 | version := h.lsTxn 127 | 128 | idx := h.hash1(k) 129 | // No nil check needed as we are filling version 0 130 | val := (*KVType)(h.values[idx].LatestVersion(version)) 131 | if (*val != KVType{} && val.Key == k) { 132 | return true, val.Val 133 | } 134 | 135 | idx = h.hash2(k) 136 | val = (*KVType)(h.values[idx].LatestVersion(version)) 137 | if (*val != KVType{} && val.Key == k) { 138 | return true, val.Val 139 | } 140 | 141 | return false, 0 142 | } 143 | 144 | // Delete deletes all the elements with the key 145 | func (h *Hashtable) Delete(k KeyType) (bool, error) { 146 | h.Lock() 147 | defer h.Unlock() 148 | txn := atomic.AddUint64(&h.txnCtr, 1) 149 | idx := h.hash1(k) 150 | val := (*KVType)(h.values[idx].LatestVersion(h.lsTxn)) 151 | if (*val != KVType{} && val.Key == k) { 152 | h.insert(txn, &KVType{}, idx) 153 | return true, nil 154 | } 155 | 156 | idx = h.hash2(k) 157 | val = (*KVType)(h.values[idx].LatestVersion(h.lsTxn)) 158 | if (*val != KVType{} && val.Key == k) { 159 | h.insert(txn, &KVType{}, idx) 160 | return true, nil 161 | } 162 | 163 | return false, nil 164 | } 165 | -------------------------------------------------------------------------------- /hashtable/hashtable_test.go: -------------------------------------------------------------------------------- 1 | package hashtable_test 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "path/filepath" 7 | "reflect" 8 | "runtime" 9 | "testing" 10 | 11 | "github.com/gouthamve/mvcc_array/hashtable" 12 | ) 13 | 14 | func TestInit(t *testing.T) { 15 | h := hashtable.NewDefaultHT() 16 | assert(t, h != nil, "Expected init to return a decent map") 17 | } 18 | 19 | func TestInsert(t *testing.T) { 20 | mp := make(map[hashtable.KeyType]hashtable.ValType) 21 | h := hashtable.NewDefaultHT() 22 | 23 | for i := 0; i < 150; i++ { 24 | kv := hashtable.KVType{ 25 | Key: hashtable.KeyType(rand.Int()), 26 | Val: hashtable.ValType(rand.Int()), 27 | } 28 | 29 | mp[kv.Key] = kv.Val 30 | ok(t, h.Put(kv)) 31 | } 32 | 33 | for k, v := range mp { 34 | exists, val := h.Get(k) 35 | assert(t, exists, "Expected key %d to exist", k) 36 | equals(t, v, val) 37 | } 38 | } 39 | 40 | func TestParallelRead(t *testing.T) { 41 | mp := make(map[hashtable.KeyType]hashtable.ValType) 42 | h := hashtable.NewDefaultHT() 43 | 44 | for i := 0; i < 150; i++ { 45 | kv := hashtable.KVType{ 46 | Key: hashtable.KeyType(rand.Int()), 47 | Val: hashtable.ValType(rand.Int()), 48 | } 49 | 50 | mp[kv.Key] = kv.Val 51 | ok(t, h.Put(kv)) 52 | } 53 | 54 | for k, v := range mp { 55 | go func(k hashtable.KeyType, v hashtable.ValType) { 56 | exists, val := h.Get(k) 57 | assert(t, exists, "Expected key %d to exist", k) 58 | equals(t, v, val) 59 | }(k, v) 60 | } 61 | } 62 | 63 | func TestDelete(t *testing.T) { 64 | mp := make(map[hashtable.KeyType]hashtable.ValType) 65 | h := hashtable.NewDefaultHT() 66 | 67 | for i := 0; i < 100; i++ { 68 | kv := hashtable.KVType{ 69 | Key: hashtable.KeyType(rand.Int()), 70 | Val: hashtable.ValType(rand.Int()), 71 | } 72 | 73 | mp[kv.Key] = kv.Val 74 | ok(t, h.Put(kv)) 75 | } 76 | 77 | for k := range mp { 78 | if rand.Float64() > 0.5 { 79 | h.Delete(k) 80 | delete(mp, k) 81 | } 82 | } 83 | 84 | for k, v := range mp { 85 | go func(k hashtable.KeyType, v hashtable.ValType) { 86 | exists, val := h.Get(k) 87 | assert(t, exists, "Expected key %d to exist", k) 88 | equals(t, v, val) 89 | }(k, v) 90 | } 91 | } 92 | 93 | // assert fails the test if the condition is false. 94 | func assert(tb testing.TB, condition bool, msg string, v ...interface{}) { 95 | if !condition { 96 | _, file, line, _ := runtime.Caller(1) 97 | fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...) 98 | tb.FailNow() 99 | } 100 | } 101 | 102 | // ok fails the test if an err is not nil. 103 | func ok(tb testing.TB, err error) { 104 | if err != nil { 105 | _, file, line, _ := runtime.Caller(1) 106 | fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) 107 | tb.FailNow() 108 | } 109 | } 110 | 111 | // equals fails the test if exp is not equal to act. 112 | func equals(tb testing.TB, exp, act interface{}) { 113 | if !reflect.DeepEqual(exp, act) { 114 | _, file, line, _ := runtime.Caller(1) 115 | fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) 116 | tb.FailNow() 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /linkedlist/list.go: -------------------------------------------------------------------------------- 1 | package linkedlist 2 | 3 | import ( 4 | "sync/atomic" 5 | "unsafe" 6 | ) 7 | 8 | // Node is a Linked List Node 9 | type Node struct { 10 | next *Node 11 | version uint64 12 | deleted *bool // Fucking atomic replace! 13 | 14 | object unsafe.Pointer 15 | } 16 | 17 | // LinkedList is a linked list? 18 | type LinkedList struct { 19 | head *Node 20 | } 21 | 22 | // Insert Inserts a node into the list 23 | func (ll *LinkedList) Insert(version uint64, data unsafe.Pointer) *Node { 24 | currentHead := ll.head 25 | f := false 26 | 27 | if currentHead == nil || version > currentHead.version { 28 | newNode := &Node{ 29 | version: version, 30 | deleted: &f, 31 | next: currentHead, 32 | 33 | object: data, 34 | } 35 | 36 | if !atomic.CompareAndSwapPointer( 37 | (*unsafe.Pointer)(unsafe.Pointer(&ll.head)), 38 | unsafe.Pointer(currentHead), 39 | unsafe.Pointer(newNode), 40 | ) { 41 | return ll.Insert(version, data) 42 | } 43 | 44 | return newNode 45 | } 46 | 47 | cursor := ll.head 48 | 49 | for { 50 | if cursor.next == nil || version > cursor.next.version { 51 | next := cursor.next 52 | 53 | // WTF are we spinning on this? 54 | if next != nil && *next.deleted { 55 | continue 56 | } 57 | 58 | newNode := &Node{ 59 | version: version, 60 | deleted: &f, 61 | next: next, 62 | 63 | object: data, 64 | } 65 | 66 | if !atomic.CompareAndSwapPointer( 67 | (*unsafe.Pointer)(unsafe.Pointer(&cursor.next)), 68 | unsafe.Pointer(next), 69 | unsafe.Pointer(newNode), 70 | ) { 71 | return ll.Insert(version, data) 72 | } 73 | 74 | return newNode 75 | } 76 | 77 | cursor = cursor.next 78 | } 79 | } 80 | 81 | func assignTrue() *bool { 82 | b := true 83 | return &b 84 | } 85 | 86 | // Delete deletes the shit out bruh 87 | func (ll *LinkedList) Delete(version uint64) { 88 | var prev *Node 89 | currentHead := ll.head 90 | cursor := ll.head 91 | var t *bool 92 | t = assignTrue() 93 | 94 | for { 95 | if cursor == nil { 96 | break 97 | } 98 | 99 | if cursor.version == version { 100 | if !atomic.CompareAndSwapPointer( 101 | (*unsafe.Pointer)(unsafe.Pointer(&cursor.deleted)), 102 | unsafe.Pointer(cursor.deleted), 103 | unsafe.Pointer(t), 104 | ) { 105 | ll.Delete(version) 106 | return 107 | } 108 | 109 | rt := false 110 | 111 | if prev != nil { 112 | rt = atomic.CompareAndSwapPointer( 113 | (*unsafe.Pointer)(unsafe.Pointer(&(prev.next))), 114 | unsafe.Pointer(prev.next), 115 | unsafe.Pointer(cursor.next), 116 | ) 117 | } else { 118 | // HEAD! 119 | rt = atomic.CompareAndSwapPointer( 120 | (*unsafe.Pointer)(unsafe.Pointer(¤tHead)), 121 | unsafe.Pointer(currentHead), 122 | unsafe.Pointer(cursor.next), 123 | ) 124 | } 125 | 126 | if !rt { 127 | ll.Delete(version) 128 | } 129 | 130 | break 131 | } 132 | 133 | prev = cursor 134 | cursor = cursor.next 135 | } 136 | } 137 | 138 | // Head returns the object stored in the first node 139 | func (ll *LinkedList) Head() unsafe.Pointer { 140 | return ll.head.object 141 | } 142 | 143 | // LatestVersion returns the node that has a version equal to 144 | // or less than the version given 145 | func (ll *LinkedList) LatestVersion(v uint64) unsafe.Pointer { 146 | cur := ll.head 147 | for cur != nil && cur.version > v { 148 | cur = cur.next 149 | } 150 | 151 | if cur == nil { 152 | return nil 153 | } 154 | 155 | return cur.object 156 | } 157 | 158 | // Snapshot gets the current Snapshot. 159 | // For debugging only 160 | func (ll *LinkedList) Snapshot() (s []uint64) { 161 | cursor := ll.head 162 | 163 | for cursor != nil { 164 | if !*cursor.deleted { 165 | s = append(s, cursor.version) 166 | } 167 | 168 | cursor = cursor.next 169 | } 170 | 171 | return 172 | } 173 | -------------------------------------------------------------------------------- /linkedlist/list_test.go: -------------------------------------------------------------------------------- 1 | package linkedlist_test 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "reflect" 7 | "runtime" 8 | "testing" 9 | "unsafe" 10 | 11 | "github.com/gouthamve/mvcc_array/linkedlist" 12 | ) 13 | 14 | func TestInitial(t *testing.T) { 15 | ll := linkedlist.LinkedList{} 16 | 17 | assert(t, len(ll.Snapshot()) == 0, "Expected initial length to be 0") 18 | } 19 | 20 | func TestSerialInsert(t *testing.T) { 21 | ll := linkedlist.LinkedList{} 22 | dummy := 1 23 | 24 | table := []struct { 25 | elem uint64 26 | list []uint64 27 | }{ 28 | {1, []uint64{1}}, 29 | {2, []uint64{2, 1}}, 30 | {30, []uint64{30, 2, 1}}, 31 | {25, []uint64{30, 25, 2, 1}}, 32 | {0, []uint64{30, 25, 2, 1, 0}}, 33 | //{-10, []uint64{30, 25, 2, 1, 0, -10}}, 34 | //{20, []uint64{30, 25, 20, 2, 1, 0, -10}}, 35 | } 36 | 37 | for _, v := range table { 38 | ll.Insert(v.elem, unsafe.Pointer(&dummy)) 39 | equals(t, v.list, ll.Snapshot()) 40 | } 41 | 42 | } 43 | 44 | func TestSerialDelete(t *testing.T) { 45 | ll := linkedlist.LinkedList{} 46 | dummy := 1 47 | 48 | table := []struct { 49 | elem uint64 50 | list []uint64 51 | }{ 52 | {20, []uint64{30, 25, 2, 1, 0}}, 53 | //{-10, []uint64{30, 25, 2, 1, 0}}, 54 | {0, []uint64{30, 25, 2, 1}}, 55 | {25, []uint64{30, 2, 1}}, 56 | {30, []uint64{2, 1}}, 57 | {2, []uint64{1}}, 58 | {1, []uint64(nil)}, 59 | } 60 | 61 | for _, v := range []uint64{30, 25, 20, 2, 1, 0} { 62 | ll.Insert(v, unsafe.Pointer(&dummy)) 63 | } 64 | 65 | equals(t, []uint64{30, 25, 20, 2, 1, 0}, ll.Snapshot()) 66 | 67 | for _, v := range table { 68 | ll.Delete(v.elem) 69 | equals(t, v.list, ll.Snapshot()) 70 | } 71 | 72 | } 73 | 74 | func TestParallelInsert(t *testing.T) { 75 | dummy := 1 76 | 77 | table := []struct { 78 | elem uint64 79 | list []uint64 80 | }{ 81 | {1, []uint64{1}}, 82 | {2, []uint64{2, 1}}, 83 | {30, []uint64{30, 2, 1}}, 84 | {25, []uint64{30, 25, 2, 1}}, 85 | {0, []uint64{30, 25, 2, 1, 0}}, 86 | //{-10, []uint64{30, 25, 2, 1, 0, -10}}, 87 | //{20, []uint64{30, 25, 20, 2, 1, 0, -10}}, 88 | } 89 | 90 | for i := 0; i < len(table); i++ { 91 | ll := linkedlist.LinkedList{} 92 | c := make(chan bool) 93 | for j := 0; j <= i; j++ { 94 | go func(j int) { 95 | ll.Insert(table[j].elem, unsafe.Pointer(&dummy)) 96 | c <- true 97 | }(j) 98 | } 99 | 100 | for j := 0; j <= i; j++ { 101 | <-c 102 | } 103 | 104 | equals(t, table[i].list, ll.Snapshot()) 105 | } 106 | } 107 | 108 | func TestParallelDelete(t *testing.T) { 109 | dummy := 1 110 | 111 | table := []struct { 112 | elem uint64 113 | list []uint64 114 | }{ 115 | {20, []uint64{30, 25, 2, 1, 0}}, 116 | //{-10, []int{30, 25, 2, 1, 0}}, 117 | {0, []uint64{30, 25, 2, 1}}, 118 | {25, []uint64{30, 2, 1}}, 119 | {30, []uint64{2, 1}}, 120 | {2, []uint64{1}}, 121 | {1, []uint64(nil)}, 122 | } 123 | // equals(t, []int{30, 25, 20, 2, 1, 0, -10}, ll.Snapshot()) 124 | 125 | for i := range table { 126 | ll := linkedlist.LinkedList{} 127 | for _, val := range []uint64{30, 25, 20, 2, 1, 0} { 128 | ll.Insert(val, unsafe.Pointer(&dummy)) 129 | } 130 | ch := make(chan bool) 131 | for j := 0; j <= i; j++ { 132 | go func(d int) { 133 | ll.Delete(table[d].elem) 134 | ch <- true 135 | }(j) 136 | } 137 | 138 | for j := 0; j <= i; j++ { 139 | <-ch 140 | } 141 | 142 | //fmt.Printf("%d\n", i) 143 | equals(t, table[i].list, ll.Snapshot()) 144 | } 145 | } 146 | 147 | // assert fails the test if the condition is false. 148 | func assert(tb testing.TB, condition bool, msg string, v ...interface{}) { 149 | if !condition { 150 | _, file, line, _ := runtime.Caller(1) 151 | fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...) 152 | tb.FailNow() 153 | } 154 | } 155 | 156 | // ok fails the test if an err is not nil. 157 | func ok(tb testing.TB, err error) { 158 | if err != nil { 159 | _, file, line, _ := runtime.Caller(1) 160 | fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) 161 | tb.FailNow() 162 | } 163 | } 164 | 165 | // equals fails the test if exp is not equal to act. 166 | func equals(tb testing.TB, exp, act interface{}) { 167 | if !reflect.DeepEqual(exp, act) { 168 | _, file, line, _ := runtime.Caller(1) 169 | fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) 170 | tb.FailNow() 171 | } 172 | } 173 | --------------------------------------------------------------------------------