| Index: go/src/infra/gae/libs/memlock/memlock_test.go
|
| diff --git a/go/src/infra/gae/libs/memlock/memlock_test.go b/go/src/infra/gae/libs/memlock/memlock_test.go
|
| index e6de4fb4b1a56eb81bda9c46d09d5d906df3cee0..ddf20972d2e9bd58f7da7db1b1e798a0358b6f8d 100644
|
| --- a/go/src/infra/gae/libs/memlock/memlock_test.go
|
| +++ b/go/src/infra/gae/libs/memlock/memlock_test.go
|
| @@ -6,7 +6,7 @@ package memlock
|
|
|
| import (
|
| "fmt"
|
| - "runtime"
|
| + "sync"
|
| "testing"
|
| "time"
|
|
|
| @@ -25,6 +25,17 @@ func init() {
|
| memcacheLockTime = time.Millisecond * 4
|
| }
|
|
|
| +type getBlockerFilter struct {
|
| + gae.Memcache
|
| + sync.Mutex
|
| +}
|
| +
|
| +func (f *getBlockerFilter) Get(key string) (gae.MCItem, error) {
|
| + f.Lock()
|
| + defer f.Unlock()
|
| + return f.Memcache.Get(key)
|
| +}
|
| +
|
| func TestSimple(t *testing.T) {
|
| // TODO(riannucci): Mock time.After so that we don't have to delay for real.
|
|
|
| @@ -41,12 +52,24 @@ func TestSimple(t *testing.T) {
|
| default:
|
| }
|
| })
|
| + waitFalse := func(ctx context.Context) {
|
| + loop:
|
| + for {
|
| + select {
|
| + case <-blocker:
|
| + continue
|
| + case <-ctx.Done():
|
| + break loop
|
| + }
|
| + }
|
| + }
|
| +
|
| ctx, fb := featureBreaker.FilterMC(memory.Use(ctx), nil)
|
| mc := gae.GetMC(ctx)
|
|
|
| Convey("fails to acquire when memcache is down", func() {
|
| fb.BreakFeatures(nil, "Add")
|
| - err := TryWithLock(ctx, "testkey", "id", func(check func() bool) error {
|
| + err := TryWithLock(ctx, "testkey", "id", func(context.Context) error {
|
| // should never reach here
|
| So(false, ShouldBeTrue)
|
| return nil
|
| @@ -56,7 +79,7 @@ func TestSimple(t *testing.T) {
|
|
|
| Convey("returns the inner error", func() {
|
| toRet := fmt.Errorf("sup")
|
| - err := TryWithLock(ctx, "testkey", "id", func(check func() bool) error {
|
| + err := TryWithLock(ctx, "testkey", "id", func(context.Context) error {
|
| return toRet
|
| })
|
| So(err, ShouldEqual, toRet)
|
| @@ -64,26 +87,25 @@ func TestSimple(t *testing.T) {
|
|
|
| Convey("returns the error", func() {
|
| toRet := fmt.Errorf("sup")
|
| - err := TryWithLock(ctx, "testkey", "id", func(check func() bool) error {
|
| + err := TryWithLock(ctx, "testkey", "id", func(context.Context) error {
|
| return toRet
|
| })
|
| So(err, ShouldEqual, toRet)
|
| })
|
|
|
| Convey("can acquire when empty", func() {
|
| - err := TryWithLock(ctx, "testkey", "id", func(check func() bool) error {
|
| - So(check(), ShouldBeTrue)
|
| -
|
| - waitFalse := func() {
|
| - <-blocker
|
| - for i := 0; i < 3; i++ {
|
| - if check() {
|
| - runtime.Gosched()
|
| - }
|
| + err := TryWithLock(ctx, "testkey", "id", func(ctx context.Context) error {
|
| + isDone := func() bool {
|
| + select {
|
| + case <-ctx.Done():
|
| + return true
|
| + default:
|
| + return false
|
| }
|
| - So(check(), ShouldBeFalse)
|
| }
|
|
|
| + So(isDone(), ShouldBeFalse)
|
| +
|
| Convey("waiting for a while keeps refreshing the lock", func() {
|
| // simulate waiting for 64*delay time, and ensuring that checkLoop
|
| // runs that many times.
|
| @@ -91,24 +113,19 @@ func TestSimple(t *testing.T) {
|
| <-blocker
|
| clk.Add(delay)
|
| }
|
| - So(check(), ShouldBeTrue)
|
| + So(isDone(), ShouldBeFalse)
|
| })
|
|
|
| Convey("but sometimes we might lose it", func() {
|
| Convey("because it was evicted", func() {
|
| mc.Delete(key)
|
| clk.Add(memcacheLockTime)
|
| - waitFalse()
|
| - })
|
| -
|
| - Convey("or because it was stolen", func() {
|
| - mc.Set(mc.NewItem(key).SetValue([]byte("wat")))
|
| - waitFalse()
|
| + waitFalse(ctx)
|
| })
|
|
|
| Convey("or because of service issues", func() {
|
| fb.BreakFeatures(nil, "CompareAndSwap")
|
| - waitFalse()
|
| + waitFalse(ctx)
|
| })
|
| })
|
| return nil
|
| @@ -116,6 +133,29 @@ func TestSimple(t *testing.T) {
|
| So(err, ShouldBeNil)
|
| })
|
|
|
| + Convey("can lose it when it gets stolen", func() {
|
| + gbf := &getBlockerFilter{}
|
| + ctx = gae.AddMCFilters(ctx, func(_ context.Context, mc gae.Memcache) gae.Memcache {
|
| + gbf.Memcache = mc
|
| + return gbf
|
| + })
|
| + mc = gae.GetMC(ctx)
|
| + err := TryWithLock(ctx, "testkey", "id", func(ctx context.Context) error {
|
| + // simulate waiting for 64*delay time, and ensuring that checkLoop
|
| + // runs that many times.
|
| + for i := 0; i < 64; i++ {
|
| + <-blocker
|
| + clk.Add(delay)
|
| + }
|
| + gbf.Lock()
|
| + mc.Set(mc.NewItem(key).SetValue([]byte("wat")))
|
| + gbf.Unlock()
|
| + waitFalse(ctx)
|
| + return nil
|
| + })
|
| + So(err, ShouldBeNil)
|
| + })
|
| +
|
| Convey("an empty context id is an error", func() {
|
| So(TryWithLock(ctx, "testkey", "", nil), ShouldEqual, ErrEmptyClientID)
|
| })
|
|
|