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

Side by Side Diff: go/src/infra/gae/libs/mlock/mlock.go

Issue 986553002: A simple memcache lock for appengine. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@meta
Patch Set: Created 5 years, 9 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 unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 mlock
6
7 import (
8 "bytes"
9 "crypto/rand"
10 "fmt"
11 "sync/atomic"
12 "time"
13
14 "appengine"
15 "appengine/memcache"
16 )
17
18 var (
19 // ErrUnableToLock we failed to obtain the lock.
20 ErrUnableToLock = fmt.Errorf("mlock: unable to lock")
Vadim Sh. 2015/03/06 03:39:26 mlock is also syscall... maybe pick some different
iannucci 2015/03/07 02:27:52 done
21 )
22
23 // MemcacheLockTime is the expiration time of the memcache entry. If the lock
24 // is correctly released, then it will be released before this time. It is set
25 // to 64 seconds.
26 const MemcacheLockTime = (time.Second << 6) // 64 seconds
Vadim Sh. 2015/03/06 03:39:26 omg, why not "64 * time.Second" and remove two com
iannucci 2015/03/07 02:27:52 haha :P. It used to be `time.Duration(numSecs) * t
27
28 // MemcacheLock allows multiple appengine handlers to coordinate best-effort
29 // mutual execution via memcache. "best-effort" here means "best-effort"...
30 // memcache is not reliable. However, colliding on memcache is a lot cheaper
31 // than, for example, colliding with datastore transactions.
32 //
33 // Construct a new MemcacheLock with the New() method.
34 type MemcacheLock struct {
35 ctx appengine.Context
36 key string
37 clientID []byte
38
39 running uint32
40 }
41
42 // New creates a MemcacheLock. `key` is the memcache key to use. Clients locking
43 // the same data must use the same key. clientID is the unique identifier for
44 // this client (lock-holder). If it's empty then New() will generate a random
45 // one.
46 //
47 // Using a deterministid clientID (like the taskqueue task name, for example) ha s
48 // the benefit that on an error the re-tried taskqueue handler may be able to
49 // re-obtain the lock, assuming it wasn't released properly.
50 func New(c appengine.Context, key, clientID string) (*MemcacheLock, error) {
51 clientIDbytes := []byte(clientID)
52 if len(clientIDbytes) == 0 {
53 clientIDbytes = make([]byte, 32)
54 _, err := rand.Read(clientIDbytes)
55 if err != nil {
56 return nil, err
57 }
58 c.Debugf("mlock: generated clientId: %v", clientIDbytes)
59 }
60 return &MemcacheLock{ctx: c, key: key, clientID: clientIDbytes}, nil
61 }
62
63 // Obtain obtains the lock. Returns true iff we were able to obtain the lock.
64 func (m *MemcacheLock) Obtain() bool {
Vadim Sh. 2015/03/06 03:39:26 nit: TryLock() is more canonical name, imho. TryLo
Vadim Sh. 2015/03/06 03:46:30 For completness you can also implement "Lock()" th
iannucci 2015/03/07 02:27:52 SGTM
65 err := memcache.Add(m.ctx, &memcache.Item{
66 Key: m.key, Expiration: MemcacheLockTime, Value: m.clientID})
Vadim Sh. 2015/03/06 03:39:26 nit: namespace lock keys, e.g Key: "mlock:" + m.ke
iannucci 2015/03/07 02:27:52 done.
67 if err != nil {
68 if err != memcache.ErrNotStored {
69 m.ctx.Warningf("Error adding: %s", err)
70 }
71 if !m.checkAndRefresh(true) {
72 return false
73 }
74 }
75
76 atomic.StoreUint32(&m.running, 1)
77 go func() {
78 for m.Check() {
79 time.Sleep(time.Second)
Vadim Sh. 2015/03/06 03:39:26 this can be a problem if all goroutines must compl
iannucci 2015/03/07 02:27:52 OK, done. I should have done that from the beginni
80 if !m.checkAndRefresh(true) {
81 m.ctx.Warningf("lost lock (%s:%s): %s", m.key, s tring(m.clientID), err)
82 break
83 }
84 }
85 m.checkAndRefresh(false)
86 }()
87
88 return true
89 }
90
91 // Check ensures that we still have control of the lock. Returns true if we
Vadim Sh. 2015/03/06 03:39:26 nit: Check returns true if we ... "ensures" impli
iannucci 2015/03/07 02:27:53 good point
92 // probably still have the lock :)
93 func (m *MemcacheLock) Check() bool {
94 return atomic.LoadUint32(&m.running) == 1
95 }
96
97 // Release drops the lock (if we still own it, otherwise leaves it alone).
98 func (m *MemcacheLock) Release() {
99 m.checkAndRefresh(false)
100 atomic.StoreUint32(&m.running, 0)
101 }
102
103 func (m *MemcacheLock) checkAndRefresh(refresh bool) bool {
104 itm, err := memcache.Get(m.ctx, m.key)
105 if err != nil {
106 m.ctx.Warningf("mlock: error getting: %s", err)
107 return false
108 }
109
110 if len(itm.Value) != 0 && !bytes.Equal(itm.Value, m.clientID) {
111 m.ctx.Infof("mlock: lock owned by %s", itm.Value)
112 return false
113 }
114
115 var op string
116 if refresh {
117 itm.Value = m.clientID
118 itm.Expiration = MemcacheLockTime
119 op = "refresh"
Vadim Sh. 2015/03/06 03:39:26 nit: move "op" to argument instead of "refresh".
iannucci 2015/03/07 02:27:52 and then make it module-level constants? k :)
120 } else {
121 itm.Value = []byte{}
Vadim Sh. 2015/03/06 03:39:26 at this point you can compare itm.Value to empty b
iannucci 2015/03/07 02:27:52 Ah, sure. Done.
122 itm.Expiration = time.Second
123 op = "release"
124 }
125
126 err = memcache.CompareAndSwap(m.ctx, itm)
Vadim Sh. 2015/03/06 03:39:26 CompareAndSwap taking single argument blows my min
iannucci 2015/03/07 02:27:52 Yeah, it's a really weird API. AND! The Item objec
127 if err != nil {
128 m.ctx.Warningf("failed to %s lock (%s:%s): %s",
Vadim Sh. 2015/03/06 03:39:26 should a race condition on a lock (when compare an
iannucci 2015/03/07 02:27:52 shrug. It's true that it failed to lock though. At
129 op, m.key, string(m.clientID), err)
130 return false
131 }
132
133 m.ctx.Infof("%sed lock (%s:%s)", op, m.key, string(m.clientID))
Vadim Sh. 2015/03/06 03:39:26 clientID is ugly byte string. Perhaps %v instead o
iannucci 2015/03/07 02:27:52 it's usually not though. Maybe %q, then we'll see
134 return true
135 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698