blob: 3c2704553e9454547f6f32eca5cb2a935c01cc21 [file]
package sse
import (
"regexp"
"strings"
"sync"
"go.skia.org/infra/status/go/rpc"
)
// requestCache stores sets of inputs and outputs for task query and filtering
// requests. It is intended to be shared between multiple clients for a single
// update. It uses a two-level locking system to solve the race condition
// between multiple concurrent clients which would ordinarily call Get(), find
// no entry, perform expensive computation, and then call Put().
type requestCache struct {
m map[requestCacheKey]*requestCacheEntry
mtx sync.Mutex
}
func newRequestCache() *requestCache {
return &requestCache{
m: map[requestCacheKey]*requestCacheEntry{},
}
}
type requestCacheKey struct {
repoURL string
displayedCommits string
taskFilter taskFilter
taskSearch string
}
type requestCacheEntry struct {
tasks []*rpc.Task
hideTaskSpecs []string
ready bool
mtx sync.Mutex
}
func (c *requestCache) key(repoURL string, displayedCommits []*rpc.LongCommit, taskFilter taskFilter, taskSearch *regexp.Regexp) requestCacheKey {
taskSearchStr := ""
if taskSearch != nil {
taskSearchStr = taskSearch.String()
}
hashes := make([]string, 0, len(displayedCommits))
for _, commit := range displayedCommits {
hashes = append(hashes, commit.Hash)
}
return requestCacheKey{
repoURL: repoURL,
displayedCommits: strings.Join(hashes, ","),
taskFilter: taskFilter,
taskSearch: taskSearchStr,
}
}
// GetOrCache retrieves the cache entry associated with the given inputs, if it
// exists. Otherwise, it calls the given function to produce the outputs, stores
// them as a new entry in the cache, and returns the outputs. The function call
// is mutexed to ensure that it is only called once, even if multiple clients
// call GetOrCache concurrently with the same inputs.
func (c *requestCache) GetOrCache(repoURL string, displayedCommits []*rpc.LongCommit, taskFilter taskFilter, taskSearch *regexp.Regexp, fn func() ([]*rpc.Task, []string, error)) ([]*rpc.Task, []string, error) {
// Retrieve the cache entry, creating it if it does not exist.
key := c.key(repoURL, displayedCommits, taskFilter, taskSearch)
c.mtx.Lock()
entry, ok := c.m[key]
if !ok {
entry = &requestCacheEntry{}
c.m[key] = entry
}
c.mtx.Unlock()
entry.mtx.Lock()
defer entry.mtx.Unlock()
if !entry.ready {
tasks, hideTaskSpecs, err := fn()
if err != nil {
return nil, nil, err
}
entry.tasks = tasks
entry.hideTaskSpecs = hideTaskSpecs
entry.ready = true
}
return entry.tasks, entry.hideTaskSpecs, nil
}