internal/fetchdatasource: cache resolved latest version

After successfully fetching a module at latest, cache it at its
resolved version.  This avoids a second fetch when the frontend
redirects "latest".

Change-Id: I127029e7818205f7ef3b74e8723aac753f53a896
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/347550
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/internal/fetchdatasource/fetchdatasource.go b/internal/fetchdatasource/fetchdatasource.go
index e7e38c4..c0a335c 100644
--- a/internal/fetchdatasource/fetchdatasource.go
+++ b/internal/fetchdatasource/fetchdatasource.go
@@ -89,10 +89,10 @@
 
 // getModule gets the module at the given path and version. It first checks the
 // cache, and if it isn't there it then tries to fetch it.
-func (ds *FetchDataSource) getModule(ctx context.Context, modulePath, version string) (_ *internal.Module, err error) {
-	defer derrors.Wrap(&err, "FetchDataSource.getModule(%q, %q)", modulePath, version)
+func (ds *FetchDataSource) getModule(ctx context.Context, modulePath, vers string) (_ *internal.Module, err error) {
+	defer derrors.Wrap(&err, "FetchDataSource.getModule(%q, %q)", modulePath, vers)
 
-	mod, err := ds.cacheGet(modulePath, version)
+	mod, err := ds.cacheGet(modulePath, vers)
 	if mod != nil || err != nil {
 		return mod, err
 	}
@@ -100,7 +100,7 @@
 	// There can be a benign race here, where two goroutines both fetch the same
 	// module. At worst some work will be duplicated, but if that turns out to
 	// be a problem we could use golang.org/x/sync/singleflight.
-	m, err := ds.fetch(ctx, modulePath, version)
+	m, err := ds.fetch(ctx, modulePath, vers)
 	if m != nil && ds.opts.ProxyClientForLatest != nil {
 		// Use the go.mod file at the raw latest version to fill in deprecation
 		// and retraction information. Ignore any problems getting the
@@ -113,7 +113,12 @@
 
 	// Cache both successes and failures, but not cancellations.
 	if !errors.Is(err, context.Canceled) {
-		ds.cachePut(modulePath, version, m, err)
+		ds.cachePut(modulePath, vers, m, err)
+		// Cache the resolved version of "latest" too. A useful optimization
+		// because the frontend redirects "latest", resulting in another fetch.
+		if m != nil && vers == version.Latest {
+			ds.cachePut(modulePath, m.Version, m, err)
+		}
 	}
 	return m, err
 }