Skip to content

Commit

Permalink
refresh site data in background when cache expiration is hit (#35)
Browse files Browse the repository at this point in the history
Previously, when cached site data expired, it would block the current request (and all other requests) on refreshing the site data (eg by downloading a new repository archive zip). Now it does that in the background. If the refresh fails, it clears the cache so that the problem is made user-visible on the next request (so that monitoring tools will detect the problem and the site won't continue silently serving stale data).

This will significantly improve page load times because users won't have to wait for the site data to be fetched each 5 min.
  • Loading branch information
sqs authored Dec 23, 2019
1 parent 61231fb commit 6f89f12
Showing 1 changed file with 22 additions and 6 deletions.
28 changes: 22 additions & 6 deletions cmd/docsite/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,14 @@ type versionedFileSystemURL struct {
url string

mu sync.Mutex
cache map[string]fileSystemCacheEntry
cache map[string]*fileSystemCacheEntry
}

type fileSystemCacheEntry struct {
fs http.FileSystem
at time.Time

refresh sync.Once // ensures only one attempt to refresh this entry is active
}

const fileSystemCacheTTL = 5 * time.Minute
Expand All @@ -250,19 +252,33 @@ func (fs *versionedFileSystemURL) OpenVersion(ctx context.Context, version strin

fs.mu.Lock()
if fs.cache == nil {
fs.cache = map[string]fileSystemCacheEntry{}
fs.cache = map[string]*fileSystemCacheEntry{}
}
e, ok := fs.cache[version]
if ok && time.Since(e.at) > fileSystemCacheTTL {
log.Printf("# Cached site data expired after %s, will re-download", fileSystemCacheTTL)
delete(fs.cache, version)
ok = false
log.Printf("# Cached site data for version %q expired after %s, refreshing in background", version, fileSystemCacheTTL)
go e.refresh.Do(func() {
ctx := context.Background() // use separate context because this runs in the background
if _, err := fs.fetchAndCacheVersion(ctx, version); err != nil {
log.Printf("# Error refreshing site data for version %q in background: %s", version, err)
// Cause the error to be user-visible on the next request so that external
// monitoring tools will detect the problem (and the site won't silently remain
// stale).
fs.mu.Lock()
delete(fs.cache, version)
fs.mu.Unlock()
return
}
})
}
fs.mu.Unlock()
if ok {
return e.fs, nil
}
return fs.fetchAndCacheVersion(ctx, version)
}

func (fs *versionedFileSystemURL) fetchAndCacheVersion(ctx context.Context, version string) (http.FileSystem, error) {
urlStr := fs.url
if strings.Contains(urlStr, "$VERSION") && strings.Contains(urlStr, "github") && !strings.Contains(urlStr, "refs/heads/$VERSION") {
return nil, fmt.Errorf("refusing to use insecure docsite configuration for multi-version-aware GitHub URLs: the URL pattern %q must include \"refs/heads/$VERSION\", not just \"$VERSION\" (see docsite README.md for more information)", urlStr)
Expand All @@ -280,7 +296,7 @@ func (fs *versionedFileSystemURL) OpenVersion(ctx context.Context, version strin
return nil, err
}
fs.mu.Lock()
fs.cache[version] = fileSystemCacheEntry{fs: vfs, at: time.Now()}
fs.cache[version] = &fileSystemCacheEntry{fs: vfs, at: time.Now()}
fs.mu.Unlock()
return vfs, nil
}
Expand Down

0 comments on commit 6f89f12

Please sign in to comment.