Skip to content

Commit 9eae625

Browse files
authored
refactor(cache)!: extract hybrid_cache package (#2477)
* refactor(HybridCache)!: extract hybrid_cache package and rename cache_threshold to auto_memory_limit * split HybridCache and stores into internal/hybrid_cache package * introduce BackingStore abstraction with BufferStore and FileStore * rename CacheThreshold to AutoMemoryLimit in config/env/bootstrap * update stream/request integration and related tests * rename singleFileCache and MultiFileCache to singleFileStore and MultiFileStore * refactor(HybridCache): improve memory management strategies in NewHybridCache * rename * alias hybrid_cache to hcache * omments
1 parent daad21e commit 9eae625

19 files changed

Lines changed: 476 additions & 603 deletions

File tree

internal/bootstrap/config.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,12 @@ func InitConfig() {
131131
log.Warn("failed to get memory info, disable memory cache")
132132
}
133133

134-
if conf.Conf.CacheThreshold > 0 {
135-
conf.CacheThreshold = uint64(conf.Conf.CacheThreshold) << 20
134+
if conf.Conf.AutoMemoryLimit > 0 {
135+
conf.AutoMemoryLimit = uint64(conf.Conf.AutoMemoryLimit) << 20
136136
} else {
137-
conf.CacheThreshold = 0
137+
conf.AutoMemoryLimit = 0
138138
}
139-
log.Infof("cache threshold: %dMB", conf.CacheThreshold>>20)
139+
log.Infof("auto memory limit: %dMB", conf.AutoMemoryLimit>>20)
140140

141141
if len(conf.Conf.Log.Filter.Filters) == 0 {
142142
conf.Conf.Log.Filter.Enable = false

internal/conf/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ type Config struct {
120120
DistDir string `json:"dist_dir"`
121121
Log LogConfig `json:"log" envPrefix:"LOG_"`
122122
DelayedStart int `json:"delayed_start" env:"DELAYED_START"`
123+
AutoMemoryLimit int `json:"auto_memory_limit" env:"AUTO_MEMORY_LIMIT"`
123124
MinFreeMemory int `json:"min_free_memory" env:"MIN_FREE_MEMORY"`
124125
MaxBlockLimit int `json:"max_block_limit" env:"MAX_BLOCK_LIMIT"`
125-
CacheThreshold int `json:"cache_threshold" env:"CACHE_THRESHOLD"`
126126
MaxConnections int `json:"max_connections" env:"MAX_CONNECTIONS"`
127127
MaxConcurrency int `json:"max_concurrency" env:"MAX_CONCURRENCY"`
128128
TlsInsecureSkipVerify bool `json:"tls_insecure_skip_verify" env:"TLS_INSECURE_SKIP_VERIFY"`
@@ -179,7 +179,7 @@ func DefaultConfig(dataDir string) *Config {
179179
},
180180
},
181181
},
182-
CacheThreshold: 4,
182+
AutoMemoryLimit: 4,
183183
MaxConnections: 0,
184184
MaxConcurrency: 64,
185185
TlsInsecureSkipVerify: false,

internal/conf/var.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ var FilenameCharMap = make(map[string]string)
2525
var PrivacyReg []*regexp.Regexp
2626

2727
var (
28-
// 限制单次内存的扩容大小,超过该阈值将分多次扩容。
29-
// CacheThreshold大于0时,也限制 Downloader 的PartSize
30-
MaxBlockLimit uint64 = 16 * 1024 * 1024
31-
// 大于该阈值的数据流将使用HybridCache,可主动释放内存。
32-
// 否则使用Go的内存分配,直到GC回收。
33-
CacheThreshold uint64 = 4 * 1024 * 1024
28+
// 在HybridCache中使用[]byte缓存数据流的限制,内存为Go自动管理,直到GC
29+
AutoMemoryLimit uint64 = 4 * 1024 * 1024
3430
// 最小空闲内存,当内存不足时,HybridCache会回退到文件缓存。
3531
// 如果为0,HybridCache会使用文件缓存,不占用内存。
3632
MinFreeMemory uint64 = 16 * 1024 * 1024
33+
// 限制HybridCache手动管理内存单次的扩容大小,超过该阈值将分多次扩容。
34+
// MinFreeMemory大于0时,也限制 Downloader 的PartSize
35+
MaxBlockLimit uint64 = 16 * 1024 * 1024
3736
)
37+
3838
var (
3939
RawIndexHtml string
4040
ManageHtml string

internal/hybrid_cache/buffer.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package hybrid_cache
2+
3+
import (
4+
"fmt"
5+
"io"
6+
)
7+
8+
type BufferStore struct {
9+
blocks [][]byte
10+
size int64
11+
}
12+
13+
func (m *BufferStore) Size() int64 {
14+
return m.size
15+
}
16+
17+
// 用于存储不复用的[]byte
18+
func (m *BufferStore) Append(buf []byte) {
19+
m.size += int64(len(buf))
20+
m.blocks = append(m.blocks, buf)
21+
}
22+
23+
func (m *BufferStore) Close() error {
24+
if len(m.blocks) > 0 {
25+
clear(m.blocks)
26+
m.blocks = m.blocks[:0]
27+
m.size = 0
28+
}
29+
return nil
30+
}
31+
32+
func (m *BufferStore) ReadAt(p []byte, off int64) (int, error) {
33+
if len(p) == 0 {
34+
return 0, nil
35+
}
36+
if off < 0 || off >= m.size {
37+
return 0, io.EOF
38+
}
39+
40+
var n int
41+
for _, buf := range m.blocks {
42+
if off >= int64(len(buf)) {
43+
off -= int64(len(buf))
44+
continue
45+
}
46+
nn := copy(p[n:], buf[off:])
47+
n += nn
48+
if n == len(p) {
49+
return n, nil
50+
}
51+
off = 0
52+
}
53+
54+
return n, io.EOF
55+
}
56+
57+
func (m *BufferStore) WriteAt(p []byte, off int64) (int, error) {
58+
if len(p) == 0 {
59+
return 0, nil
60+
}
61+
if off < 0 || off >= m.size {
62+
return 0, io.ErrShortWrite
63+
}
64+
65+
var n int
66+
for _, b := range m.blocks {
67+
if off >= int64(len(b)) {
68+
off -= int64(len(b))
69+
continue
70+
}
71+
nn := copy(b[off:], p[n:])
72+
n += nn
73+
if n == len(p) {
74+
return n, nil
75+
}
76+
off = 0
77+
}
78+
79+
return n, io.ErrShortWrite
80+
}
81+
82+
func (m *BufferStore) GrowTo(size int64) (err error) {
83+
if size <= m.size {
84+
return nil
85+
}
86+
defer func() {
87+
if r := recover(); r != nil {
88+
err = fmt.Errorf("recovered in %v", r)
89+
}
90+
}()
91+
m.blocks = append(m.blocks, make([]byte, size-m.size))
92+
m.size = size
93+
return nil
94+
}
95+
96+
var _ BackingStore = (*BufferStore)(nil)
Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
1-
package buffer
1+
package hybrid_cache_test
22

33
import (
44
"errors"
55
"io"
66
"testing"
7+
8+
"github.com/OpenListTeam/OpenList/v4/internal/hybrid_cache"
79
)
810

9-
func TestReadAt(t *testing.T) {
11+
func TestBufferStore(t *testing.T) {
1012
type args struct {
1113
p []byte
1214
off int64
1315
}
14-
bs := &Reader{}
16+
bs := &hybrid_cache.BufferStore{}
1517
bs.Append([]byte("github.com"))
1618
bs.Append([]byte("/OpenList"))
17-
bs.Append([]byte("Team/"))
18-
bs.Append([]byte("OpenList"))
19+
bs.Append([]byte("Team/?"))
20+
b := []byte("OpenList")
21+
off := bs.Size() - 1
22+
_ = bs.GrowTo(off + int64(len(b)))
23+
_, _ = bs.WriteAt(b, off)
24+
1925
tests := []struct {
2026
name string
21-
b *Reader
27+
b *hybrid_cache.BufferStore
2228
args args
2329
check func(a args, n int, err error) error
2430
}{
@@ -87,7 +93,7 @@ func TestReadAt(t *testing.T) {
8793
t.Run(tt.name, func(t *testing.T) {
8894
got, err := tt.b.ReadAt(tt.args.p, tt.args.off)
8995
if err := tt.check(tt.args, got, err); err != nil {
90-
t.Errorf("Bytes.ReadAt() error = %v", err)
96+
t.Errorf("BufferStore.ReadAt() error = %v", err)
9197
}
9298
})
9399
}
Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,23 @@
1-
package cache
1+
package hybrid_cache
22

33
import (
44
"errors"
55
"io"
66
"os"
77

88
"github.com/OpenListTeam/OpenList/v4/internal/conf"
9-
"github.com/OpenListTeam/OpenList/v4/pkg/buffer"
109
)
1110

12-
type FileCache interface {
13-
buffer.Block
14-
io.Closer
15-
Truncate(size int64) error
16-
}
17-
18-
type singleFileCache struct {
11+
type singleFileStore struct {
1912
*os.File
2013
size int64
2114
}
2215

23-
func (s *singleFileCache) Size() int64 {
16+
func (s *singleFileStore) Size() int64 {
2417
return s.size
2518
}
2619

27-
func (s *singleFileCache) Truncate(size int64) error {
20+
func (s *singleFileStore) GrowTo(size int64) error {
2821
if size <= s.size {
2922
return nil
3023
}
@@ -35,7 +28,7 @@ func (s *singleFileCache) Truncate(size int64) error {
3528
return err
3629
}
3730

38-
func (s *singleFileCache) Close() error {
31+
func (s *singleFileStore) Close() error {
3932
err := s.File.Close()
4033
_ = os.Remove(s.File.Name())
4134
return err
@@ -47,16 +40,16 @@ type fileBlock struct {
4740
written int64
4841
}
4942

50-
type MultiFileCache struct {
43+
type MultiFileStore struct {
5144
blocks []*fileBlock
5245
size int64
5346
}
5447

55-
func (s *MultiFileCache) Size() int64 {
48+
func (s *MultiFileStore) Size() int64 {
5649
return s.size
5750
}
5851

59-
func (m *MultiFileCache) Close() error {
52+
func (m *MultiFileStore) Close() error {
6053
var errs []error
6154
for _, c := range m.blocks {
6255
if err := c.file.Close(); err != nil {
@@ -69,7 +62,7 @@ func (m *MultiFileCache) Close() error {
6962
return errors.Join(errs...)
7063
}
7164

72-
func (m *MultiFileCache) Truncate(size int64) error {
65+
func (m *MultiFileStore) GrowTo(size int64) error {
7366
if size <= m.size {
7467
return nil
7568
}
@@ -82,7 +75,7 @@ func (m *MultiFileCache) Truncate(size int64) error {
8275
return nil
8376
}
8477

85-
func (m *MultiFileCache) ReadAt(p []byte, off int64) (n int, err error) {
78+
func (m *MultiFileStore) ReadAt(p []byte, off int64) (n int, err error) {
8679
if len(p) == 0 {
8780
return 0, nil
8881
}
@@ -131,7 +124,7 @@ func (m *MultiFileCache) ReadAt(p []byte, off int64) (n int, err error) {
131124
return n, io.EOF
132125
}
133126

134-
func (m *MultiFileCache) WriteAt(p []byte, off int64) (n int, err error) {
127+
func (m *MultiFileStore) WriteAt(p []byte, off int64) (n int, err error) {
135128
if len(p) == 0 {
136129
return 0, nil
137130
}
@@ -170,16 +163,16 @@ func (m *MultiFileCache) WriteAt(p []byte, off int64) (n int, err error) {
170163
return n, io.ErrShortWrite
171164
}
172165

173-
func NewFileCache(blockSize int64) (FileCache, error) {
166+
func NewFileStore(blockSize int64) (BackingStore, error) {
174167
f, err := os.CreateTemp(conf.Conf.TempDir, "file-*")
175168
if err != nil {
176169
return nil, err
177170
}
178171
err = f.Truncate(blockSize)
179172
if err == nil {
180-
return &singleFileCache{File: f, size: blockSize}, nil
173+
return &singleFileStore{File: f, size: blockSize}, nil
181174
}
182-
return &MultiFileCache{
175+
return &MultiFileStore{
183176
blocks: []*fileBlock{{file: f, size: blockSize}},
184177
size: blockSize,
185178
}, nil
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package cache_test
1+
package hybrid_cache_test
22

33
import (
44
"bytes"
@@ -8,8 +8,8 @@ import (
88
"reflect"
99
"testing"
1010

11-
"github.com/OpenListTeam/OpenList/v4/internal/cache"
1211
"github.com/OpenListTeam/OpenList/v4/internal/conf"
12+
"github.com/OpenListTeam/OpenList/v4/internal/hybrid_cache"
1313
)
1414

1515
func TestFile(t *testing.T) {
@@ -59,7 +59,7 @@ func TestMultiFileCache(t *testing.T) {
5959
conf.Conf = prevConf
6060
})
6161
conf.Conf = &conf.Config{}
62-
f := cache.MultiFileCache{}
62+
f := hybrid_cache.MultiFileStore{}
6363
defer f.Close()
6464
t.Run("ReadAt", func(t *testing.T) {
6565
_, err := f.ReadAt(make([]byte, 1), 20)
@@ -68,7 +68,7 @@ func TestMultiFileCache(t *testing.T) {
6868
}
6969
})
7070
t.Run("WriteAt", func(t *testing.T) {
71-
err := f.Truncate(15)
71+
err := f.GrowTo(15)
7272
if err != nil {
7373
t.Errorf("truncate err=%v", err)
7474
return
@@ -79,7 +79,7 @@ func TestMultiFileCache(t *testing.T) {
7979
return
8080
}
8181

82-
err = f.Truncate(30)
82+
err = f.GrowTo(30)
8383
if err != nil {
8484
t.Errorf("truncate err=%v", err)
8585
return

0 commit comments

Comments
 (0)