##// END OF EJS Templates
lock: while releasing, unlink lockfile even if the release function throws...
Siddharth Agarwal -
r23032:f484be02 default
parent child Browse files
Show More
@@ -1,154 +1,156 b''
1 1 # lock.py - simple advisory locking scheme for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import util, error
9 9 import errno, os, socket, time
10 10 import warnings
11 11
12 12 class lock(object):
13 13 '''An advisory lock held by one process to control access to a set
14 14 of files. Non-cooperating processes or incorrectly written scripts
15 15 can ignore Mercurial's locking scheme and stomp all over the
16 16 repository, so don't do that.
17 17
18 18 Typically used via localrepository.lock() to lock the repository
19 19 store (.hg/store/) or localrepository.wlock() to lock everything
20 20 else under .hg/.'''
21 21
22 22 # lock is symlink on platforms that support it, file on others.
23 23
24 24 # symlink is used because create of directory entry and contents
25 25 # are atomic even over nfs.
26 26
27 27 # old-style lock: symlink to pid
28 28 # new-style lock: symlink to hostname:pid
29 29
30 30 _host = None
31 31
32 32 def __init__(self, vfs, file, timeout=-1, releasefn=None, desc=None):
33 33 self.vfs = vfs
34 34 self.f = file
35 35 self.held = 0
36 36 self.timeout = timeout
37 37 self.releasefn = releasefn
38 38 self.desc = desc
39 39 self.postrelease = []
40 40 self.pid = os.getpid()
41 41 self.delay = self.lock()
42 42
43 43 def __del__(self):
44 44 if self.held:
45 45 warnings.warn("use lock.release instead of del lock",
46 46 category=DeprecationWarning,
47 47 stacklevel=2)
48 48
49 49 # ensure the lock will be removed
50 50 # even if recursive locking did occur
51 51 self.held = 1
52 52
53 53 self.release()
54 54
55 55 def lock(self):
56 56 timeout = self.timeout
57 57 while True:
58 58 try:
59 59 self.trylock()
60 60 return self.timeout - timeout
61 61 except error.LockHeld, inst:
62 62 if timeout != 0:
63 63 time.sleep(1)
64 64 if timeout > 0:
65 65 timeout -= 1
66 66 continue
67 67 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
68 68 inst.locker)
69 69
70 70 def trylock(self):
71 71 if self.held:
72 72 self.held += 1
73 73 return
74 74 if lock._host is None:
75 75 lock._host = socket.gethostname()
76 76 lockname = '%s:%s' % (lock._host, self.pid)
77 77 while not self.held:
78 78 try:
79 79 self.vfs.makelock(lockname, self.f)
80 80 self.held = 1
81 81 except (OSError, IOError), why:
82 82 if why.errno == errno.EEXIST:
83 83 locker = self.testlock()
84 84 if locker is not None:
85 85 raise error.LockHeld(errno.EAGAIN,
86 86 self.vfs.join(self.f), self.desc,
87 87 locker)
88 88 else:
89 89 raise error.LockUnavailable(why.errno, why.strerror,
90 90 why.filename, self.desc)
91 91
92 92 def testlock(self):
93 93 """return id of locker if lock is valid, else None.
94 94
95 95 If old-style lock, we cannot tell what machine locker is on.
96 96 with new-style lock, if locker is on this machine, we can
97 97 see if locker is alive. If locker is on this machine but
98 98 not alive, we can safely break lock.
99 99
100 100 The lock file is only deleted when None is returned.
101 101
102 102 """
103 103 try:
104 104 locker = self.vfs.readlock(self.f)
105 105 except (OSError, IOError), why:
106 106 if why.errno == errno.ENOENT:
107 107 return None
108 108 raise
109 109 try:
110 110 host, pid = locker.split(":", 1)
111 111 except ValueError:
112 112 return locker
113 113 if host != lock._host:
114 114 return locker
115 115 try:
116 116 pid = int(pid)
117 117 except ValueError:
118 118 return locker
119 119 if util.testpid(pid):
120 120 return locker
121 121 # if locker dead, break lock. must do this with another lock
122 122 # held, or can race and break valid lock.
123 123 try:
124 124 l = lock(self.vfs, self.f + '.break', timeout=0)
125 125 self.vfs.unlink(self.f)
126 126 l.release()
127 127 except error.LockError:
128 128 return locker
129 129
130 130 def release(self):
131 131 """release the lock and execute callback function if any
132 132
133 133 If the lock has been acquired multiple times, the actual release is
134 134 delayed to the last release call."""
135 135 if self.held > 1:
136 136 self.held -= 1
137 137 elif self.held == 1:
138 138 self.held = 0
139 139 if os.getpid() != self.pid:
140 140 # we forked, and are not the parent
141 141 return
142 if self.releasefn:
143 self.releasefn()
144 142 try:
145 self.vfs.unlink(self.f)
146 except OSError:
147 pass
143 if self.releasefn:
144 self.releasefn()
145 finally:
146 try:
147 self.vfs.unlink(self.f)
148 except OSError:
149 pass
148 150 for callback in self.postrelease:
149 151 callback()
150 152
151 153 def release(*locks):
152 154 for lock in locks:
153 155 if lock is not None:
154 156 lock.release()
@@ -1,41 +1,77 b''
1 1 #require unix-permissions no-root no-windows
2 2
3 3 Prepare
4 4
5 5 $ hg init a
6 6 $ echo a > a/a
7 7 $ hg -R a ci -A -m a
8 8 adding a
9 9
10 10 $ hg clone a b
11 11 updating to branch default
12 12 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
13 13
14 Test that raising an exception in the release function doesn't cause the lock to choke
15
16 $ cat > testlock.py << EOF
17 > from mercurial import cmdutil, error, util
18 >
19 > cmdtable = {}
20 > command = cmdutil.command(cmdtable)
21 >
22 > def acquiretestlock(repo, releaseexc):
23 > def unlock():
24 > if releaseexc:
25 > raise util.Abort('expected release exception')
26 > l = repo._lock(repo.vfs, 'testlock', False, unlock, None, 'test lock')
27 > return l
28 >
29 > @command('testlockexc')
30 > def testlockexc(ui, repo):
31 > testlock = acquiretestlock(repo, True)
32 > try:
33 > testlock.release()
34 > finally:
35 > try:
36 > testlock = acquiretestlock(repo, False)
37 > except error.LockHeld:
38 > raise util.Abort('lockfile on disk even after releasing!')
39 > testlock.release()
40 > EOF
41 $ cat >> $HGRCPATH << EOF
42 > [extensions]
43 > testlock=$TESTTMP/testlock.py
44 > EOF
45
46 $ hg -R b testlockexc
47 abort: expected release exception
48 [255]
49
14 50 One process waiting for another
15 51
16 52 $ cat > hooks.py << EOF
17 53 > import time
18 54 > def sleepone(**x): time.sleep(1)
19 55 > def sleephalf(**x): time.sleep(0.5)
20 56 > EOF
21 57 $ echo b > b/b
22 58 $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
23 59 $ hg -R b up -q --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf"
24 60 waiting for lock on working directory of b held by '*:*' (glob)
25 61 got lock after ? seconds (glob)
26 62 warning: ignoring unknown working parent d2ae7f538514!
27 63 $ wait
28 64 $ cat stdout
29 65 adding b
30 66
31 67 Pushing to a local read-only repo that can't be locked
32 68
33 69 $ chmod 100 a/.hg/store
34 70
35 71 $ hg -R b push a
36 72 pushing to a
37 73 searching for changes
38 74 abort: could not lock repository a: Permission denied
39 75 [255]
40 76
41 77 $ chmod 700 a/.hg/store
General Comments 0
You need to be logged in to leave comments. Login now