OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package memlock | 5 package memlock |
6 | 6 |
7 import ( | 7 import ( |
8 "fmt" | 8 "fmt" |
9 » "runtime" | 9 » "sync" |
10 "testing" | 10 "testing" |
11 "time" | 11 "time" |
12 | 12 |
13 "infra/gae/libs/gae" | 13 "infra/gae/libs/gae" |
14 "infra/gae/libs/gae/filters/featureBreaker" | 14 "infra/gae/libs/gae/filters/featureBreaker" |
15 "infra/gae/libs/gae/memory" | 15 "infra/gae/libs/gae/memory" |
16 | 16 |
17 "github.com/luci/luci-go/common/clock" | 17 "github.com/luci/luci-go/common/clock" |
18 "github.com/luci/luci-go/common/clock/testclock" | 18 "github.com/luci/luci-go/common/clock/testclock" |
19 . "github.com/smartystreets/goconvey/convey" | 19 . "github.com/smartystreets/goconvey/convey" |
20 "golang.org/x/net/context" | 20 "golang.org/x/net/context" |
21 ) | 21 ) |
22 | 22 |
23 func init() { | 23 func init() { |
24 delay = time.Millisecond | 24 delay = time.Millisecond |
25 memcacheLockTime = time.Millisecond * 4 | 25 memcacheLockTime = time.Millisecond * 4 |
26 } | 26 } |
27 | 27 |
| 28 type getBlockerFilter struct { |
| 29 gae.Memcache |
| 30 sync.Mutex |
| 31 } |
| 32 |
| 33 func (f *getBlockerFilter) Get(key string) (gae.MCItem, error) { |
| 34 f.Lock() |
| 35 defer f.Unlock() |
| 36 return f.Memcache.Get(key) |
| 37 } |
| 38 |
28 func TestSimple(t *testing.T) { | 39 func TestSimple(t *testing.T) { |
29 // TODO(riannucci): Mock time.After so that we don't have to delay for r
eal. | 40 // TODO(riannucci): Mock time.After so that we don't have to delay for r
eal. |
30 | 41 |
31 const key = memlockKeyPrefix + "testkey" | 42 const key = memlockKeyPrefix + "testkey" |
32 | 43 |
33 Convey("basic locking", t, func() { | 44 Convey("basic locking", t, func() { |
34 start := time.Date(1986, time.October, 26, 1, 20, 00, 00, time.U
TC) | 45 start := time.Date(1986, time.October, 26, 1, 20, 00, 00, time.U
TC) |
35 ctx, clk := testclock.UseTime(context.Background(), start) | 46 ctx, clk := testclock.UseTime(context.Background(), start) |
36 blocker := make(chan struct{}) | 47 blocker := make(chan struct{}) |
37 clk.SetTimerCallback(func(clock.Timer) { | 48 clk.SetTimerCallback(func(clock.Timer) { |
38 clk.Add(delay) | 49 clk.Add(delay) |
39 select { | 50 select { |
40 case blocker <- struct{}{}: | 51 case blocker <- struct{}{}: |
41 default: | 52 default: |
42 } | 53 } |
43 }) | 54 }) |
| 55 waitFalse := func(ctx context.Context) { |
| 56 loop: |
| 57 for { |
| 58 select { |
| 59 case <-blocker: |
| 60 continue |
| 61 case <-ctx.Done(): |
| 62 break loop |
| 63 } |
| 64 } |
| 65 } |
| 66 |
44 ctx, fb := featureBreaker.FilterMC(memory.Use(ctx), nil) | 67 ctx, fb := featureBreaker.FilterMC(memory.Use(ctx), nil) |
45 mc := gae.GetMC(ctx) | 68 mc := gae.GetMC(ctx) |
46 | 69 |
47 Convey("fails to acquire when memcache is down", func() { | 70 Convey("fails to acquire when memcache is down", func() { |
48 fb.BreakFeatures(nil, "Add") | 71 fb.BreakFeatures(nil, "Add") |
49 » » » err := TryWithLock(ctx, "testkey", "id", func(check func
() bool) error { | 72 » » » err := TryWithLock(ctx, "testkey", "id", func(context.Co
ntext) error { |
50 // should never reach here | 73 // should never reach here |
51 So(false, ShouldBeTrue) | 74 So(false, ShouldBeTrue) |
52 return nil | 75 return nil |
53 }) | 76 }) |
54 So(err, ShouldEqual, ErrFailedToLock) | 77 So(err, ShouldEqual, ErrFailedToLock) |
55 }) | 78 }) |
56 | 79 |
57 Convey("returns the inner error", func() { | 80 Convey("returns the inner error", func() { |
58 toRet := fmt.Errorf("sup") | 81 toRet := fmt.Errorf("sup") |
59 » » » err := TryWithLock(ctx, "testkey", "id", func(check func
() bool) error { | 82 » » » err := TryWithLock(ctx, "testkey", "id", func(context.Co
ntext) error { |
60 return toRet | 83 return toRet |
61 }) | 84 }) |
62 So(err, ShouldEqual, toRet) | 85 So(err, ShouldEqual, toRet) |
63 }) | 86 }) |
64 | 87 |
65 Convey("returns the error", func() { | 88 Convey("returns the error", func() { |
66 toRet := fmt.Errorf("sup") | 89 toRet := fmt.Errorf("sup") |
67 » » » err := TryWithLock(ctx, "testkey", "id", func(check func
() bool) error { | 90 » » » err := TryWithLock(ctx, "testkey", "id", func(context.Co
ntext) error { |
68 return toRet | 91 return toRet |
69 }) | 92 }) |
70 So(err, ShouldEqual, toRet) | 93 So(err, ShouldEqual, toRet) |
71 }) | 94 }) |
72 | 95 |
73 Convey("can acquire when empty", func() { | 96 Convey("can acquire when empty", func() { |
74 » » » err := TryWithLock(ctx, "testkey", "id", func(check func
() bool) error { | 97 » » » err := TryWithLock(ctx, "testkey", "id", func(ctx contex
t.Context) error { |
75 » » » » So(check(), ShouldBeTrue) | 98 » » » » isDone := func() bool { |
| 99 » » » » » select { |
| 100 » » » » » case <-ctx.Done(): |
| 101 » » » » » » return true |
| 102 » » » » » default: |
| 103 » » » » » » return false |
| 104 » » » » » } |
| 105 » » » » } |
76 | 106 |
77 » » » » waitFalse := func() { | 107 » » » » So(isDone(), ShouldBeFalse) |
78 » » » » » <-blocker | |
79 » » » » » for i := 0; i < 3; i++ { | |
80 » » » » » » if check() { | |
81 » » » » » » » runtime.Gosched() | |
82 » » » » » » } | |
83 » » » » » } | |
84 » » » » » So(check(), ShouldBeFalse) | |
85 » » » » } | |
86 | 108 |
87 Convey("waiting for a while keeps refreshing the
lock", func() { | 109 Convey("waiting for a while keeps refreshing the
lock", func() { |
88 // simulate waiting for 64*delay time, a
nd ensuring that checkLoop | 110 // simulate waiting for 64*delay time, a
nd ensuring that checkLoop |
89 // runs that many times. | 111 // runs that many times. |
90 for i := 0; i < 64; i++ { | 112 for i := 0; i < 64; i++ { |
91 <-blocker | 113 <-blocker |
92 clk.Add(delay) | 114 clk.Add(delay) |
93 } | 115 } |
94 » » » » » So(check(), ShouldBeTrue) | 116 » » » » » So(isDone(), ShouldBeFalse) |
95 }) | 117 }) |
96 | 118 |
97 Convey("but sometimes we might lose it", func()
{ | 119 Convey("but sometimes we might lose it", func()
{ |
98 Convey("because it was evicted", func()
{ | 120 Convey("because it was evicted", func()
{ |
99 mc.Delete(key) | 121 mc.Delete(key) |
100 clk.Add(memcacheLockTime) | 122 clk.Add(memcacheLockTime) |
101 » » » » » » waitFalse() | 123 » » » » » » waitFalse(ctx) |
102 » » » » » }) | |
103 | |
104 » » » » » Convey("or because it was stolen", func(
) { | |
105 » » » » » » mc.Set(mc.NewItem(key).SetValue(
[]byte("wat"))) | |
106 » » » » » » waitFalse() | |
107 }) | 124 }) |
108 | 125 |
109 Convey("or because of service issues", f
unc() { | 126 Convey("or because of service issues", f
unc() { |
110 fb.BreakFeatures(nil, "CompareAn
dSwap") | 127 fb.BreakFeatures(nil, "CompareAn
dSwap") |
111 » » » » » » waitFalse() | 128 » » » » » » waitFalse(ctx) |
112 }) | 129 }) |
113 }) | 130 }) |
114 return nil | 131 return nil |
115 }) | 132 }) |
116 So(err, ShouldBeNil) | 133 So(err, ShouldBeNil) |
117 }) | 134 }) |
118 | 135 |
| 136 Convey("can lose it when it gets stolen", func() { |
| 137 gbf := &getBlockerFilter{} |
| 138 ctx = gae.AddMCFilters(ctx, func(_ context.Context, mc g
ae.Memcache) gae.Memcache { |
| 139 gbf.Memcache = mc |
| 140 return gbf |
| 141 }) |
| 142 mc = gae.GetMC(ctx) |
| 143 err := TryWithLock(ctx, "testkey", "id", func(ctx contex
t.Context) error { |
| 144 // simulate waiting for 64*delay time, and ensur
ing that checkLoop |
| 145 // runs that many times. |
| 146 for i := 0; i < 64; i++ { |
| 147 <-blocker |
| 148 clk.Add(delay) |
| 149 } |
| 150 gbf.Lock() |
| 151 mc.Set(mc.NewItem(key).SetValue([]byte("wat"))) |
| 152 gbf.Unlock() |
| 153 waitFalse(ctx) |
| 154 return nil |
| 155 }) |
| 156 So(err, ShouldBeNil) |
| 157 }) |
| 158 |
119 Convey("an empty context id is an error", func() { | 159 Convey("an empty context id is an error", func() { |
120 So(TryWithLock(ctx, "testkey", "", nil), ShouldEqual, Er
rEmptyClientID) | 160 So(TryWithLock(ctx, "testkey", "", nil), ShouldEqual, Er
rEmptyClientID) |
121 }) | 161 }) |
122 }) | 162 }) |
123 } | 163 } |
OLD | NEW |