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