| Index: build_scheduler/go/db/cache.go
|
| diff --git a/build_scheduler/go/db/cache.go b/build_scheduler/go/db/cache.go
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..54668137c7f07015ca62fe16860f379fd2102b5d
|
| --- /dev/null
|
| +++ b/build_scheduler/go/db/cache.go
|
| @@ -0,0 +1,146 @@
|
| +package db
|
| +
|
| +import (
|
| + "sync"
|
| + "time"
|
| +)
|
| +
|
| +type BuildCache struct {
|
| + builds map[string]*Build
|
| + buildsByCommit map[string]map[string]*Build
|
| + db DB
|
| + lastUpdate time.Time
|
| + mtx sync.RWMutex
|
| + queryId string
|
| +}
|
| +
|
| +// GetBuildsForCommits retrieves all builds which included[1] each of the
|
| +// given commits. Returns a map whose keys are commit hashes and values are
|
| +// sub-maps whose keys are builder names and values are builds.
|
| +//
|
| +// 1) Blamelist calculation is outside the scope of the BuildCache, but the
|
| +// implied assumption here is that there is at most one build for each
|
| +// builder which has a given commit in its blamelist. The user is responsible
|
| +// for inserting builds into the database so that this invariant is
|
| +// maintained. Generally, a more recent build will "steal" commits from an
|
| +// earlier build's blamelist, if the blamelists overlap. There are three
|
| +// cases to consider:
|
| +// 1. The newer build ran at a newer commit than the older build. Its
|
| +// blamelist consists of all commits not covered by the previous build,
|
| +// and therefore does not overlap with the older build's blamelist.
|
| +// 2. The newer build ran at the same commit as the older build. Its
|
| +// blamelist is the same as the previous build's blamelist, and
|
| +// therefore it "steals" all commits from the previous build, whose
|
| +// blamelist becomes empty.
|
| +// 3. The newer build ran at a commit which was in the previous build's
|
| +// blamelist. Its blamelist consists of the commits in the previous
|
| +// build's blamelist which it also covered. Those commits move out of
|
| +// the previous build's blamelist and into the newer build's blamelist.
|
| +func (c *BuildCache) GetBuildsForCommits(commits []string) (map[string]map[string]*Build, error) {
|
| + c.mtx.RLock()
|
| + defer c.mtx.RUnlock()
|
| +
|
| + rv := make(map[string]map[string]*Build, len(commits))
|
| + for _, commit := range commits {
|
| + if builds, ok := c.buildsByCommit[commit]; ok {
|
| + rv[commit] = make(map[string]*Build, len(builds))
|
| + for k, v := range builds {
|
| + rv[commit][k] = v.Copy()
|
| + }
|
| + } else {
|
| + rv[commit] = map[string]*Build{}
|
| + }
|
| + }
|
| + return rv, nil
|
| +}
|
| +
|
| +// update inserts the new/updated builds into the cache. Assumes the caller
|
| +// holds a lock.
|
| +func (c *BuildCache) update(builds []*Build) error {
|
| + for _, b := range builds {
|
| + // If we already know about this build, the blamelist might,
|
| + // have changed, so we need to remove it from buildsByCommit
|
| + // and re-insert where needed.
|
| + if old, ok := c.builds[b.Id]; ok {
|
| + for _, commit := range old.Commits {
|
| + delete(c.buildsByCommit[commit], b.Builder)
|
| + }
|
| + }
|
| +
|
| + // Insert the new build into the main map.
|
| + c.builds[b.Id] = b.Copy()
|
| +
|
| + // Insert the build into buildsByCommits.
|
| + for _, commit := range b.Commits {
|
| + if _, ok := c.buildsByCommit[commit]; !ok {
|
| + c.buildsByCommit[commit] = map[string]*Build{}
|
| + }
|
| + c.buildsByCommit[commit][b.Builder] = c.builds[b.Id]
|
| + }
|
| + }
|
| + return nil
|
| +}
|
| +
|
| +// Load new builds from the database.
|
| +func (c *BuildCache) Update() error {
|
| + now := time.Now()
|
| + newBuilds, err := c.db.GetModifiedBuilds(c.queryId)
|
| + c.mtx.Lock()
|
| + defer c.mtx.Unlock()
|
| + if err != nil {
|
| + if err.Error() == ErrUnknownId.Error() {
|
| + // The database may have restarted. Attempt to re-establish connection.
|
| + queryId, err := c.db.StartTrackingModifiedBuilds()
|
| + if err != nil {
|
| + return err
|
| + }
|
| + c.queryId = queryId
|
| + // We may have missed something. Query for builds since the last
|
| + // successful query.
|
| + builds, err := c.db.GetBuildsFromDateRange(c.lastUpdate, now)
|
| + if err != nil {
|
| + return err
|
| + }
|
| + if err := c.update(builds); err == nil {
|
| + c.lastUpdate = now
|
| + return nil
|
| + } else {
|
| + return err
|
| + }
|
| + } else {
|
| + return err
|
| + }
|
| + }
|
| + if err := c.update(newBuilds); err == nil {
|
| + c.lastUpdate = now
|
| + return nil
|
| + } else {
|
| + return err
|
| + }
|
| +}
|
| +
|
| +// NewBuildCache returns a local cache which provides more convenient views of
|
| +// build data than the database can provide.
|
| +func NewBuildCache(db DB, timePeriod time.Duration) (*BuildCache, error) {
|
| + queryId, err := db.StartTrackingModifiedBuilds()
|
| + if err != nil {
|
| + return nil, err
|
| + }
|
| + now := time.Now()
|
| + start := now.Add(-timePeriod)
|
| + builds, err := db.GetBuildsFromDateRange(start, now)
|
| + if err != nil {
|
| + return nil, err
|
| + }
|
| + bc := &BuildCache{
|
| + builds: map[string]*Build{},
|
| + buildsByCommit: map[string]map[string]*Build{},
|
| + db: db,
|
| + lastUpdate: now,
|
| + queryId: queryId,
|
| + }
|
| + if err := bc.update(builds); err != nil {
|
| + return nil, err
|
| + }
|
| + return bc, nil
|
| +}
|
|
|