mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-20 11:55:18 +00:00
65aaa607ab
Change made by: - running "gvt fetch" on each of the packages mentioned in Godeps/Godeps.json - `rm -rf Godeps` - tweaking the build scripts to not mention Godeps - tweaking the build scripts to test `./lib/...`, `./cmd/...` explicitly (to avoid testing vendor) - tweaking the build scripts to not juggle GOPATH for Godeps and instead set GO15VENDOREXPERIMENT. This also results in some updated packages at the same time I bet. Building with Go 1.3 and 1.4 still *works* but won't use our vendored dependencies - the user needs to have the actual packages in their GOPATH then, which they'll get with a normal "go get". Building with Go 1.6+ will get our vendored dependencies by default even when not using our build script, which is nice. By doing this we gain some freedom in that we can pick and choose manually what to include in vendor, as it's not based on just dependency analysis of our own code. This is also a risk as we might pick up dependencies we are unaware of, as the build may work locally with those packages present in GOPATH. On the other hand the build server will detect this as it has no packages in it's GOPATH beyond what is included in the repo. Recommended tool to manage dependencies is github.com/FiloSottile/gvt.
554 lines
13 KiB
Go
554 lines
13 KiB
Go
// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
|
|
// All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
package cache
|
|
|
|
import (
|
|
"math/rand"
|
|
"runtime"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
type int32o int32
|
|
|
|
func (o *int32o) acquire() {
|
|
if atomic.AddInt32((*int32)(o), 1) != 1 {
|
|
panic("BUG: invalid ref")
|
|
}
|
|
}
|
|
|
|
func (o *int32o) Release() {
|
|
if atomic.AddInt32((*int32)(o), -1) != 0 {
|
|
panic("BUG: invalid ref")
|
|
}
|
|
}
|
|
|
|
type releaserFunc struct {
|
|
fn func()
|
|
value Value
|
|
}
|
|
|
|
func (r releaserFunc) Release() {
|
|
if r.fn != nil {
|
|
r.fn()
|
|
}
|
|
}
|
|
|
|
func set(c *Cache, ns, key uint64, value Value, charge int, relf func()) *Handle {
|
|
return c.Get(ns, key, func() (int, Value) {
|
|
if relf != nil {
|
|
return charge, releaserFunc{relf, value}
|
|
}
|
|
return charge, value
|
|
})
|
|
}
|
|
|
|
func TestCacheMap(t *testing.T) {
|
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
|
|
|
nsx := []struct {
|
|
nobjects, nhandles, concurrent, repeat int
|
|
}{
|
|
{10000, 400, 50, 3},
|
|
{100000, 1000, 100, 10},
|
|
}
|
|
|
|
var (
|
|
objects [][]int32o
|
|
handles [][]unsafe.Pointer
|
|
)
|
|
|
|
for _, x := range nsx {
|
|
objects = append(objects, make([]int32o, x.nobjects))
|
|
handles = append(handles, make([]unsafe.Pointer, x.nhandles))
|
|
}
|
|
|
|
c := NewCache(nil)
|
|
|
|
wg := new(sync.WaitGroup)
|
|
var done int32
|
|
|
|
for ns, x := range nsx {
|
|
for i := 0; i < x.concurrent; i++ {
|
|
wg.Add(1)
|
|
go func(ns, i, repeat int, objects []int32o, handles []unsafe.Pointer) {
|
|
defer wg.Done()
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
for j := len(objects) * repeat; j >= 0; j-- {
|
|
key := uint64(r.Intn(len(objects)))
|
|
h := c.Get(uint64(ns), key, func() (int, Value) {
|
|
o := &objects[key]
|
|
o.acquire()
|
|
return 1, o
|
|
})
|
|
if v := h.Value().(*int32o); v != &objects[key] {
|
|
t.Fatalf("#%d invalid value: want=%p got=%p", ns, &objects[key], v)
|
|
}
|
|
if objects[key] != 1 {
|
|
t.Fatalf("#%d invalid object %d: %d", ns, key, objects[key])
|
|
}
|
|
if !atomic.CompareAndSwapPointer(&handles[r.Intn(len(handles))], nil, unsafe.Pointer(h)) {
|
|
h.Release()
|
|
}
|
|
}
|
|
}(ns, i, x.repeat, objects[ns], handles[ns])
|
|
}
|
|
|
|
go func(handles []unsafe.Pointer) {
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
for atomic.LoadInt32(&done) == 0 {
|
|
i := r.Intn(len(handles))
|
|
h := (*Handle)(atomic.LoadPointer(&handles[i]))
|
|
if h != nil && atomic.CompareAndSwapPointer(&handles[i], unsafe.Pointer(h), nil) {
|
|
h.Release()
|
|
}
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
}(handles[ns])
|
|
}
|
|
|
|
go func() {
|
|
handles := make([]*Handle, 100000)
|
|
for atomic.LoadInt32(&done) == 0 {
|
|
for i := range handles {
|
|
handles[i] = c.Get(999999999, uint64(i), func() (int, Value) {
|
|
return 1, 1
|
|
})
|
|
}
|
|
for _, h := range handles {
|
|
h.Release()
|
|
}
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
atomic.StoreInt32(&done, 1)
|
|
|
|
for _, handles0 := range handles {
|
|
for i := range handles0 {
|
|
h := (*Handle)(atomic.LoadPointer(&handles0[i]))
|
|
if h != nil && atomic.CompareAndSwapPointer(&handles0[i], unsafe.Pointer(h), nil) {
|
|
h.Release()
|
|
}
|
|
}
|
|
}
|
|
|
|
for ns, objects0 := range objects {
|
|
for i, o := range objects0 {
|
|
if o != 0 {
|
|
t.Fatalf("invalid object #%d.%d: ref=%d", ns, i, o)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCacheMap_NodesAndSize(t *testing.T) {
|
|
c := NewCache(nil)
|
|
if c.Nodes() != 0 {
|
|
t.Errorf("invalid nodes counter: want=%d got=%d", 0, c.Nodes())
|
|
}
|
|
if c.Size() != 0 {
|
|
t.Errorf("invalid size counter: want=%d got=%d", 0, c.Size())
|
|
}
|
|
set(c, 0, 1, 1, 1, nil)
|
|
set(c, 0, 2, 2, 2, nil)
|
|
set(c, 1, 1, 3, 3, nil)
|
|
set(c, 2, 1, 4, 1, nil)
|
|
if c.Nodes() != 4 {
|
|
t.Errorf("invalid nodes counter: want=%d got=%d", 4, c.Nodes())
|
|
}
|
|
if c.Size() != 7 {
|
|
t.Errorf("invalid size counter: want=%d got=%d", 4, c.Size())
|
|
}
|
|
}
|
|
|
|
func TestLRUCache_Capacity(t *testing.T) {
|
|
c := NewCache(NewLRU(10))
|
|
if c.Capacity() != 10 {
|
|
t.Errorf("invalid capacity: want=%d got=%d", 10, c.Capacity())
|
|
}
|
|
set(c, 0, 1, 1, 1, nil).Release()
|
|
set(c, 0, 2, 2, 2, nil).Release()
|
|
set(c, 1, 1, 3, 3, nil).Release()
|
|
set(c, 2, 1, 4, 1, nil).Release()
|
|
set(c, 2, 2, 5, 1, nil).Release()
|
|
set(c, 2, 3, 6, 1, nil).Release()
|
|
set(c, 2, 4, 7, 1, nil).Release()
|
|
set(c, 2, 5, 8, 1, nil).Release()
|
|
if c.Nodes() != 7 {
|
|
t.Errorf("invalid nodes counter: want=%d got=%d", 7, c.Nodes())
|
|
}
|
|
if c.Size() != 10 {
|
|
t.Errorf("invalid size counter: want=%d got=%d", 10, c.Size())
|
|
}
|
|
c.SetCapacity(9)
|
|
if c.Capacity() != 9 {
|
|
t.Errorf("invalid capacity: want=%d got=%d", 9, c.Capacity())
|
|
}
|
|
if c.Nodes() != 6 {
|
|
t.Errorf("invalid nodes counter: want=%d got=%d", 6, c.Nodes())
|
|
}
|
|
if c.Size() != 8 {
|
|
t.Errorf("invalid size counter: want=%d got=%d", 8, c.Size())
|
|
}
|
|
}
|
|
|
|
func TestCacheMap_NilValue(t *testing.T) {
|
|
c := NewCache(NewLRU(10))
|
|
h := c.Get(0, 0, func() (size int, value Value) {
|
|
return 1, nil
|
|
})
|
|
if h != nil {
|
|
t.Error("cache handle is non-nil")
|
|
}
|
|
if c.Nodes() != 0 {
|
|
t.Errorf("invalid nodes counter: want=%d got=%d", 0, c.Nodes())
|
|
}
|
|
if c.Size() != 0 {
|
|
t.Errorf("invalid size counter: want=%d got=%d", 0, c.Size())
|
|
}
|
|
}
|
|
|
|
func TestLRUCache_GetLatency(t *testing.T) {
|
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
|
|
|
const (
|
|
concurrentSet = 30
|
|
concurrentGet = 3
|
|
duration = 3 * time.Second
|
|
delay = 3 * time.Millisecond
|
|
maxkey = 100000
|
|
)
|
|
|
|
var (
|
|
set, getHit, getAll int32
|
|
getMaxLatency, getDuration int64
|
|
)
|
|
|
|
c := NewCache(NewLRU(5000))
|
|
wg := &sync.WaitGroup{}
|
|
until := time.Now().Add(duration)
|
|
for i := 0; i < concurrentSet; i++ {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
for time.Now().Before(until) {
|
|
c.Get(0, uint64(r.Intn(maxkey)), func() (int, Value) {
|
|
time.Sleep(delay)
|
|
atomic.AddInt32(&set, 1)
|
|
return 1, 1
|
|
}).Release()
|
|
}
|
|
}(i)
|
|
}
|
|
for i := 0; i < concurrentGet; i++ {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
for {
|
|
mark := time.Now()
|
|
if mark.Before(until) {
|
|
h := c.Get(0, uint64(r.Intn(maxkey)), nil)
|
|
latency := int64(time.Now().Sub(mark))
|
|
m := atomic.LoadInt64(&getMaxLatency)
|
|
if latency > m {
|
|
atomic.CompareAndSwapInt64(&getMaxLatency, m, latency)
|
|
}
|
|
atomic.AddInt64(&getDuration, latency)
|
|
if h != nil {
|
|
atomic.AddInt32(&getHit, 1)
|
|
h.Release()
|
|
}
|
|
atomic.AddInt32(&getAll, 1)
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
getAvglatency := time.Duration(getDuration) / time.Duration(getAll)
|
|
t.Logf("set=%d getHit=%d getAll=%d getMaxLatency=%v getAvgLatency=%v",
|
|
set, getHit, getAll, time.Duration(getMaxLatency), getAvglatency)
|
|
|
|
if getAvglatency > delay/3 {
|
|
t.Errorf("get avg latency > %v: got=%v", delay/3, getAvglatency)
|
|
}
|
|
}
|
|
|
|
func TestLRUCache_HitMiss(t *testing.T) {
|
|
cases := []struct {
|
|
key uint64
|
|
value string
|
|
}{
|
|
{1, "vvvvvvvvv"},
|
|
{100, "v1"},
|
|
{0, "v2"},
|
|
{12346, "v3"},
|
|
{777, "v4"},
|
|
{999, "v5"},
|
|
{7654, "v6"},
|
|
{2, "v7"},
|
|
{3, "v8"},
|
|
{9, "v9"},
|
|
}
|
|
|
|
setfin := 0
|
|
c := NewCache(NewLRU(1000))
|
|
for i, x := range cases {
|
|
set(c, 0, x.key, x.value, len(x.value), func() {
|
|
setfin++
|
|
}).Release()
|
|
for j, y := range cases {
|
|
h := c.Get(0, y.key, nil)
|
|
if j <= i {
|
|
// should hit
|
|
if h == nil {
|
|
t.Errorf("case '%d' iteration '%d' is miss", i, j)
|
|
} else {
|
|
if x := h.Value().(releaserFunc).value.(string); x != y.value {
|
|
t.Errorf("case '%d' iteration '%d' has invalid value got '%s', want '%s'", i, j, x, y.value)
|
|
}
|
|
}
|
|
} else {
|
|
// should miss
|
|
if h != nil {
|
|
t.Errorf("case '%d' iteration '%d' is hit , value '%s'", i, j, h.Value().(releaserFunc).value.(string))
|
|
}
|
|
}
|
|
if h != nil {
|
|
h.Release()
|
|
}
|
|
}
|
|
}
|
|
|
|
for i, x := range cases {
|
|
finalizerOk := false
|
|
c.Delete(0, x.key, func() {
|
|
finalizerOk = true
|
|
})
|
|
|
|
if !finalizerOk {
|
|
t.Errorf("case %d delete finalizer not executed", i)
|
|
}
|
|
|
|
for j, y := range cases {
|
|
h := c.Get(0, y.key, nil)
|
|
if j > i {
|
|
// should hit
|
|
if h == nil {
|
|
t.Errorf("case '%d' iteration '%d' is miss", i, j)
|
|
} else {
|
|
if x := h.Value().(releaserFunc).value.(string); x != y.value {
|
|
t.Errorf("case '%d' iteration '%d' has invalid value got '%s', want '%s'", i, j, x, y.value)
|
|
}
|
|
}
|
|
} else {
|
|
// should miss
|
|
if h != nil {
|
|
t.Errorf("case '%d' iteration '%d' is hit, value '%s'", i, j, h.Value().(releaserFunc).value.(string))
|
|
}
|
|
}
|
|
if h != nil {
|
|
h.Release()
|
|
}
|
|
}
|
|
}
|
|
|
|
if setfin != len(cases) {
|
|
t.Errorf("some set finalizer may not be executed, want=%d got=%d", len(cases), setfin)
|
|
}
|
|
}
|
|
|
|
func TestLRUCache_Eviction(t *testing.T) {
|
|
c := NewCache(NewLRU(12))
|
|
o1 := set(c, 0, 1, 1, 1, nil)
|
|
set(c, 0, 2, 2, 1, nil).Release()
|
|
set(c, 0, 3, 3, 1, nil).Release()
|
|
set(c, 0, 4, 4, 1, nil).Release()
|
|
set(c, 0, 5, 5, 1, nil).Release()
|
|
if h := c.Get(0, 2, nil); h != nil { // 1,3,4,5,2
|
|
h.Release()
|
|
}
|
|
set(c, 0, 9, 9, 10, nil).Release() // 5,2,9
|
|
|
|
for _, key := range []uint64{9, 2, 5, 1} {
|
|
h := c.Get(0, key, nil)
|
|
if h == nil {
|
|
t.Errorf("miss for key '%d'", key)
|
|
} else {
|
|
if x := h.Value().(int); x != int(key) {
|
|
t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
|
|
}
|
|
h.Release()
|
|
}
|
|
}
|
|
o1.Release()
|
|
for _, key := range []uint64{1, 2, 5} {
|
|
h := c.Get(0, key, nil)
|
|
if h == nil {
|
|
t.Errorf("miss for key '%d'", key)
|
|
} else {
|
|
if x := h.Value().(int); x != int(key) {
|
|
t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
|
|
}
|
|
h.Release()
|
|
}
|
|
}
|
|
for _, key := range []uint64{3, 4, 9} {
|
|
h := c.Get(0, key, nil)
|
|
if h != nil {
|
|
t.Errorf("hit for key '%d'", key)
|
|
if x := h.Value().(int); x != int(key) {
|
|
t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
|
|
}
|
|
h.Release()
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLRUCache_Evict(t *testing.T) {
|
|
c := NewCache(NewLRU(6))
|
|
set(c, 0, 1, 1, 1, nil).Release()
|
|
set(c, 0, 2, 2, 1, nil).Release()
|
|
set(c, 1, 1, 4, 1, nil).Release()
|
|
set(c, 1, 2, 5, 1, nil).Release()
|
|
set(c, 2, 1, 6, 1, nil).Release()
|
|
set(c, 2, 2, 7, 1, nil).Release()
|
|
|
|
for ns := 0; ns < 3; ns++ {
|
|
for key := 1; key < 3; key++ {
|
|
if h := c.Get(uint64(ns), uint64(key), nil); h != nil {
|
|
h.Release()
|
|
} else {
|
|
t.Errorf("Cache.Get on #%d.%d return nil", ns, key)
|
|
}
|
|
}
|
|
}
|
|
|
|
if ok := c.Evict(0, 1); !ok {
|
|
t.Error("first Cache.Evict on #0.1 return false")
|
|
}
|
|
if ok := c.Evict(0, 1); ok {
|
|
t.Error("second Cache.Evict on #0.1 return true")
|
|
}
|
|
if h := c.Get(0, 1, nil); h != nil {
|
|
t.Errorf("Cache.Get on #0.1 return non-nil: %v", h.Value())
|
|
}
|
|
|
|
c.EvictNS(1)
|
|
if h := c.Get(1, 1, nil); h != nil {
|
|
t.Errorf("Cache.Get on #1.1 return non-nil: %v", h.Value())
|
|
}
|
|
if h := c.Get(1, 2, nil); h != nil {
|
|
t.Errorf("Cache.Get on #1.2 return non-nil: %v", h.Value())
|
|
}
|
|
|
|
c.EvictAll()
|
|
for ns := 0; ns < 3; ns++ {
|
|
for key := 1; key < 3; key++ {
|
|
if h := c.Get(uint64(ns), uint64(key), nil); h != nil {
|
|
t.Errorf("Cache.Get on #%d.%d return non-nil: %v", ns, key, h.Value())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLRUCache_Delete(t *testing.T) {
|
|
delFuncCalled := 0
|
|
delFunc := func() {
|
|
delFuncCalled++
|
|
}
|
|
|
|
c := NewCache(NewLRU(2))
|
|
set(c, 0, 1, 1, 1, nil).Release()
|
|
set(c, 0, 2, 2, 1, nil).Release()
|
|
|
|
if ok := c.Delete(0, 1, delFunc); !ok {
|
|
t.Error("Cache.Delete on #1 return false")
|
|
}
|
|
if h := c.Get(0, 1, nil); h != nil {
|
|
t.Errorf("Cache.Get on #1 return non-nil: %v", h.Value())
|
|
}
|
|
if ok := c.Delete(0, 1, delFunc); ok {
|
|
t.Error("Cache.Delete on #1 return true")
|
|
}
|
|
|
|
h2 := c.Get(0, 2, nil)
|
|
if h2 == nil {
|
|
t.Error("Cache.Get on #2 return nil")
|
|
}
|
|
if ok := c.Delete(0, 2, delFunc); !ok {
|
|
t.Error("(1) Cache.Delete on #2 return false")
|
|
}
|
|
if ok := c.Delete(0, 2, delFunc); !ok {
|
|
t.Error("(2) Cache.Delete on #2 return false")
|
|
}
|
|
|
|
set(c, 0, 3, 3, 1, nil).Release()
|
|
set(c, 0, 4, 4, 1, nil).Release()
|
|
c.Get(0, 2, nil).Release()
|
|
|
|
for key := 2; key <= 4; key++ {
|
|
if h := c.Get(0, uint64(key), nil); h != nil {
|
|
h.Release()
|
|
} else {
|
|
t.Errorf("Cache.Get on #%d return nil", key)
|
|
}
|
|
}
|
|
|
|
h2.Release()
|
|
if h := c.Get(0, 2, nil); h != nil {
|
|
t.Errorf("Cache.Get on #2 return non-nil: %v", h.Value())
|
|
}
|
|
|
|
if delFuncCalled != 4 {
|
|
t.Errorf("delFunc isn't called 4 times: got=%d", delFuncCalled)
|
|
}
|
|
}
|
|
|
|
func TestLRUCache_Close(t *testing.T) {
|
|
relFuncCalled := 0
|
|
relFunc := func() {
|
|
relFuncCalled++
|
|
}
|
|
delFuncCalled := 0
|
|
delFunc := func() {
|
|
delFuncCalled++
|
|
}
|
|
|
|
c := NewCache(NewLRU(2))
|
|
set(c, 0, 1, 1, 1, relFunc).Release()
|
|
set(c, 0, 2, 2, 1, relFunc).Release()
|
|
|
|
h3 := set(c, 0, 3, 3, 1, relFunc)
|
|
if h3 == nil {
|
|
t.Error("Cache.Get on #3 return nil")
|
|
}
|
|
if ok := c.Delete(0, 3, delFunc); !ok {
|
|
t.Error("Cache.Delete on #3 return false")
|
|
}
|
|
|
|
c.Close()
|
|
|
|
if relFuncCalled != 3 {
|
|
t.Errorf("relFunc isn't called 3 times: got=%d", relFuncCalled)
|
|
}
|
|
if delFuncCalled != 1 {
|
|
t.Errorf("delFunc isn't called 1 times: got=%d", delFuncCalled)
|
|
}
|
|
}
|