Skip to content

Commit 215b2db

Browse files
committed
Use muesli for cache
1 parent 626ade2 commit 215b2db

File tree

8 files changed

+56
-45
lines changed

8 files changed

+56
-45
lines changed

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ require (
2626
github.com/invopop/jsonschema v0.13.0 // indirect
2727
github.com/josharian/intern v1.0.0 // indirect
2828
github.com/mailru/easyjson v0.7.7 // indirect
29-
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 // indirect
3029
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
3130
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
3231
go.yaml.in/yaml/v3 v3.0.4 // indirect

pkg/github/issues_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ func Test_GetIssue(t *testing.T) {
120120
} `graphql:"repository(owner: $owner, name: $name)"`
121121
}{},
122122
map[string]any{
123-
"owner": githubv4.String("owner"),
124-
"name": githubv4.String("repo"),
123+
"owner": githubv4.String("github"),
124+
"name": githubv4.String("github-mcp-server"),
125125
"username": githubv4.String("testuser"),
126126
},
127127
githubv4mock.DataResponse(map[string]any{
@@ -136,8 +136,8 @@ func Test_GetIssue(t *testing.T) {
136136
),
137137
requestArgs: map[string]interface{}{
138138
"method": "get",
139-
"owner": "owner",
140-
"repo": "repo",
139+
"owner": "github",
140+
"repo": "github-mcp-server",
141141
"issue_number": float64(42),
142142
},
143143
expectedIssue: mockIssue,

pkg/lockdown/lockdown.go

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,12 @@ import (
66
"log/slog"
77
"strings"
88
"sync"
9-
"sync/atomic"
109
"time"
1110

1211
"github.com/muesli/cache2go"
1312
"github.com/shurcooL/githubv4"
1413
)
1514

16-
var cacheNameCounter atomic.Uint64
17-
1815
// RepoAccessCache caches repository metadata related to lockdown checks so that
1916
// multiple tools can reuse the same access information safely across goroutines.
2017
type RepoAccessCache struct {
@@ -28,7 +25,6 @@ type RepoAccessCache struct {
2825
type repoAccessCacheEntry struct {
2926
isPrivate bool
3027
knownUsers map[string]bool // normalized login -> has push access
31-
ready bool
3228
}
3329

3430
const defaultRepoAccessTTL = 5 * time.Minute
@@ -55,7 +51,7 @@ func WithLogger(logger *slog.Logger) RepoAccessOption {
5551
// client. The cache is safe for concurrent use.
5652
func NewRepoAccessCache(client *githubv4.Client, opts ...RepoAccessOption) *RepoAccessCache {
5753
// Use a unique cache name for each instance to avoid sharing state between tests
58-
cacheName := fmt.Sprintf("repoAccess-%d", cacheNameCounter.Add(1))
54+
cacheName := "repo-access-cache"
5955
c := &RepoAccessCache{
6056
client: client,
6157
cache: cache2go.Cache(cacheName),
@@ -77,16 +73,16 @@ func (c *RepoAccessCache) SetTTL(ttl time.Duration) {
7773
defer c.mu.Unlock()
7874
c.ttl = ttl
7975
c.logInfo("repo access cache TTL updated", "ttl", ttl)
80-
76+
8177
// Collect all current entries
8278
entries := make(map[interface{}]*repoAccessCacheEntry)
8379
c.cache.Foreach(func(key interface{}, item *cache2go.CacheItem) {
8480
entries[key] = item.Data().(*repoAccessCacheEntry)
8581
})
86-
82+
8783
// Flush the cache
8884
c.cache.Flush()
89-
85+
9086
// Re-add all entries with the new TTL
9187
for key, entry := range entries {
9288
c.cache.Add(key, ttl, entry)
@@ -119,16 +115,14 @@ func (c *RepoAccessCache) GetRepoAccessInfo(ctx context.Context, username, owner
119115
userKey := strings.ToLower(username)
120116
c.mu.Lock()
121117
defer c.mu.Unlock()
122-
118+
123119
// Try to get entry from cache - this will keep the item alive if it exists
124120
cacheItem, err := c.cache.Value(key)
125121
if err == nil {
126122
entry := cacheItem.Data().(*repoAccessCacheEntry)
127-
if entry.ready {
128-
if cachedHasPush, known := entry.knownUsers[userKey]; known {
129-
c.logDebug("repo access cache hit", "owner", owner, "repo", repo, "user", username)
130-
return entry.isPrivate, cachedHasPush, nil
131-
}
123+
if cachedHasPush, known := entry.knownUsers[userKey]; known {
124+
c.logDebug("repo access cache hit", "owner", owner, "repo", repo, "user", username)
125+
return entry.isPrivate, cachedHasPush, nil
132126
}
133127
// Entry exists but user not in knownUsers, need to query
134128
}
@@ -139,26 +133,22 @@ func (c *RepoAccessCache) GetRepoAccessInfo(ctx context.Context, username, owner
139133
return false, false, queryErr
140134
}
141135

142-
// Get or create entry - don't use Value() here to avoid keeping alive unnecessarily
136+
// Repo access info retrieved, update or create cache entry
143137
var entry *repoAccessCacheEntry
144138
if err == nil && cacheItem != nil {
145-
// Entry already existed, just update it
146139
entry = cacheItem.Data().(*repoAccessCacheEntry)
147-
} else {
148-
// Create new entry
149-
entry = &repoAccessCacheEntry{
150-
knownUsers: make(map[string]bool),
151-
}
140+
entry.knownUsers[userKey] = hasPush
141+
return entry.isPrivate, entry.knownUsers[userKey], nil
142+
}
143+
144+
// Create new entry
145+
entry = &repoAccessCacheEntry{
146+
knownUsers: map[string]bool{userKey: hasPush},
147+
isPrivate: isPrivate,
152148
}
153-
154-
entry.ready = true
155-
entry.isPrivate = isPrivate
156-
entry.knownUsers[userKey] = hasPush
157-
158-
// Add or update the entry in cache with TTL
159149
c.cache.Add(key, c.ttl, entry)
160150

161-
return isPrivate, hasPush, nil
151+
return entry.isPrivate, entry.knownUsers[userKey], nil
162152
}
163153

164154
func (c *RepoAccessCache) queryRepoAccessInfo(ctx context.Context, username, owner, repo string) (bool, bool, error) {
@@ -207,14 +197,6 @@ func cacheKey(owner, repo string) string {
207197
return fmt.Sprintf("%s/%s", strings.ToLower(owner), strings.ToLower(repo))
208198
}
209199

210-
func splitKey(key string) (string, string) {
211-
owner, rest, found := strings.Cut(key, "/")
212-
if !found {
213-
return key, ""
214-
}
215-
return owner, rest
216-
}
217-
218200
func (c *RepoAccessCache) logDebug(msg string, args ...any) {
219201
if c != nil && c.logger != nil {
220202
c.logger.Debug(msg, args...)

pkg/lockdown/lockdown_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,13 @@ func TestRepoAccessCacheTTLDisabled(t *testing.T) {
115115
ctx := t.Context()
116116
t.Parallel()
117117

118-
cache, transport := newMockRepoAccessCache(t, 0)
118+
// make sure cache TTL is sufficiently large to avoid evictions during the test
119+
cache, transport := newMockRepoAccessCache(t, 1000*time.Millisecond)
119120

120121
requireAccess(ctx, t, cache)
121122
requireAccess(ctx, t, cache)
122123
require.EqualValues(t, 1, transport.CallCount())
123124

124-
time.Sleep(20 * time.Millisecond)
125-
126125
requireAccess(ctx, t, cache)
127126
require.EqualValues(t, 1, transport.CallCount())
128127
}
@@ -131,7 +130,7 @@ func TestRepoAccessCacheSetTTLReschedulesExistingEntry(t *testing.T) {
131130
ctx := t.Context()
132131
t.Parallel()
133132

134-
cache, transport := newMockRepoAccessCache(t, 0)
133+
cache, transport := newMockRepoAccessCache(t, 10*time.Millisecond)
135134

136135
requireAccess(ctx, t, cache)
137136
require.EqualValues(t, 1, transport.CallCount())

third-party-licenses.darwin.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Some packages may only be included on certain architectures or operating systems
2828
- [github.com/mark3labs/mcp-go](https://pkg.go.dev/github.com/mark3labs/mcp-go) ([MIT](https://github.com/mark3labs/mcp-go/blob/v0.36.0/LICENSE))
2929
- [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md))
3030
- [github.com/migueleliasweb/go-github-mock/src/mock](https://pkg.go.dev/github.com/migueleliasweb/go-github-mock/src/mock) ([MIT](https://github.com/migueleliasweb/go-github-mock/blob/v1.3.0/LICENSE))
31+
- [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt))
3132
- [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE))
3233
- [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.11.0/LICENSE))
3334
- [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/48295856cce7/LICENSE))

third-party-licenses.linux.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Some packages may only be included on certain architectures or operating systems
2828
- [github.com/mark3labs/mcp-go](https://pkg.go.dev/github.com/mark3labs/mcp-go) ([MIT](https://github.com/mark3labs/mcp-go/blob/v0.36.0/LICENSE))
2929
- [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md))
3030
- [github.com/migueleliasweb/go-github-mock/src/mock](https://pkg.go.dev/github.com/migueleliasweb/go-github-mock/src/mock) ([MIT](https://github.com/migueleliasweb/go-github-mock/blob/v1.3.0/LICENSE))
31+
- [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt))
3132
- [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE))
3233
- [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.11.0/LICENSE))
3334
- [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/48295856cce7/LICENSE))

third-party-licenses.windows.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Some packages may only be included on certain architectures or operating systems
2929
- [github.com/mark3labs/mcp-go](https://pkg.go.dev/github.com/mark3labs/mcp-go) ([MIT](https://github.com/mark3labs/mcp-go/blob/v0.36.0/LICENSE))
3030
- [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md))
3131
- [github.com/migueleliasweb/go-github-mock/src/mock](https://pkg.go.dev/github.com/migueleliasweb/go-github-mock/src/mock) ([MIT](https://github.com/migueleliasweb/go-github-mock/blob/v1.3.0/LICENSE))
32+
- [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt))
3233
- [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE))
3334
- [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.11.0/LICENSE))
3435
- [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/48295856cce7/LICENSE))
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Copyright (c) 2012, Radu Ioan Fericean
2+
2013-2017, Christian Muehlhaeuser <[email protected]>
3+
All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
8+
Redistributions of source code must retain the above copyright notice, this
9+
list of conditions and the following disclaimer.
10+
11+
Redistributions in binary form must reproduce the above copyright notice, this
12+
list of conditions and the following disclaimer in the documentation and/or
13+
other materials provided with the distribution.
14+
15+
Neither the name of Radu Ioan Fericean nor the names of its contributors may be
16+
used to endorse or promote products derived from this software without specific
17+
prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

0 commit comments

Comments
 (0)