1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
// Package pool implements the reader pooling.
package pool
import (
"container/list"
"runtime"
"sort"
"sync"
"github.com/bodgit/sevenzip/internal/util"
)
// Pooler is the interface implemented by a pool.
type Pooler interface {
Get(offset int64) (util.SizeReadSeekCloser, bool)
Put(offset int64, rc util.SizeReadSeekCloser) (bool, error)
}
// Constructor is the function prototype used to instantiate a pool.
type Constructor func() (Pooler, error)
type noopPool struct{}
// NewNoopPool returns a Pooler that doesn't actually pool anything.
func NewNoopPool() (Pooler, error) {
return new(noopPool), nil
}
func (noopPool) Get(_ int64) (util.SizeReadSeekCloser, bool) {
return nil, false
}
func (noopPool) Put(_ int64, rc util.SizeReadSeekCloser) (bool, error) {
return false, rc.Close() //nolint:wrapcheck
}
type pool struct {
mutex sync.Mutex
size int
evictList *list.List
items map[int64]*list.Element
}
type entry struct {
key int64
value 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) {
return &pool{
size: runtime.NumCPU(),
evictList: list.New(),
items: make(map[int64]*list.Element),
}, nil
}
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)
return ent.Value.(*entry).value, true //nolint:forcetypeassert
}
// Sort keys in descending order
keys := p.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)
return ent.Value.(*entry).value, true //nolint:forcetypeassert
}
}
return nil, false
}
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)
if cb {
return kv.value.Close() //nolint:wrapcheck
}
return nil
}
|