From 00f8d47ea38923aca105d84e2c7701be95a701ea Mon Sep 17 00:00:00 2001 From: Matt Dainty Date: Tue, 2 Apr 2024 01:02:42 +0100 Subject: [PATCH] refactor: Refactor pool to use upstream hashicorp/golang-lru package --- go.mod | 2 + go.sum | 4 +- internal/pool/pool.go | 97 ++++++++++++++----------------------------- 3 files changed, 36 insertions(+), 67 deletions(-) diff --git a/go.mod b/go.mod index 5895b4a..8d9ebea 100644 --- a/go.mod +++ b/go.mod @@ -23,3 +23,5 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/hashicorp/golang-lru/v2 => github.com/bodgit/golang-lru/v2 v2.0.0-20240402231653-73e2fffc225d diff --git a/go.sum b/go.sum index 13955a0..c0fa597 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/bodgit/golang-lru/v2 v2.0.0-20240402231653-73e2fffc225d h1:+VY4VHDvXKqMaaRcl7MUCs1YCIz7D4dC642ZClCGmXo= +github.com/bodgit/golang-lru/v2 v2.0.0-20240402231653-73e2fffc225d/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= @@ -66,8 +68,6 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 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.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= diff --git a/internal/pool/pool.go b/internal/pool/pool.go index 3389fb0..48a20d7 100644 --- a/internal/pool/pool.go +++ b/internal/pool/pool.go @@ -1,12 +1,12 @@ package pool import ( - "container/list" "runtime" "sort" "sync" "github.com/bodgit/sevenzip/internal/util" + lru "github.com/hashicorp/golang-lru/v2" ) // Pooler is the interface implemented by a pool. @@ -34,24 +34,30 @@ func (noopPool) Put(_ int64, rc util.SizeReadSeekCloser) (bool, error) { } type pool struct { - mutex sync.Mutex - size int - evictList *list.List - items map[int64]*list.Element -} - -type entry struct { - key int64 - value util.SizeReadSeekCloser + mutex sync.Mutex + errs chan error + cache *lru.Cache[int64, util.SizeReadSeekCloser] } // NewPool returns a Pooler that uses a LRU strategy to maintain a fixed pool // of util.SizeReadSeekCloser's keyed by their stream offset. func NewPool() (Pooler, error) { + errs := make(chan error) + + cache, err := lru.NewWithEvict[int64, util.SizeReadSeekCloser]( + runtime.NumCPU(), + func(_ int64, value util.SizeReadSeekCloser) { + if err := value.Close(); err != nil { + errs <- err + } + }) + if err != nil { + return nil, err + } + return &pool{ - size: runtime.NumCPU(), - evictList: list.New(), - items: make(map[int64]*list.Element), + errs: errs, + cache: cache, }, nil } @@ -59,23 +65,23 @@ func (p *pool) Get(offset int64) (util.SizeReadSeekCloser, bool) { p.mutex.Lock() defer p.mutex.Unlock() - if ent, ok := p.items[offset]; ok { - _ = p.removeElement(ent, false) + if reader, ok := p.cache.Get(offset); ok { + _ = p.cache.RemoveWithoutEvict(offset) - return ent.Value.(*entry).value, true //nolint:forcetypeassert + return reader, true } // Sort keys in descending order - keys := p.keys() + keys := p.cache.Keys() sort.Slice(keys, func(i, j int) bool { return keys[i] > keys[j] }) for _, k := range keys { // First key less than offset is the closest if k < offset { - ent := p.items[k] - _ = p.removeElement(ent, false) + reader, _ := p.cache.Get(k) + _ = p.cache.RemoveWithoutEvict(k) - return ent.Value.(*entry).value, true //nolint:forcetypeassert + return reader, true } } @@ -86,52 +92,13 @@ func (p *pool) Put(offset int64, rc util.SizeReadSeekCloser) (bool, error) { p.mutex.Lock() defer p.mutex.Unlock() - if _, ok := p.items[offset]; ok { - return false, nil - } - - ent := &entry{offset, rc} - entry := p.evictList.PushFront(ent) - p.items[offset] = entry - - var err error - - evict := p.evictList.Len() > p.size - if evict { - err = p.removeOldest() - } - - return evict, err -} - -func (p *pool) keys() []int64 { - keys := make([]int64, len(p.items)) - i := 0 - - for ent := p.evictList.Back(); ent != nil; ent = ent.Prev() { - keys[i] = ent.Value.(*entry).key //nolint:forcetypeassert - i++ - } - - return keys -} - -func (p *pool) removeOldest() error { - if ent := p.evictList.Back(); ent != nil { - return p.removeElement(ent, true) - } - - return nil -} - -func (p *pool) removeElement(e *list.Element, cb bool) error { - p.evictList.Remove(e) - kv := e.Value.(*entry) //nolint:forcetypeassert - delete(p.items, kv.key) + _, ok := p.cache.ContainsOrAdd(offset, rc) - if cb { - return kv.value.Close() + select { + case err := <-p.errs: + return ok, err + default: } - return nil + return ok, nil }