Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2549)

Unified Diff: appengine/memlock/memlock_test.go

Issue 1399533003: Move appengine/memlock from infra.git (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-go@add_meta
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « appengine/memlock/memlock.go ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: appengine/memlock/memlock_test.go
diff --git a/appengine/memlock/memlock_test.go b/appengine/memlock/memlock_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4b9893daffd03154acad81e2c6c79f677fc115eb
--- /dev/null
+++ b/appengine/memlock/memlock_test.go
@@ -0,0 +1,188 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package memlock
+
+import (
+ "fmt"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/luci/gae/filter/featureBreaker"
+ "github.com/luci/gae/impl/memory"
+ "github.com/luci/gae/service/memcache"
+ "github.com/luci/luci-go/common/clock"
+ "github.com/luci/luci-go/common/clock/testclock"
+ . "github.com/smartystreets/goconvey/convey"
+ "golang.org/x/net/context"
+)
+
+type getBlockerFilter struct {
+ memcache.RawInterface
+ sync.Mutex
+
+ dropAll bool
+}
+
+func (f *getBlockerFilter) GetMulti(keys []string, cb memcache.RawItemCB) error {
+ f.Lock()
+ defer f.Unlock()
+ if f.dropAll {
+ for _, key := range keys {
+ cb(f.NewItem(key), nil)
+ }
+ return nil
+ }
+ return f.RawInterface.GetMulti(keys, cb)
+}
+
+func TestSimple(t *testing.T) {
+ // TODO(riannucci): Mock time.After so that we don't have to delay for real.
+
+ const key = memlockKeyPrefix + "testkey"
+
+ Convey("basic locking", t, func() {
+ start := time.Date(1986, time.October, 26, 1, 20, 00, 00, time.UTC)
+ ctx, clk := testclock.UseTime(context.Background(), start)
+ blocker := make(chan struct{})
+ clk.SetTimerCallback(func(time.Duration, clock.Timer) {
+ clk.Add(delay)
+ select {
+ case blocker <- struct{}{}:
+ 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 := memcache.Get(ctx)
+
+ Convey("fails to acquire when memcache is down", func() {
+ fb.BreakFeatures(nil, "AddMulti")
+ err := TryWithLock(ctx, "testkey", "id", func(context.Context) error {
+ // should never reach here
+ So(false, ShouldBeTrue)
+ return nil
+ })
+ So(err, ShouldEqual, ErrFailedToLock)
+ })
+
+ Convey("returns the inner error", func() {
+ toRet := fmt.Errorf("sup")
+ err := TryWithLock(ctx, "testkey", "id", func(context.Context) error {
+ return toRet
+ })
+ So(err, ShouldEqual, toRet)
+ })
+
+ Convey("returns the error", func() {
+ toRet := fmt.Errorf("sup")
+ 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(ctx context.Context) error {
+ isDone := func() bool {
+ select {
+ case <-ctx.Done():
+ return true
+ default:
+ return false
+ }
+ }
+
+ 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.
+ for i := 0; i < 64; i++ {
+ <-blocker
+ clk.Add(delay)
+ }
+ So(isDone(), ShouldBeFalse)
+ })
+
+ Convey("but sometimes we might lose it", func() {
+ Convey("because it was evicted", func() {
+ mc.Delete(key)
+ clk.Add(memcacheLockTime)
+ waitFalse(ctx)
+ })
+
+ Convey("or because of service issues", func() {
+ fb.BreakFeatures(nil, "CompareAndSwapMulti")
+ waitFalse(ctx)
+ })
+ })
+ return nil
+ })
+ So(err, ShouldBeNil)
+ })
+
+ Convey("can lose it when it gets stolen", func() {
+ gbf := &getBlockerFilter{}
+ ctx = memcache.AddRawFilters(ctx, func(_ context.Context, mc memcache.RawInterface) memcache.RawInterface {
+ gbf.RawInterface = mc
+ return gbf
+ })
+ mc = memcache.Get(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("can lose it when it gets preemptively released", func() {
+ gbf := &getBlockerFilter{}
+ ctx = memcache.AddRawFilters(ctx, func(_ context.Context, mc memcache.RawInterface) memcache.RawInterface {
+ gbf.RawInterface = mc
+ return gbf
+ })
+ ctx = context.WithValue(ctx, testStopCBKey, func() {
+ gbf.dropAll = true
+ })
+ mc = memcache.Get(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)
+ }
+ return nil
+ })
+ So(err, ShouldBeNil)
+ })
+
+ Convey("an empty context id is an error", func() {
+ So(TryWithLock(ctx, "testkey", "", nil), ShouldEqual, ErrEmptyClientID)
+ })
+ })
+}
« no previous file with comments | « appengine/memlock/memlock.go ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698