Skip to content

Commit a6b082c

Browse files
tatianabgopherbot
authored andcommitted
internal/pkgsite: make pkgsite cache thread-safe
Add a mutex to protect the cache and add a test that would fail with the "-race" flag without this fix. Change-Id: I13e2bbd4d6019f425959cd8c660b4b6123ca8162 Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/554795 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Damien Neil <[email protected]> Auto-Submit: Tatiana Bradley <[email protected]>
1 parent 7aaf26e commit a6b082c

File tree

3 files changed

+106
-21
lines changed

3 files changed

+106
-21
lines changed

internal/pkgsite/client.go

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"path/filepath"
1616
"strconv"
1717
"strings"
18+
"sync"
1819
"testing"
1920
"time"
2021

@@ -23,11 +24,8 @@ import (
2324
)
2425

2526
type Client struct {
26-
url string
27-
// Cache of module paths already seen.
28-
seen map[string]bool
29-
// Does seen contain all known modules?
30-
cacheComplete bool
27+
url string
28+
cache *cache
3129
}
3230

3331
func Default() *Client {
@@ -36,17 +34,13 @@ func Default() *Client {
3634

3735
func New(url string) *Client {
3836
return &Client{
39-
url: url,
40-
seen: make(map[string]bool),
41-
cacheComplete: false,
37+
url: url,
38+
cache: newCache(),
4239
}
4340
}
4441

4542
func (pc *Client) SetKnownModules(known []string) {
46-
for _, km := range known {
47-
pc.seen[km] = true
48-
}
49-
pc.cacheComplete = true
43+
pc.cache.setKnownModules(known)
5044
}
5145

5246
// Limit pkgsite requests to this many per second.
@@ -64,13 +58,11 @@ var pkgsiteURL = "https://pkg.go.dev"
6458
// Known reports whether pkgsite knows that modulePath actually refers
6559
// to a module.
6660
func (pc *Client) Known(ctx context.Context, modulePath string) (bool, error) {
67-
// If we've seen it before, no need to call.
68-
if b, ok := pc.seen[modulePath]; ok {
69-
return b, nil
70-
}
71-
if pc.cacheComplete {
72-
return false, nil
61+
found, ok := pc.cache.lookup(modulePath)
62+
if ok {
63+
return found, nil
7364
}
65+
7466
// Pause to maintain a max QPS.
7567
if err := pkgsiteRateLimiter.Wait(ctx); err != nil {
7668
return false, err
@@ -92,7 +84,7 @@ func (pc *Client) Known(ctx context.Context, modulePath string) (bool, error) {
9284
return false, err
9385
}
9486
known := res.StatusCode == http.StatusOK
95-
pc.seen[modulePath] = known
87+
pc.cache.add(modulePath, known)
9688
return known, nil
9789
}
9890

@@ -115,7 +107,10 @@ func readKnown(r io.Reader) (map[string]bool, error) {
115107
return seen, nil
116108
}
117109

118-
func (c *Client) writeKnown(w io.Writer) error {
110+
func (c *cache) writeKnown(w io.Writer) error {
111+
c.mu.Lock()
112+
defer c.mu.Unlock()
113+
119114
b, err := json.MarshalIndent(c.seen, "", " ")
120115
if err != nil {
121116
return err
@@ -164,7 +159,7 @@ func TestClient(t *testing.T, useRealPkgsite bool, rw io.ReadWriter) (*Client, e
164159
if useRealPkgsite {
165160
c := Default()
166161
t.Cleanup(func() {
167-
err := c.writeKnown(rw)
162+
err := c.cache.writeKnown(rw)
168163
if err != nil {
169164
t.Error(err)
170165
}
@@ -184,3 +179,54 @@ func TestClient(t *testing.T, useRealPkgsite bool, rw io.ReadWriter) (*Client, e
184179
t.Cleanup(s.Close)
185180
return New(s.URL), nil
186181
}
182+
183+
type cache struct {
184+
mu sync.Mutex
185+
// Module paths already seen.
186+
seen map[string]bool
187+
// Does the cache contain all known modules?
188+
complete bool
189+
}
190+
191+
func newCache() *cache {
192+
return &cache{
193+
seen: make(map[string]bool),
194+
complete: false,
195+
}
196+
}
197+
198+
func (c *cache) setKnownModules(known []string) {
199+
c.mu.Lock()
200+
defer c.mu.Unlock()
201+
202+
for _, km := range known {
203+
c.seen[km] = true
204+
}
205+
c.complete = true
206+
}
207+
208+
func (c *cache) lookup(modulePath string) (known bool, ok bool) {
209+
c.mu.Lock()
210+
defer c.mu.Unlock()
211+
212+
// In the cache.
213+
if known, ok := c.seen[modulePath]; ok {
214+
return known, true
215+
}
216+
217+
// Not in the cache, but the cache is complete, so this
218+
// module is not known.
219+
if c.complete {
220+
return false, true
221+
}
222+
223+
// We can't make a statement about this module.
224+
return false, false
225+
}
226+
227+
func (c *cache) add(modulePath string, known bool) {
228+
c.mu.Lock()
229+
defer c.mu.Unlock()
230+
231+
c.seen[modulePath] = known
232+
}

internal/pkgsite/client_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,38 @@ func TestKnown(t *testing.T) {
4343
})
4444
}
4545
}
46+
47+
func TestKnownParallel(t *testing.T) {
48+
ctx := context.Background()
49+
cf, err := CacheFile(t)
50+
if err != nil {
51+
t.Fatal(err)
52+
}
53+
pc, err := TestClient(t, *usePkgsite, cf)
54+
if err != nil {
55+
t.Fatal(err)
56+
}
57+
58+
for _, test := range []struct {
59+
name string
60+
in string
61+
want bool
62+
}{
63+
{name: "valid", in: "golang.org/x/mod", want: true},
64+
{name: "invalid", in: "github.com/something/something", want: false},
65+
} {
66+
test := test
67+
t.Run(test.name, func(t *testing.T) {
68+
t.Parallel()
69+
70+
got, err := pc.Known(ctx, test.in)
71+
if err != nil {
72+
t.Fatal(err)
73+
}
74+
75+
if got != test.want {
76+
t.Errorf("%s: got %t, want %t", test.in, got, test.want)
77+
}
78+
})
79+
}
80+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"github.com/something/something": false,
3+
"golang.org/x/mod": true
4+
}

0 commit comments

Comments
 (0)