cmd/fetchlogs: refresh maintner data if a GoRevision appears to be missing

Most of the time, fetchlogs can get by with cached maintner data.
However, rarely the data may become too stale while fetching logs,
resulting in a log entry with an unknown GoRevision.

If that occurs, it seems preferable to pay the latency hit of
reloading the maintner data than to ignore log entries or fail
completely.

Change-Id: I16298448b3b67b7f23bc73cd316c4a0552a1abec
Reviewed-on: https://go-review.googlesource.com/c/build/+/408937
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Run-TryBot: Bryan Mills <bcmills@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/cmd/fetchlogs/fetchlogs.go b/cmd/fetchlogs/fetchlogs.go
index ef5197b..abc2157 100644
--- a/cmd/fetchlogs/fetchlogs.go
+++ b/cmd/fetchlogs/fetchlogs.go
@@ -142,9 +142,17 @@
 					}
 					var goDate time.Time
 					if rev.GoRevision != "" {
-						commit, err := goProject().GitCommit(rev.GoRevision)
+						commit, err := goProject(useCached).GitCommit(rev.GoRevision)
 						if err != nil {
-							log.Fatal("invalid GoRevision: ", err)
+							// A rare race is possible here: if a commit is added to the Go repo
+							// after the initial maintner load, and a dashboard test run completes
+							// for that commit before we're done fetching logs, the maintner data
+							// might not include that commit. To rule out that possibility, refresh
+							// the local maintner data before bailing out.
+							commit, err = goProject(forceRefresh).GitCommit(rev.GoRevision)
+							if err == nil {
+								log.Fatal("invalid GoRevision: ", err)
+							}
 						}
 						goDate = commit.CommitTime
 					}
@@ -252,7 +260,7 @@
 			// right away (in which case we can't hide any substantial latency) or not
 			// at all (in which case we shouldn't bother churning memory and disk
 			// pages to load it).
-			_ = goProject()
+			_ = goProject(useCached)
 		}()
 	}
 
@@ -346,7 +354,7 @@
 }
 
 var (
-	goProjectOnce   sync.Once
+	goProjectMu     sync.Mutex
 	cachedGoProject *maintner.GerritProject
 	goProjectErr    error
 )
@@ -365,16 +373,26 @@
 	return gp, nil
 }
 
-func goProject() *maintner.GerritProject {
-	goProjectOnce.Do(func() {
+func goProject(policy refreshPolicy) *maintner.GerritProject {
+	goProjectMu.Lock()
+	defer goProjectMu.Unlock()
+	if policy == forceRefresh || (cachedGoProject == nil && goProjectErr == nil) {
 		cachedGoProject, goProjectErr = getGoProject(context.Background())
-	})
+	}
+
 	if goProjectErr != nil {
 		log.Fatal(goProjectErr)
 	}
 	return cachedGoProject
 }
 
+type refreshPolicy int8
+
+const (
+	useCached refreshPolicy = iota
+	forceRefresh
+)
+
 // ensureDir creates directory name if it does not exist.
 func ensureDir(name string) {
 	err := os.MkdirAll(name, 0777)