OLD | NEW |
(Empty) | |
| 1 package db |
| 2 |
| 3 import ( |
| 4 "sync" |
| 5 "time" |
| 6 ) |
| 7 |
| 8 type BuildCache struct { |
| 9 builds map[string]*Build |
| 10 buildsByCommit map[string]map[string]*Build |
| 11 db DB |
| 12 lastUpdate time.Time |
| 13 mtx sync.RWMutex |
| 14 queryId string |
| 15 } |
| 16 |
| 17 // GetBuildsForCommits retrieves all builds which included[1] each of the |
| 18 // given commits. Returns a map whose keys are commit hashes and values are |
| 19 // sub-maps whose keys are builder names and values are builds. |
| 20 // |
| 21 // 1) Blamelist calculation is outside the scope of the BuildCache, but the |
| 22 // implied assumption here is that there is at most one build for each |
| 23 // builder which has a given commit in its blamelist. The user is responsible |
| 24 // for inserting builds into the database so that this invariant is |
| 25 // maintained. Generally, a more recent build will "steal" commits from an |
| 26 // earlier build's blamelist, if the blamelists overlap. There are three |
| 27 // cases to consider: |
| 28 // 1. The newer build ran at a newer commit than the older build. Its |
| 29 // blamelist consists of all commits not covered by the previous build, |
| 30 // and therefore does not overlap with the older build's blamelist. |
| 31 // 2. The newer build ran at the same commit as the older build. Its |
| 32 // blamelist is the same as the previous build's blamelist, and |
| 33 // therefore it "steals" all commits from the previous build, whose |
| 34 // blamelist becomes empty. |
| 35 // 3. The newer build ran at a commit which was in the previous build's |
| 36 // blamelist. Its blamelist consists of the commits in the previous |
| 37 // build's blamelist which it also covered. Those commits move out of |
| 38 // the previous build's blamelist and into the newer build's blamelist. |
| 39 func (c *BuildCache) GetBuildsForCommits(commits []string) (map[string]map[strin
g]*Build, error) { |
| 40 c.mtx.RLock() |
| 41 defer c.mtx.RUnlock() |
| 42 |
| 43 rv := make(map[string]map[string]*Build, len(commits)) |
| 44 for _, commit := range commits { |
| 45 if builds, ok := c.buildsByCommit[commit]; ok { |
| 46 rv[commit] = make(map[string]*Build, len(builds)) |
| 47 for k, v := range builds { |
| 48 rv[commit][k] = v.Copy() |
| 49 } |
| 50 } else { |
| 51 rv[commit] = map[string]*Build{} |
| 52 } |
| 53 } |
| 54 return rv, nil |
| 55 } |
| 56 |
| 57 // update inserts the new/updated builds into the cache. Assumes the caller |
| 58 // holds a lock. |
| 59 func (c *BuildCache) update(builds []*Build) error { |
| 60 for _, b := range builds { |
| 61 // If we already know about this build, the blamelist might, |
| 62 // have changed, so we need to remove it from buildsByCommit |
| 63 // and re-insert where needed. |
| 64 if old, ok := c.builds[b.Id]; ok { |
| 65 for _, commit := range old.Commits { |
| 66 delete(c.buildsByCommit[commit], b.Builder) |
| 67 } |
| 68 } |
| 69 |
| 70 // Insert the new build into the main map. |
| 71 c.builds[b.Id] = b.Copy() |
| 72 |
| 73 // Insert the build into buildsByCommits. |
| 74 for _, commit := range b.Commits { |
| 75 if _, ok := c.buildsByCommit[commit]; !ok { |
| 76 c.buildsByCommit[commit] = map[string]*Build{} |
| 77 } |
| 78 c.buildsByCommit[commit][b.Builder] = c.builds[b.Id] |
| 79 } |
| 80 } |
| 81 return nil |
| 82 } |
| 83 |
| 84 // Load new builds from the database. |
| 85 func (c *BuildCache) Update() error { |
| 86 now := time.Now() |
| 87 newBuilds, err := c.db.GetModifiedBuilds(c.queryId) |
| 88 c.mtx.Lock() |
| 89 defer c.mtx.Unlock() |
| 90 if err != nil { |
| 91 if err.Error() == ErrUnknownId.Error() { |
| 92 // The database may have restarted. Attempt to re-establ
ish connection. |
| 93 queryId, err := c.db.StartTrackingModifiedBuilds() |
| 94 if err != nil { |
| 95 return err |
| 96 } |
| 97 c.queryId = queryId |
| 98 // We may have missed something. Query for builds since
the last |
| 99 // successful query. |
| 100 builds, err := c.db.GetBuildsFromDateRange(c.lastUpdate,
now) |
| 101 if err != nil { |
| 102 return err |
| 103 } |
| 104 if err := c.update(builds); err == nil { |
| 105 c.lastUpdate = now |
| 106 return nil |
| 107 } else { |
| 108 return err |
| 109 } |
| 110 } else { |
| 111 return err |
| 112 } |
| 113 } |
| 114 if err := c.update(newBuilds); err == nil { |
| 115 c.lastUpdate = now |
| 116 return nil |
| 117 } else { |
| 118 return err |
| 119 } |
| 120 } |
| 121 |
| 122 // NewBuildCache returns a local cache which provides more convenient views of |
| 123 // build data than the database can provide. |
| 124 func NewBuildCache(db DB, timePeriod time.Duration) (*BuildCache, error) { |
| 125 queryId, err := db.StartTrackingModifiedBuilds() |
| 126 if err != nil { |
| 127 return nil, err |
| 128 } |
| 129 now := time.Now() |
| 130 start := now.Add(-timePeriod) |
| 131 builds, err := db.GetBuildsFromDateRange(start, now) |
| 132 if err != nil { |
| 133 return nil, err |
| 134 } |
| 135 bc := &BuildCache{ |
| 136 builds: map[string]*Build{}, |
| 137 buildsByCommit: map[string]map[string]*Build{}, |
| 138 db: db, |
| 139 lastUpdate: now, |
| 140 queryId: queryId, |
| 141 } |
| 142 if err := bc.update(builds); err != nil { |
| 143 return nil, err |
| 144 } |
| 145 return bc, nil |
| 146 } |
OLD | NEW |