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: Started implementing cached ads map #2524

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from
Draft
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
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ require (
github.com/go-playground/assert/v2 v2.0.1
github.com/go-resty/resty/v2 v2.6.0
github.com/gorilla/websocket v1.5.0
github.com/iotaledger/hive.go/core v1.0.0-rc.2.0.20230110152711-02063266eb24
github.com/hashicorp/golang-lru/v2 v2.0.1
github.com/iotaledger/hive.go/core v1.0.0-rc.2.0.20230112011142-169d55ffd5cb
github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1
github.com/labstack/echo v3.3.10+incompatible
github.com/libp2p/go-libp2p v0.23.4
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,8 @@ github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
Expand Down Expand Up @@ -440,8 +442,8 @@ github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/C
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/iotaledger/grocksdb v1.7.5-0.20221128103803-fcdb79760195 h1:W5v+7oSXtSq2OSadYPyaAbPjTJW10T2bOgMDGZcyVOc=
github.com/iotaledger/grocksdb v1.7.5-0.20221128103803-fcdb79760195/go.mod h1:AoAM7v6lyWRQzrmmegOEq759o1PgvvKvn2bEe1A1mc8=
github.com/iotaledger/hive.go/core v1.0.0-rc.2.0.20230110152711-02063266eb24 h1:HiZNj4cCgqiCNolYk6qCF9FaYdUcTThufV/O9h+gTqI=
github.com/iotaledger/hive.go/core v1.0.0-rc.2.0.20230110152711-02063266eb24/go.mod h1:POmRNWlS/NWFCrMowt+CcE4H8GAVe1BotWLN9QD6FLE=
github.com/iotaledger/hive.go/core v1.0.0-rc.2.0.20230112011142-169d55ffd5cb h1:X9mv19JMH10yxA9sM/3haYTx0PM8tl6uMl0CDtzkpvA=
github.com/iotaledger/hive.go/core v1.0.0-rc.2.0.20230112011142-169d55ffd5cb/go.mod h1:POmRNWlS/NWFCrMowt+CcE4H8GAVe1BotWLN9QD6FLE=
github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1 h1:x3xsI32h+1wTIzLWInC+AcwrUyk9/l7z2RFMQiuua2E=
github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1/go.mod h1:jkV//O5d+HHm32qDmTy6AWZUgxuZaXazTUVqox+5z4g=
github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc=
Expand Down
116 changes: 116 additions & 0 deletions packages/core/ads/cachedmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package ads

import (
"sync"

lru "github.com/hashicorp/golang-lru/v2"
"github.com/iotaledger/hive.go/core/generics/constraints"
"github.com/iotaledger/hive.go/core/generics/lo"
"github.com/iotaledger/hive.go/core/generics/shrinkingmap"
"github.com/iotaledger/hive.go/core/kvstore"
"github.com/iotaledger/hive.go/core/typeutils"
)

type CachedMap[K, V constraints.Serializable, KPtr constraints.MarshalablePtr[K], VPtr constraints.MarshalablePtr[V]] struct {
storedMap *Map[K, V, KPtr, VPtr]

writeCache *shrinkingmap.ShrinkingMap[string, VPtr]
readCache *lru.Cache[string, VPtr]

mutex sync.Mutex
newElements *sync.Cond
writeCacheEmpty *sync.Cond
}

func NewCachedMap[K, V constraints.Serializable, KPtr constraints.MarshalablePtr[K], VPtr constraints.MarshalablePtr[V]](store kvstore.KVStore, cacheSize int) (newMap *CachedMap[K, V, KPtr, VPtr]) {
newMap = &CachedMap[K, V, KPtr, VPtr]{
storedMap: NewMap[K, V, KPtr, VPtr](store),
writeCache: shrinkingmap.New[string, VPtr](),
readCache: lo.PanicOnErr(lru.New[string, VPtr](cacheSize)),
}

newMap.newElements = &sync.Cond{
L: &newMap.mutex,
}

newMap.writeCacheEmpty = &sync.Cond{
L: &newMap.mutex,
}

go newMap.writeLoop()

return
}

func (c *CachedMap[K, V, KPtr, VPtr]) Set(key K, value VPtr) {
keyString := typeutils.BytesToString(lo.PanicOnErr(key.Bytes()))

c.mutex.Lock()
defer c.mutex.Unlock()

if c.writeCache.Set(keyString, value) && c.writeCache.Size() == 1 {
c.newElements.Signal()
}

c.readCache.Remove(keyString)
}

func (c *CachedMap[K, V, KPtr, VPtr]) Delete(key K) {
keyString := typeutils.BytesToString(lo.PanicOnErr(key.Bytes()))

c.mutex.Lock()
defer c.mutex.Unlock()

c.writeCache.Set(keyString, nil)
c.readCache.Remove(keyString)
}

func (c *CachedMap[K, V, KPtr, VPtr]) Has(key K) (has bool) {
return lo.Return1(c.Get(key)) != nil
}

func (c *CachedMap[K, V, KPtr, VPtr]) Get(key K) (value VPtr, exists bool) {
keyBytes := lo.PanicOnErr(key.Bytes())
keyString := typeutils.BytesToString(keyBytes)

c.mutex.Lock()
defer c.mutex.Unlock()

if writtenValue, writtenValueExists := c.writeCache.Get(keyString); writtenValueExists {
return writtenValue, writtenValue != nil
}

if readValue, readValueExists := c.readCache.Get(keyString); readValueExists {
return readValue, readValue != nil
}

value, exists = c.storedMap.Get(key)
c.readCache.Add(keyString, value)

return
}

func (c *CachedMap[K, V, KPtr, VPtr]) writeLoop() {
for {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop needs a way to be exited for cleanup

c.mutex.Lock()
for c.writeCache.Size() == 0 {
c.writeCacheEmpty.Broadcast()

c.newElements.Wait()
}

keyToWrite, valueToWrite, exists := c.writeCache.Pop()
if !exists {
panic("writeCache should not be empty")
}

c.readCache.Add(keyToWrite, valueToWrite)
c.mutex.Unlock()

if valueToWrite == nil {
c.storedMap.delete(typeutils.StringToBytes(keyToWrite))
} else {
c.storedMap.set(typeutils.StringToBytes(keyToWrite), lo.PanicOnErr(valueToWrite.Bytes()))
}
}
}
28 changes: 17 additions & 11 deletions packages/core/ads/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,21 @@ func (m *Map[K, V, KPtr, VPtr]) Root() (root types.Identifier) {

// Set sets the output to unspent outputs set.
func (m *Map[K, V, KPtr, VPtr]) Set(key K, value VPtr) {
m.mutex.Lock()
defer m.mutex.Unlock()

valueBytes := lo.PanicOnErr(value.Bytes())
if len(valueBytes) == 0 {
panic("value cannot be empty")
}

keyBytes := lo.PanicOnErr(key.Bytes())
m.set(lo.PanicOnErr(key.Bytes()), valueBytes)
}

m.root.Set(lo.PanicOnErr(m.tree.Update(keyBytes, valueBytes)))
func (m *Map[K, V, KPtr, VPtr]) set(key, value []byte) {
m.mutex.Lock()
defer m.mutex.Unlock()

if err := m.rawKeysStore.Set(keyBytes, []byte{}); err != nil {
m.root.Set(lo.PanicOnErr(m.tree.Update(key, value)))

if err := m.rawKeysStore.Set(key, []byte{}); err != nil {
panic(err)
}
}
Expand All @@ -74,14 +76,18 @@ func (m *Map[K, V, KPtr, VPtr]) Delete(key K) (deleted bool) {
return
}

m.mutex.Lock()
defer m.mutex.Unlock()

keyBytes := lo.PanicOnErr(key.Bytes())
if deleted = m.has(keyBytes); !deleted {
return
if deleted = m.has(keyBytes); deleted {
m.delete(keyBytes)
}

return
}

func (m *Map[K, V, KPtr, VPtr]) delete(keyBytes []byte) {
m.mutex.Lock()
defer m.mutex.Unlock()

m.root.Set(lo.PanicOnErr(m.tree.Delete(keyBytes)))

if err := m.rawKeysStore.Delete(keyBytes); err != nil {
Expand Down