Merge pull request #544 from erizocosmico/fix/race-condition-object-lru

fix race condition on ObjectLRU
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 @@
 
 import (
 	"container/list"
+	"sync"
 
 	"gopkg.in/src-d/go-git.v4/plumbing"
 )
@@ -14,6 +15,7 @@
 	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 @@
 // 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 @@
 // 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 @@
 
 // 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 @@
 	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