• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

effective-security / porto / 20027053258

08 Dec 2025 11:47AM UTC coverage: 75.241%. First build
20027053258

Pull #512

github

dissoupov
Cache: delete multiple keys
Pull Request #512: Cache: delete multiple keys

19 of 24 new or added lines in 3 files covered. (79.17%)

5315 of 7064 relevant lines covered (75.24%)

11.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.38
/pkg/cache/memory.go
1
package cache
2

3
import (
4
        "context"
5
        "encoding/json"
6
        "path"
7
        "strings"
8
        "sync"
9
        "time"
10

11
        "github.com/cockroachdb/errors"
12
        "github.com/effective-security/x/guid"
13
)
14

15
type memProv struct {
16
        prefix string
17

18
        subs  sync.Map
19
        cache sync.Map
20
}
21

22
type entry struct {
23
        expires *time.Time
24
        // keep JSON encoded to be in parity with Redis
25
        data []byte
26
}
27

28
// NewMemoryProvider returns memory cache
29
func NewMemoryProvider(prefix string) Provider {
1✔
30
        prov := &memProv{
1✔
31
                prefix: prefix,
1✔
32
        }
1✔
33

1✔
34
        return prov
1✔
35
}
1✔
36

37
// Close closes the client, releasing any open resources.
38
// It is rare to Close a Client, as the Client is meant to be long-lived and shared between many goroutines.
39
func (p *memProv) Close() error {
1✔
40
        return nil
1✔
41
}
1✔
42

43
// IsLocal returns true, if cache is local
44
func (p *memProv) IsLocal() bool {
2✔
45
        return true
2✔
46
}
2✔
47

48
// Set data
49
func (p *memProv) Set(_ context.Context, key string, v any, ttl time.Duration) error {
80✔
50
        if ttl == 0 {
80✔
51
                ttl = DefaultTTL
×
52
        }
×
53

54
        k := path.Join(p.prefix, key)
80✔
55
        b, err := json.Marshal(v)
80✔
56
        if err != nil {
80✔
57
                return errors.Wrapf(err, "failed to marshal value: %s", k)
×
58
        }
×
59

60
        val := &entry{
80✔
61
                data: b,
80✔
62
        }
80✔
63

80✔
64
        if ttl != KeepTTL {
144✔
65
                exp := NowFunc().Add(ttl)
64✔
66
                val.expires = &exp
64✔
67
        }
64✔
68
        p.cache.Store(k, val)
80✔
69
        return nil
80✔
70
}
71

72
// Get data
73
func (p *memProv) Get(_ context.Context, key string, v any) error {
98✔
74
        k := path.Join(p.prefix, key)
98✔
75
        if ent, ok := p.cache.Load(k); ok {
162✔
76
                e := ent.(*entry)
64✔
77
                if e.expires == nil || e.expires.After(NowFunc()) {
96✔
78
                        err := json.Unmarshal(ent.(*entry).data, v)
32✔
79
                        if err != nil {
32✔
80
                                return errors.Wrapf(err, "failed to unmarshal value: %s", k)
×
81
                        }
×
82
                        return nil
32✔
83
                }
84
        }
85

86
        return ErrNotFound
66✔
87
}
88

89
// Delete data
90
func (p *memProv) Delete(_ context.Context, keys ...string) error {
48✔
91
        if len(keys) == 0 {
48✔
NEW
92
                return nil
×
NEW
93
        }
×
94
        for _, key := range keys {
96✔
95
                k := path.Join(p.prefix, key)
48✔
96
                p.cache.Delete(k)
48✔
97
        }
48✔
98
        return nil
48✔
99
}
100

101
// CleanExpired data
102
func (p *memProv) CleanExpired(_ context.Context) {
6✔
103
        now := NowFunc()
6✔
104
        p.cache.Range(func(key any, value any) bool {
54✔
105
                e := value.(*entry)
48✔
106
                if e.expires != nil && e.expires.After(now) {
64✔
107
                        k := key.(string)
16✔
108
                        p.cache.Delete(k)
16✔
109
                }
16✔
110
                return true
48✔
111
        })
112
}
113

114
// Keys returns list of keys.
115
// This method should be used mostly for testing, as in prod many keys maybe returned
116
func (p *memProv) Keys(_ context.Context, pattern string) ([]string, error) {
2✔
117
        k := path.Join(p.prefix, pattern)
2✔
118
        k = strings.TrimRight(k, "*?")
2✔
119

2✔
120
        var list []string
2✔
121

2✔
122
        p.cache.Range(func(key any, _ any) bool {
18✔
123
                name := key.(string)
16✔
124
                if strings.HasPrefix(name, k) {
32✔
125
                        list = append(list, name)
16✔
126
                }
16✔
127
                return true
16✔
128
        })
129
        return list, nil
2✔
130
}
131

132
// Publish publishes message to channel
133
func (p *memProv) Publish(_ context.Context, channel, message string) error {
2✔
134
        p.subs.Range(func(_ any, value any) bool {
6✔
135
                s := value.(*msub)
4✔
136
                if s.channel == channel {
8✔
137
                        s.ch <- message
4✔
138
                }
4✔
139
                return true
4✔
140
        })
141

142
        return nil
2✔
143
}
144

145
// Subscribe subscribes to channel
146
func (p *memProv) Subscribe(_ context.Context, channel string) Subscription {
6✔
147
        s := &msub{
6✔
148
                prov:    p,
6✔
149
                channel: channel,
6✔
150
                id:      guid.MustCreate(),
6✔
151
                ch:      make(chan string, 10),
6✔
152
        }
6✔
153
        p.subs.Store(s.id, s)
6✔
154
        return s
6✔
155
}
6✔
156

157
type msub struct {
158
        prov    *memProv
159
        id      string
160
        channel string
161

162
        ch chan string
163
}
164

165
func (s *msub) Close() error {
6✔
166
        s.prov.subs.Delete(s.id)
6✔
167
        return nil
6✔
168
}
6✔
169

170
func (s *msub) ReceiveMessage(ctx context.Context) (string, error) {
6✔
171
        for {
12✔
172
                select {
6✔
173
                case msg := <-s.ch:
4✔
174
                        return msg, nil
4✔
175
                case <-time.After(time.Second):
2✔
176
                        if e := ctx.Err(); e != nil {
4✔
177
                                return "", e
2✔
178
                        }
2✔
179
                }
180
        }
181
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc