From 3fd398852745b218e4346a38c98d95e5e773751d Mon Sep 17 00:00:00 2001 From: Miguel Molina Date: Fri, 11 Aug 2017 13:21:14 +0200 Subject: fix race condition on ObjectLRU Signed-off-by: Miguel Molina --- plumbing/cache/object_lru.go | 11 +++++++++++ plumbing/cache/object_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/plumbing/cache/object_lru.go b/plumbing/cache/object_lru.go index e4c3160..e8414ab 100644 --- a/plumbing/cache/object_lru.go +++ b/plumbing/cache/object_lru.go @@ -2,6 +2,7 @@ package cache import ( "container/list" + "sync" "gopkg.in/src-d/go-git.v4/plumbing" ) @@ -14,6 +15,7 @@ type ObjectLRU struct { actualSize FileSize ll *list.List cache map[interface{}]*list.Element + mut sync.Mutex } // NewObjectLRU creates a new ObjectLRU with the given maximum size. The maximum @@ -26,6 +28,9 @@ func NewObjectLRU(maxSize FileSize) *ObjectLRU { // will be marked as used. Otherwise, it will be inserted. A single object might // be evicted to make room for the new object. func (c *ObjectLRU) Put(obj plumbing.EncodedObject) { + c.mut.Lock() + defer c.mut.Unlock() + if c.cache == nil { c.actualSize = 0 c.cache = make(map[interface{}]*list.Element, 1000) @@ -67,6 +72,9 @@ func (c *ObjectLRU) Put(obj plumbing.EncodedObject) { // Get returns an object by its hash. It marks the object as used. If the object // is not in the cache, (nil, false) will be returned. func (c *ObjectLRU) Get(k plumbing.Hash) (plumbing.EncodedObject, bool) { + c.mut.Lock() + defer c.mut.Unlock() + ee, ok := c.cache[k] if !ok { return nil, false @@ -78,6 +86,9 @@ func (c *ObjectLRU) Get(k plumbing.Hash) (plumbing.EncodedObject, bool) { // Clear the content of this object cache. func (c *ObjectLRU) Clear() { + c.mut.Lock() + defer c.mut.Unlock() + c.ll = nil c.cache = nil c.actualSize = 0 diff --git a/plumbing/cache/object_test.go b/plumbing/cache/object_test.go index 9359455..b38272f 100644 --- a/plumbing/cache/object_test.go +++ b/plumbing/cache/object_test.go @@ -1,7 +1,9 @@ package cache import ( + "fmt" "io" + "sync" "testing" "gopkg.in/src-d/go-git.v4/plumbing" @@ -67,6 +69,32 @@ func (s *ObjectSuite) TestClear(c *C) { c.Assert(obj, IsNil) } +func (s *ObjectSuite) TestConcurrentAccess(c *C) { + var wg sync.WaitGroup + + for i := 0; i < 1000; i++ { + wg.Add(3) + go func(i int) { + s.c.Put(newObject(fmt.Sprint(i), FileSize(i))) + wg.Done() + }(i) + + go func(i int) { + if i%30 == 0 { + s.c.Clear() + } + wg.Done() + }(i) + + go func(i int) { + s.c.Get(plumbing.NewHash(fmt.Sprint(i))) + wg.Done() + }(i) + } + + wg.Wait() +} + type dummyObject struct { hash plumbing.Hash size FileSize -- cgit