@@ -24,14 +24,13 @@ func bugCacheFilePath(repo repository.Repo) string {
// bugUpdated is a callback to trigger when the excerpt of a bug changed,
// that is each time a bug is updated
func (c *RepoCache) bugUpdated(id entity.Id) error {
- c.muBug.Lock()
-
- b, ok := c.bugs[id]
- if !ok {
- c.muBug.Unlock()
- panic("missing bug in the cache")
+ err := c.ensureBugLoaded(id)
+ if err != nil {
+ return err
}
+ c.muBug.Lock()
+ b, _ := c.bugs[id]
c.bugExcerpts[id] = NewBugExcerpt(b.bug, b.Snapshot())
c.muBug.Unlock()
@@ -106,38 +105,59 @@ func (c *RepoCache) writeBugCache() error {
// ResolveBugExcerpt retrieve a BugExcerpt matching the exact given id
func (c *RepoCache) ResolveBugExcerpt(id entity.Id) (*BugExcerpt, error) {
- c.muBug.RLock()
- defer c.muBug.RUnlock()
-
- e, ok := c.bugExcerpts[id]
- if !ok {
- return nil, bug.ErrBugNotExist
+ err := c.ensureBugLoaded(id)
+ if err != nil {
+ return nil, err
}
- return e, nil
+ c.muBug.RLock()
+ defer c.muBug.RUnlock()
+ return c.bugExcerpts[id], nil
}
// ResolveBug retrieve a bug matching the exact given id
func (c *RepoCache) ResolveBug(id entity.Id) (*BugCache, error) {
+ err := c.ensureBugLoaded(id)
+ if err != nil {
+ return nil, err
+ }
+
c.muBug.RLock()
- cached, ok := c.bugs[id]
- c.muBug.RUnlock()
- if ok {
- return cached, nil
+ defer c.muBug.RUnlock()
+ return c.bugs[id], nil
+}
+
+func (c *RepoCache) ensureBugLoaded(id entity.Id) error {
+ if c.presentBugs.Get(id) {
+ return nil
}
b, err := bug.ReadLocalBug(c.repo, id)
if err != nil {
- return nil, err
+ return err
}
- cached = NewBugCache(c, b)
+ bugCache := NewBugCache(c, b)
c.muBug.Lock()
- c.bugs[id] = cached
- c.muBug.Unlock()
+ if c.presentBugs.Len() == c.presentBugs.maxSize {
+ for _, id := range c.presentBugs.GetAll() {
+ if b := c.bugs[id]; !b.NeedCommit() {
+ b.mu.Lock()
+ c.presentBugs.Remove(id)
+ delete(c.bugExcerpts, id)
+ delete(c.bugs, id)
+ }
+ }
+ }
- return cached, nil
+ c.presentBugs.Add(id)
+ c.bugs[id] = bugCache
+ excerpt := NewBugExcerpt(b, bugCache.Snapshot()) // TODO: Is this needed?
+ c.bugExcerpts[id] = excerpt
+
+ c.muBug.Unlock()
+ return nil
}
// ResolveBugExcerptPrefix retrieve a BugExcerpt matching an id prefix. It fails if multiple
@@ -363,15 +383,38 @@ func (c *RepoCache) NewBugRaw(author *IdentityCache, unixTime int64, title strin
// RemoveBug removes a bug from the cache and repo given a bug id prefix
func (c *RepoCache) RemoveBug(prefix string) error {
b, err := c.ResolveBugPrefix(prefix)
+ if err != nil {
+ return err
+ }
+ err = c.ensureBugLoaded(b.Id())
if err != nil {
return err
}
+ c.muBug.Lock()
+ b.mu.Lock()
+ fmt.Println("got lock")
err = bug.RemoveBug(c.repo, b.Id())
-
+ if err != nil {
+ c.muBug.Unlock()
+ b.mu.Unlock()
+ return err
+ }
+ fmt.Println("noerr")
+ c.presentBugs.Remove(b.Id())
+ fmt.Println("removing1")
delete(c.bugs, b.Id())
+ fmt.Println("removed2")
delete(c.bugExcerpts, b.Id())
+ fmt.Println("unlocking")
+ c.muBug.Unlock()
return c.writeBugCache()
}
+
+// onEvict will update the bugs and bugExcerpts when a bug is evicted from the cache
+func (c *RepoCache) onEvict(id entity.Id) { // TODO: Do we need this?
+ delete(c.bugs, id)
+ delete(c.bugExcerpts, id)
+}
@@ -1,9 +1,7 @@
package cache
import (
- "fmt"
"testing"
- "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -181,13 +179,16 @@ func TestRemove(t *testing.T) {
rene, err := repoCache.NewIdentity("RenΓ© Descartes", "rene@descartes.fr")
require.NoError(t, err)
+ err = repoCache.SetUserIdentity(rene)
+ require.NoError(t, err)
+
for i := 0; i < 100; i++ {
- _, _, err := repoCache.NewBugRaw(rene, time.Now().Unix(), "title", fmt.Sprintf("message%v", i), nil, nil)
+ _, _, err := repoCache.NewBug("title", "message")
require.NoError(t, err)
}
// and one more for testing
- b1, _, err := repoCache.NewBugRaw(rene, time.Now().Unix(), "title", "message", nil, nil)
+ b1, _, err := repoCache.NewBug("title", "message")
require.NoError(t, err)
_, err = repoCache.Push("remoteA")
@@ -210,3 +211,70 @@ func TestRemove(t *testing.T) {
_, err = repoCache.ResolveBug(b1.Id())
assert.Error(t, bug.ErrBugNotExist, err)
}
+
+func TestCacheEviction(t *testing.T) {
+ repo := repository.CreateTestRepo(false)
+ repoCache, err := NewRepoCache(repo)
+ require.NoError(t, err)
+ repoCache.presentBugs.Resize(2)
+
+ require.Equal(t, 0, repoCache.presentBugs.Len())
+ require.Equal(t, 0, len(repoCache.bugs))
+ require.Equal(t, 0, len(repoCache.bugExcerpts))
+
+ // Generating some bugs
+ rene, err := repoCache.NewIdentity("RenΓ© Descartes", "rene@descartes.fr")
+ require.NoError(t, err)
+ err = repoCache.SetUserIdentity(rene)
+ require.NoError(t, err)
+
+ bug1, _, err := repoCache.NewBug("title", "message")
+ require.NoError(t, err)
+
+ checkBugPresence(t, repoCache, bug1, true)
+ require.Equal(t, 1, repoCache.presentBugs.Len())
+ require.Equal(t, 1, len(repoCache.bugs))
+ require.Equal(t, 1, len(repoCache.bugExcerpts))
+
+ bug2, _, err := repoCache.NewBug("title", "message")
+ require.NoError(t, err)
+
+ checkBugPresence(t, repoCache, bug1, true)
+ checkBugPresence(t, repoCache, bug2, true)
+ require.Equal(t, 2, repoCache.presentBugs.Len())
+ require.Equal(t, 2, len(repoCache.bugs))
+ require.Equal(t, 2, len(repoCache.bugExcerpts))
+
+ // Number of bugs should not exceed max size of lruCache, oldest one should be evicted
+ bug3, _, err := repoCache.NewBug("title", "message")
+ require.NoError(t, err)
+
+ checkBugPresence(t, repoCache, bug1, false)
+ checkBugPresence(t, repoCache, bug2, true)
+ checkBugPresence(t, repoCache, bug3, true)
+ require.Equal(t, 2, repoCache.presentBugs.Len())
+ require.Equal(t, 2, len(repoCache.bugs))
+ require.Equal(t, 2, len(repoCache.bugExcerpts))
+
+ // Accessing bug should update position in lruCache and therefore it should not be evicted
+ repoCache.presentBugs.Get(bug2.Id())
+ oldestId, _ := repoCache.presentBugs.GetOldest()
+ require.Equal(t, bug3.Id(), oldestId)
+
+ checkBugPresence(t, repoCache, bug1, false)
+ checkBugPresence(t, repoCache, bug2, true)
+ checkBugPresence(t, repoCache, bug3, true)
+ require.Equal(t, 2, repoCache.presentBugs.Len())
+ require.Equal(t, 2, len(repoCache.bugs))
+ require.Equal(t, 2, len(repoCache.bugExcerpts))
+}
+
+func checkBugPresence(t *testing.T, cache *RepoCache, bug *BugCache, presence bool) {
+ id := bug.Id()
+ require.Equal(t, presence, cache.presentBugs.Contains(id))
+ b, ok := cache.bugs[id]
+ require.Equal(t, presence, ok)
+ require.Equal(t, bug, b)
+ _, ok = cache.bugExcerpts[id]
+ require.Equal(t, presence, ok)
+}