| 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 allows multiple appengine handlers to coordinate best-effort | 5 // Package memlock allows multiple appengine handlers to coordinate best-effort |
| 6 // mutual execution via memcache. "best-effort" here means "best-effort"... | 6 // mutual execution via memcache. "best-effort" here means "best-effort"... |
| 7 // memcache is not reliable. However, colliding on memcache is a lot cheaper | 7 // memcache is not reliable. However, colliding on memcache is a lot cheaper |
| 8 // than, for example, colliding with datastore transactions. | 8 // than, for example, colliding with datastore transactions. |
| 9 package memlock | 9 package memlock |
| 10 | 10 |
| 11 import ( | 11 import ( |
| 12 "bytes" | 12 "bytes" |
| 13 "errors" | 13 "errors" |
| 14 "infra/gae/libs/wrapper" | 14 "infra/gae/libs/wrapper" |
| 15 "infra/libs/clock" |
| 15 "sync/atomic" | 16 "sync/atomic" |
| 16 "time" | 17 "time" |
| 17 | 18 |
| 18 "github.com/luci/luci-go/common/logging" | 19 "github.com/luci/luci-go/common/logging" |
| 19 "golang.org/x/net/context" | 20 "golang.org/x/net/context" |
| 20 | 21 |
| 21 "appengine/memcache" | 22 "appengine/memcache" |
| 22 ) | 23 ) |
| 23 | 24 |
| 24 // ErrFailedToLock is returned from TryWithLock when it fails to obtain a lock | 25 // ErrFailedToLock is returned from TryWithLock when it fails to obtain a lock |
| (...skipping 26 matching lines...) Expand all Loading... |
| 51 // sucessful. The `check` function can be used within f to see if the lock is | 52 // sucessful. The `check` function can be used within f to see if the lock is |
| 52 // still held. | 53 // still held. |
| 53 // | 54 // |
| 54 // TryWithLock function returns ErrFailedToLock if it fails to obtain the lock, | 55 // TryWithLock function returns ErrFailedToLock if it fails to obtain the lock, |
| 55 // otherwise returns the error that f returns. | 56 // otherwise returns the error that f returns. |
| 56 // | 57 // |
| 57 // `key` is the memcache key to use (i.e. the name of the lock). Clients locking | 58 // `key` is the memcache key to use (i.e. the name of the lock). Clients locking |
| 58 // the same data must use the same key. clientID is the unique identifier for | 59 // the same data must use the same key. clientID is the unique identifier for |
| 59 // this client (lock-holder). If it's empty then TryWithLock() will return | 60 // this client (lock-holder). If it's empty then TryWithLock() will return |
| 60 // ErrEmptyClientID. | 61 // ErrEmptyClientID. |
| 61 func TryWithLock(c context.Context, key, clientID string, f func(check func() bo
ol) error) error { | 62 func TryWithLock(ctx context.Context, key, clientID string, f func(check func()
bool) error) error { |
| 62 if len(clientID) == 0 { | 63 if len(clientID) == 0 { |
| 63 return ErrEmptyClientID | 64 return ErrEmptyClientID |
| 64 } | 65 } |
| 65 | 66 |
| 66 » c = logging.SetField(c, "key", key) | 67 » ctx = logging.SetField(ctx, "key", key) |
| 67 » c = logging.SetField(c, "clientID", clientID) | 68 » ctx = logging.SetField(ctx, "clientID", clientID) |
| 68 » log := logging.Get(c) | 69 » log := logging.Get(ctx) |
| 69 » mc := wrapper.GetMC(c) | 70 » mc := wrapper.GetMC(ctx) |
| 70 | 71 |
| 71 key = memlockKeyPrefix + key | 72 key = memlockKeyPrefix + key |
| 72 cid := []byte(clientID) | 73 cid := []byte(clientID) |
| 73 | 74 |
| 74 // checkAnd gets the current value from memcache, and then attempts to d
o the | 75 // checkAnd gets the current value from memcache, and then attempts to d
o the |
| 75 // checkOp (which can either be `refresh` or `release`). These pieces of | 76 // checkOp (which can either be `refresh` or `release`). These pieces of |
| 76 // functionality are necessarially intertwined, because CAS only works w
ith | 77 // functionality are necessarially intertwined, because CAS only works w
ith |
| 77 // the exact-same *Item which was returned from a Get. | 78 // the exact-same *Item which was returned from a Get. |
| 78 // | 79 // |
| 79 // refresh will attempt to CAS the item with the same content to reset i
t's | 80 // refresh will attempt to CAS the item with the same content to reset i
t's |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 144 // if we do. It will stop doing this when either stopChan is activated (
e.g. | 145 // if we do. It will stop doing this when either stopChan is activated (
e.g. |
| 145 // the user's function returns) or we lose the lock (memcache flake, etc
.). | 146 // the user's function returns) or we lose the lock (memcache flake, etc
.). |
| 146 go func() { | 147 go func() { |
| 147 defer close(stoppedChan) | 148 defer close(stoppedChan) |
| 148 | 149 |
| 149 checkLoop: | 150 checkLoop: |
| 150 for { | 151 for { |
| 151 select { | 152 select { |
| 152 case <-stopChan: | 153 case <-stopChan: |
| 153 break checkLoop | 154 break checkLoop |
| 154 » » » case <-time.After(delay): | 155 » » » case <-clock.Get(ctx).After(delay): |
| 155 } | 156 } |
| 156 if !checkAnd(refresh) { | 157 if !checkAnd(refresh) { |
| 157 atomic.StoreUint32(&held, 0) | 158 atomic.StoreUint32(&held, 0) |
| 158 log.Warningf("lost lock: %s", err) | 159 log.Warningf("lost lock: %s", err) |
| 159 » » » » break | 160 » » » » return |
| 160 } | 161 } |
| 161 } | 162 } |
| 162 | 163 |
| 163 checkAnd(release) | 164 checkAnd(release) |
| 164 atomic.StoreUint32(&held, 0) | 165 atomic.StoreUint32(&held, 0) |
| 165 }() | 166 }() |
| 166 | 167 |
| 167 return f(func() bool { return atomic.LoadUint32(&held) == 1 }) | 168 return f(func() bool { return atomic.LoadUint32(&held) == 1 }) |
| 168 } | 169 } |
| OLD | NEW |