diff --git a/packages/datastructure/doubly_linked_list.go b/packages/datastructure/doubly_linked_list.go new file mode 100644 index 0000000000000000000000000000000000000000..724a5e3a20509542e7ab29d209bba080ba6013f3 --- /dev/null +++ b/packages/datastructure/doubly_linked_list.go @@ -0,0 +1,261 @@ +package datastructure + +import ( + "github.com/iotaledger/goshimmer/packages/errors" + "sync" +) + +type DoublyLinkedList struct { + head *DoublyLinkedListEntry + tail *DoublyLinkedListEntry + count int + mutex sync.RWMutex +} + +// region public methods with locking ////////////////////////////////////////////////////////////////////////////////// + +// Appends the specified value to the end of this list. +func (list *DoublyLinkedList) Add(value interface{}) *DoublyLinkedListEntry { + return list.AddLast(value) +} + +// Appends the specified element to the end of this list. +func (list *DoublyLinkedList) AddEntry(entry *DoublyLinkedListEntry) { + list.AddLastEntry(entry) +} + +func (list *DoublyLinkedList) AddLast(value interface{}) *DoublyLinkedListEntry { + newEntry := &DoublyLinkedListEntry{ + value: value, + } + + list.AddLastEntry(newEntry) + + return newEntry +} + +func (list *DoublyLinkedList) AddLastEntry(entry *DoublyLinkedListEntry) { + list.mutex.Lock() + defer list.mutex.Unlock() + + list.addLastEntry(entry) +} + +func (list *DoublyLinkedList) AddFirst(value interface{}) *DoublyLinkedListEntry { + newEntry := &DoublyLinkedListEntry{ + value: value, + } + + list.AddFirstEntry(newEntry) + + return newEntry +} + +func (list *DoublyLinkedList) AddFirstEntry(entry *DoublyLinkedListEntry) { + list.mutex.Lock() + defer list.mutex.Unlock() + + list.addFirstEntry(entry) +} + +func (list *DoublyLinkedList) Clear() { + list.mutex.Lock() + defer list.mutex.Unlock() + + list.clear() +} + +func (list *DoublyLinkedList) GetFirst() (interface{}, errors.IdentifiableError) { + if firstEntry, err := list.GetFirstEntry(); err != nil { + return nil, err + } else { + return firstEntry.GetValue(), nil + } +} + +func (list *DoublyLinkedList) GetFirstEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) { + list.mutex.RLock() + defer list.mutex.RUnlock() + + return list.getFirstEntry() +} + +func (list *DoublyLinkedList) GetLast() (interface{}, errors.IdentifiableError) { + if lastEntry, err := list.GetLastEntry(); err != nil { + return nil, err + } else { + return lastEntry.GetValue(), nil + } +} + +func (list *DoublyLinkedList) GetLastEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) { + list.mutex.RLock() + defer list.mutex.RUnlock() + + return list.getLastEntry() +} + +func (list *DoublyLinkedList) RemoveFirst() (interface{}, errors.IdentifiableError) { + if firstEntry, err := list.RemoveFirstEntry(); err != nil { + return nil, err + } else { + return firstEntry.GetValue(), nil + } +} + +func (list *DoublyLinkedList) RemoveFirstEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) { + list.mutex.Lock() + defer list.mutex.Unlock() + + return list.removeFirstEntry() +} + +func (list *DoublyLinkedList) RemoveLast() (interface{}, errors.IdentifiableError) { + if lastEntry, err := list.RemoveLastEntry(); err != nil { + return nil, err + } else { + return lastEntry.GetValue(), nil + } +} + +func (list *DoublyLinkedList) RemoveLastEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) { + list.mutex.Lock() + defer list.mutex.Unlock() + + return list.removeLastEntry() +} + +func (list *DoublyLinkedList) Remove(value interface{}) errors.IdentifiableError { + list.mutex.RLock() + currentEntry := list.head + for currentEntry != nil { + if currentEntry.GetValue() == value { + list.mutex.RUnlock() + + if err := list.RemoveEntry(currentEntry); err != nil { + return err + } + + return nil + } + + currentEntry = currentEntry.GetNext() + } + list.mutex.RUnlock() + + return ErrNoSuchElement.Derive("the entry is not part of the list") +} + +func (list *DoublyLinkedList) RemoveEntry(entry *DoublyLinkedListEntry) errors.IdentifiableError { + list.mutex.Lock() + defer list.mutex.Unlock() + + return list.removeEntry(entry) +} + +func (list *DoublyLinkedList) GetSize() int { + list.mutex.RLock() + defer list.mutex.RUnlock() + + return list.count +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region private methods without locking ////////////////////////////////////////////////////////////////////////////// + +func (list *DoublyLinkedList) addLastEntry(entry *DoublyLinkedListEntry) { + if list.head == nil { + list.head = entry + } else { + list.tail.SetNext(entry) + entry.SetPrev(list.tail) + } + + list.tail = entry + list.count++ +} + +func (list *DoublyLinkedList) addFirstEntry(entry *DoublyLinkedListEntry) { + if list.tail == nil { + list.tail = entry + } else { + list.head.SetPrev(entry) + entry.SetNext(list.head) + } + + list.head = entry + list.count++ +} + +func (list *DoublyLinkedList) clear() { + list.head = nil + list.tail = nil + list.count = 0 +} + +func (list *DoublyLinkedList) getFirstEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) { + if list.head == nil { + return nil, ErrNoSuchElement.Derive("the list is empty") + } + + return list.head, nil +} + +func (list *DoublyLinkedList) getLastEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) { + if list.tail == nil { + return nil, ErrNoSuchElement.Derive("the list is empty") + } + + return list.tail, nil +} + +func (list *DoublyLinkedList) removeFirstEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) { + entryToRemove := list.head + if err := list.removeEntry(entryToRemove); err != nil { + return nil, err + } + + return entryToRemove, nil +} + +func (list *DoublyLinkedList) removeLastEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) { + entryToRemove := list.tail + if err := list.removeEntry(entryToRemove); err != nil { + return nil, err + } + + return entryToRemove, nil +} + +func (list *DoublyLinkedList) removeEntry(entry *DoublyLinkedListEntry) errors.IdentifiableError { + if entry == nil { + return ErrInvalidArgument.Derive("the entry must not be nil") + } + + if list.head == nil { + return ErrNoSuchElement.Derive("the entry is not part of the list") + } + + nextEntry := entry.GetNext() + if nextEntry != nil { + nextEntry.SetPrev(entry.GetPrev()) + } + if list.head == entry { + list.head = nextEntry + } + + prevEntry := entry.GetPrev() + if prevEntry != nil { + prevEntry.SetNext(entry.GetNext()) + } + if list.tail == entry { + list.tail = prevEntry + } + + list.count-- + + return nil +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/datastructure/doubly_linked_list_entry.go b/packages/datastructure/doubly_linked_list_entry.go new file mode 100644 index 0000000000000000000000000000000000000000..73c05b2964d2bf4b2400e91f60977d327b8ae4ec --- /dev/null +++ b/packages/datastructure/doubly_linked_list_entry.go @@ -0,0 +1,54 @@ +package datastructure + +import ( + "sync" +) + +type DoublyLinkedListEntry struct { + value interface{} + prev *DoublyLinkedListEntry + next *DoublyLinkedListEntry + mutex sync.RWMutex +} + +func (entry *DoublyLinkedListEntry) GetNext() *DoublyLinkedListEntry { + entry.mutex.RLock() + defer entry.mutex.RUnlock() + + return entry.next +} + +func (entry *DoublyLinkedListEntry) SetNext(next *DoublyLinkedListEntry) { + entry.mutex.Lock() + defer entry.mutex.Unlock() + + entry.next = next +} + +func (entry *DoublyLinkedListEntry) GetPrev() *DoublyLinkedListEntry { + entry.mutex.RLock() + defer entry.mutex.RUnlock() + + return entry.prev +} + +func (entry *DoublyLinkedListEntry) SetPrev(prev *DoublyLinkedListEntry) { + entry.mutex.Lock() + defer entry.mutex.Unlock() + + entry.prev = prev +} + +func (entry *DoublyLinkedListEntry) GetValue() interface{} { + entry.mutex.RLock() + defer entry.mutex.RUnlock() + + return entry.value +} + +func (entry *DoublyLinkedListEntry) SetValue(value interface{}) { + entry.mutex.Lock() + defer entry.mutex.Unlock() + + entry.value = value +} diff --git a/packages/datastructure/doubly_linked_list_test.go b/packages/datastructure/doubly_linked_list_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4e44086a10857a1bf208fdbebc77e9c62c41afd8 --- /dev/null +++ b/packages/datastructure/doubly_linked_list_test.go @@ -0,0 +1,55 @@ +package datastructure + +import ( + "testing" +) + +func TestAdd(t *testing.T) { + doublyLinkedList := &DoublyLinkedList{} + + doublyLinkedList.Add(12) + doublyLinkedList.Add(12) + doublyLinkedList.Add(15) + doublyLinkedList.Add(99) + + if doublyLinkedList.GetSize() != 4 { + t.Error("the size of the list is wrong") + } +} + +func TestDelete(t *testing.T) { + doublyLinkedList := &DoublyLinkedList{} + + doublyLinkedList.Add(12) + doublyLinkedList.Add(13) + doublyLinkedList.Add(15) + doublyLinkedList.Add(99) + + if _, err := doublyLinkedList.RemoveFirst(); err != nil { + t.Error(err) + } + + firstEntry, err := doublyLinkedList.GetFirst() + if err != nil { + t.Error(err) + } + if firstEntry != 13 { + t.Error("first entry should be 13 after delete") + } + + if _, err := doublyLinkedList.RemoveLast(); err != nil { + t.Error(err) + } + + lastEntry, err := doublyLinkedList.GetLast() + if err != nil { + t.Error(err) + } + if lastEntry != 15 { + t.Error("last entry should be 15 after delete") + } + + if doublyLinkedList.GetSize() != 2 { + t.Error("the size of the list should be 2 after delete") + } +} diff --git a/packages/datastructure/errors.go b/packages/datastructure/errors.go new file mode 100644 index 0000000000000000000000000000000000000000..bd52cc1cf5406ef4da3b5422b2e94abafc17322e --- /dev/null +++ b/packages/datastructure/errors.go @@ -0,0 +1,10 @@ +package datastructure + +import ( + "github.com/iotaledger/goshimmer/packages/errors" +) + +var ( + ErrNoSuchElement = errors.New("element does not exist") + ErrInvalidArgument = errors.New("invalid argument") +) diff --git a/packages/datastructure/lru_cache.go b/packages/datastructure/lru_cache.go new file mode 100644 index 0000000000000000000000000000000000000000..7b4e2a2dbc746dcd45d157739bc47d26af2209c9 --- /dev/null +++ b/packages/datastructure/lru_cache.go @@ -0,0 +1,152 @@ +package datastructure + +import ( + "sync" +) + +type lruCacheElement struct { + key interface{} + value interface{} +} + +type LRUCache struct { + directory map[interface{}]*DoublyLinkedListEntry + doublyLinkedList *DoublyLinkedList + capacity int + size int + processingCallback bool + mutex sync.RWMutex +} + +func NewLRUCache(capacity int) *LRUCache { + return &LRUCache{ + directory: make(map[interface{}]*DoublyLinkedListEntry), + doublyLinkedList: &DoublyLinkedList{}, + capacity: capacity, + } +} + +func (cache *LRUCache) Set(key interface{}, value interface{}) { + if !cache.processingCallback { + cache.mutex.Lock() + defer cache.mutex.Unlock() + } + + element, exists := cache.directory[key] + if exists { + if !cache.processingCallback { + element.GetValue().(*lruCacheElement).value = value + + cache.doublyLinkedList.mutex.Lock() + defer cache.doublyLinkedList.mutex.Unlock() + } else { + element.value.(*lruCacheElement).value = value + } + + cache.promoteElement(element) + } else { + cache.directory[key] = cache.doublyLinkedList.AddFirst(&lruCacheElement{key: key, value: value}) + + if cache.size == cache.capacity { + if element, err := cache.doublyLinkedList.RemoveLast(); err != nil { + panic(err) + } else { + delete(cache.directory, element.(*lruCacheElement).key) + } + } else { + cache.size++ + } + } +} + +func (cache *LRUCache) Contains(key interface{}, optionalCallback ... func(interface{}, bool)) bool { + var callback func(interface{}, bool) + + if len(optionalCallback) >= 1 { + if !cache.processingCallback { + cache.mutex.Lock() + defer cache.mutex.Unlock() + } + + callback = optionalCallback[0] + } else { + if !cache.processingCallback { + cache.mutex.RLock() + defer cache.mutex.RUnlock() + } + } + + var elementValue interface{} + element, exists := cache.directory[key] + if exists { + cache.doublyLinkedList.mutex.Lock() + defer cache.doublyLinkedList.mutex.Unlock() + + cache.promoteElement(element) + + elementValue = element.GetValue().(*lruCacheElement).value + } + + if callback != nil { + cache.processingCallback = true + callback(elementValue, exists) + cache.processingCallback = false + } + + return exists +} + +func (cache *LRUCache) GetCapacity() int { + cache.mutex.RLock() + defer cache.mutex.RUnlock() + + return cache.capacity +} + +func (cache *LRUCache) GetSize() int { + cache.mutex.RLock() + defer cache.mutex.RUnlock() + + return cache.size +} + +func (cache *LRUCache) Delete(key interface{}) bool { + if !cache.processingCallback { + cache.mutex.RLock() + } + + entry, exists := cache.directory[key] + if exists { + if !cache.processingCallback { + cache.mutex.RUnlock() + + cache.mutex.Lock() + defer cache.mutex.Unlock() + + cache.doublyLinkedList.mutex.Lock() + defer cache.doublyLinkedList.mutex.Unlock() + } + + if err := cache.doublyLinkedList.removeEntry(entry); err != nil { + panic(err) + } + delete(cache.directory, key) + + cache.size-- + + return true + } + + if !cache.processingCallback { + cache.mutex.RUnlock() + } + + return false +} + +func (cache *LRUCache) promoteElement(element *DoublyLinkedListEntry) { + if err := cache.doublyLinkedList.removeEntry(element); err != nil { + panic(err) + } + cache.doublyLinkedList.addFirstEntry(element) +} diff --git a/packages/datastructure/lru_cache_test.go b/packages/datastructure/lru_cache_test.go new file mode 100644 index 0000000000000000000000000000000000000000..129dd4ea9780615924adc4c4fc38302e44c5c21d --- /dev/null +++ b/packages/datastructure/lru_cache_test.go @@ -0,0 +1,102 @@ +package datastructure + +import ( + "testing" +) + +func TestLRUCache(t *testing.T) { + cache := NewLRUCache(5) + + cache.Contains("test", func(elem interface{}, contains bool) { + if !contains { + cache.Set("test", 12) + } + }) + + if !cache.Contains("test", func(elem interface{}, contains bool) { + if !contains || elem != 12 { + t.Error("the cache contains the wrong element") + } + }) { + t.Error("the cache does not contain the added elements") + } + + if cache.GetSize() != 1 { + t.Error("the size should be 1") + } + + if cache.GetCapacity() != 5 { + t.Error("the capacity should be 5") + } + + cache.Set("a", 3) + cache.Set("b", 4) + cache.Set("c", 5) + cache.Set("d", 6) + + if cache.GetSize() != 5 { + t.Error("the size should be 5") + } + + cache.Set("e", 7) + + if cache.GetSize() != 5 { + t.Error("the size should be 5") + } + + if cache.Contains("test") { + t.Error("'test' should have been dropped") + } + + cache.Set("a", 6) + cache.Set("f", 8) + + if cache.GetSize() != 5 { + t.Error("the size should be 5") + } + + if !cache.Contains("a") { + t.Error("'a' should not have been dropped") + } + if cache.Contains("b") { + t.Error("'b' should have been dropped") + } + + cache.Contains("tust", func(elem interface{}, contains bool) { + if !contains { + cache.Set("tust", 1337) + } + }) + + if cache.GetSize() != 5 { + t.Error("the size should be 5") + } + + cache.Contains("a", func(value interface{}, exists bool) { + if exists { + cache.Delete("a") + } + }) + if cache.GetSize() != 4 { + t.Error("the size should be 4") + } + + cache.Delete("f") + if cache.GetSize() != 3 { + t.Error("the size should be 3") + } +} + +func BenchmarkLRUCache(b *testing.B) { + cache := NewLRUCache(10000) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + cache.Contains(i, func(val interface{}, exists bool) { + if !exists { + cache.Set(i, i) + } + }) + } +} diff --git a/plugins/tangle/approvers.go b/plugins/tangle/approvers.go index 7ca77561a9cc00e800d5e85ceac6ec222e4dd914..3606330f6dee88cb31b96625ffd254d1a740bb40 100644 --- a/plugins/tangle/approvers.go +++ b/plugins/tangle/approvers.go @@ -20,7 +20,7 @@ func NewApprovers(hash ternary.Trinary) *Approvers { } } -// region public method with locking /////////////////////////////////////////////////////////////////////////////////// +// region public methods with locking ////////////////////////////////////////////////////////////////////////////////// func (approvers *Approvers) Add(transactionHash ternary.Trinary) { approvers.hashesMutex.Lock()