-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmemory.go
More file actions
149 lines (126 loc) · 3.87 KB
/
memory.go
File metadata and controls
149 lines (126 loc) · 3.87 KB
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
139
140
141
142
143
144
145
146
147
148
149
// Copyright The ActForGood Authors.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://github.com/actforgood/xcache/blob/main/LICENSE.
package xcache
import (
"context"
"errors"
"sync"
"time"
"github.com/coocood/freecache"
)
const freecacheMinBufSize = 512 * 1024
// Memory is an in memory implementation for Cache.
// It is not distributed, keys are stored in memory,
// only for current instance.
// It relies upon Freecache package.
type Memory struct {
client *freecache.Cache
memSize int64 // memory size in bytes
mu *sync.RWMutex // concurrency semaphore used for xconf adapter.
}
// NewMemory initializes a new Memory instance.
//
// Relaying package additional notes:
// The cache size will be set to 512KB at minimum.
// If the size is set relatively large, you should call
// [runtime/debug.SetGCPercent], set it to a much smaller value
// to limit the memory consumption and GC pause time.
func NewMemory(memSize int) *Memory {
mem := getRealMemorySize(memSize)
client := freecache.NewCache(mem)
return &Memory{
client: client,
memSize: int64(mem),
}
}
// Save stores the given key-value with expiration period into cache.
// An expiration period equal to 0 (NoExpire) means no expiration.
// A negative expiration period triggers deletion of key.
// It returns an error if the key could not be saved.
//
// Additional relaying package notes:
// If the key is larger than 65535 or value is larger than 1/1024 of the cache size,
// the entry will not be written to the cache.
// Items can be evicted when cache is full.
func (cache *Memory) Save(
_ context.Context,
key string,
value []byte,
expire time.Duration,
) error {
if expire < 0 { // delete the key
cache.rLock()
_ = cache.client.Del([]byte(key))
cache.rUnlock()
return nil
}
expireSeconds := int(expire.Seconds())
if expire > 0 && expireSeconds == 0 {
// convert expire < 1s to 1s as Freecache expects seconds, and 0 means no expiration.
// highly improbable to enter here, as items are usually cached for longer periods.
expireSeconds = 1
}
cache.rLock()
err := cache.client.Set([]byte(key), value, expireSeconds)
cache.rUnlock()
return err
}
// Load returns a key's value from cache, or an error if something bad happened.
// If the key is not found, ErrNotFound is returned.
func (cache *Memory) Load(_ context.Context, key string) ([]byte, error) {
cache.rLock()
value, err := cache.client.Get([]byte(key))
cache.rUnlock()
if errors.Is(err, freecache.ErrNotFound) {
return nil, ErrNotFound
}
return value, err
}
// TTL returns a key's remaining time to live. Error is always nil.
// If the key is not found, a negative TTL is returned.
// If the key has no expiration, 0 (NoExpire) is returned.
func (cache *Memory) TTL(_ context.Context, key string) (time.Duration, error) {
cache.rLock()
ttl, err := cache.client.TTL([]byte(key))
cache.rUnlock()
if errors.Is(err, freecache.ErrNotFound) {
return -1, nil
}
return time.Duration(ttl), err
}
// Stats returns statistics about memory cache.
// Returned error is always nil and can be safely disregarded.
func (cache *Memory) Stats(_ context.Context) (Stats, error) {
cache.rLock()
stats := Stats{
Memory: cache.memSize,
MaxMemory: cache.memSize,
Hits: cache.client.HitCount(),
Misses: cache.client.MissCount(),
Keys: cache.client.EntryCount(),
Expired: cache.client.ExpiredCount(),
Evicted: cache.client.EvacuateCount(),
}
cache.rUnlock()
return stats, nil
}
func (cache *Memory) rLock() {
if cache.mu != nil {
cache.mu.RLock()
}
}
func (cache *Memory) rUnlock() {
if cache.mu != nil {
cache.mu.RUnlock()
}
}
// getRealMemorySize returns memory according to Freecache min limit (512 Kb).
func getRealMemorySize(memSize int) int {
mem := memSize
if mem < freecacheMinBufSize {
mem = freecacheMinBufSize
}
return mem
}