##// END OF EJS Templates
lock: factor code to read lock into a separate function...
Siddharth Agarwal -
r26290:9664d32b default
parent child Browse files
Show More
@@ -1,167 +1,177 b''
1 # lock.py - simple advisory locking scheme for mercurial
1 # lock.py - simple advisory locking scheme for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import socket
12 import socket
13 import time
13 import time
14 import warnings
14 import warnings
15
15
16 from . import (
16 from . import (
17 error,
17 error,
18 util,
18 util,
19 )
19 )
20
20
21 class lock(object):
21 class lock(object):
22 '''An advisory lock held by one process to control access to a set
22 '''An advisory lock held by one process to control access to a set
23 of files. Non-cooperating processes or incorrectly written scripts
23 of files. Non-cooperating processes or incorrectly written scripts
24 can ignore Mercurial's locking scheme and stomp all over the
24 can ignore Mercurial's locking scheme and stomp all over the
25 repository, so don't do that.
25 repository, so don't do that.
26
26
27 Typically used via localrepository.lock() to lock the repository
27 Typically used via localrepository.lock() to lock the repository
28 store (.hg/store/) or localrepository.wlock() to lock everything
28 store (.hg/store/) or localrepository.wlock() to lock everything
29 else under .hg/.'''
29 else under .hg/.'''
30
30
31 # lock is symlink on platforms that support it, file on others.
31 # lock is symlink on platforms that support it, file on others.
32
32
33 # symlink is used because create of directory entry and contents
33 # symlink is used because create of directory entry and contents
34 # are atomic even over nfs.
34 # are atomic even over nfs.
35
35
36 # old-style lock: symlink to pid
36 # old-style lock: symlink to pid
37 # new-style lock: symlink to hostname:pid
37 # new-style lock: symlink to hostname:pid
38
38
39 _host = None
39 _host = None
40
40
41 def __init__(self, vfs, file, timeout=-1, releasefn=None, desc=None):
41 def __init__(self, vfs, file, timeout=-1, releasefn=None, desc=None):
42 self.vfs = vfs
42 self.vfs = vfs
43 self.f = file
43 self.f = file
44 self.held = 0
44 self.held = 0
45 self.timeout = timeout
45 self.timeout = timeout
46 self.releasefn = releasefn
46 self.releasefn = releasefn
47 self.desc = desc
47 self.desc = desc
48 self.postrelease = []
48 self.postrelease = []
49 self.pid = os.getpid()
49 self.pid = os.getpid()
50 self.delay = self.lock()
50 self.delay = self.lock()
51
51
52 def __del__(self):
52 def __del__(self):
53 if self.held:
53 if self.held:
54 warnings.warn("use lock.release instead of del lock",
54 warnings.warn("use lock.release instead of del lock",
55 category=DeprecationWarning,
55 category=DeprecationWarning,
56 stacklevel=2)
56 stacklevel=2)
57
57
58 # ensure the lock will be removed
58 # ensure the lock will be removed
59 # even if recursive locking did occur
59 # even if recursive locking did occur
60 self.held = 1
60 self.held = 1
61
61
62 self.release()
62 self.release()
63
63
64 def lock(self):
64 def lock(self):
65 timeout = self.timeout
65 timeout = self.timeout
66 while True:
66 while True:
67 try:
67 try:
68 self._trylock()
68 self._trylock()
69 return self.timeout - timeout
69 return self.timeout - timeout
70 except error.LockHeld as inst:
70 except error.LockHeld as inst:
71 if timeout != 0:
71 if timeout != 0:
72 time.sleep(1)
72 time.sleep(1)
73 if timeout > 0:
73 if timeout > 0:
74 timeout -= 1
74 timeout -= 1
75 continue
75 continue
76 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
76 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
77 inst.locker)
77 inst.locker)
78
78
79 def _trylock(self):
79 def _trylock(self):
80 if self.held:
80 if self.held:
81 self.held += 1
81 self.held += 1
82 return
82 return
83 if lock._host is None:
83 if lock._host is None:
84 lock._host = socket.gethostname()
84 lock._host = socket.gethostname()
85 lockname = '%s:%s' % (lock._host, self.pid)
85 lockname = '%s:%s' % (lock._host, self.pid)
86 retry = 5
86 retry = 5
87 while not self.held and retry:
87 while not self.held and retry:
88 retry -= 1
88 retry -= 1
89 try:
89 try:
90 self.vfs.makelock(lockname, self.f)
90 self.vfs.makelock(lockname, self.f)
91 self.held = 1
91 self.held = 1
92 except (OSError, IOError) as why:
92 except (OSError, IOError) as why:
93 if why.errno == errno.EEXIST:
93 if why.errno == errno.EEXIST:
94 locker = self.testlock()
94 locker = self.testlock()
95 if locker is not None:
95 if locker is not None:
96 raise error.LockHeld(errno.EAGAIN,
96 raise error.LockHeld(errno.EAGAIN,
97 self.vfs.join(self.f), self.desc,
97 self.vfs.join(self.f), self.desc,
98 locker)
98 locker)
99 else:
99 else:
100 raise error.LockUnavailable(why.errno, why.strerror,
100 raise error.LockUnavailable(why.errno, why.strerror,
101 why.filename, self.desc)
101 why.filename, self.desc)
102
102
103 def _readlock(self):
104 """read lock and return its value
105
106 Returns None if no lock exists, pid for old-style locks, and host:pid
107 for new-style locks.
108 """
109 try:
110 return self.vfs.readlock(self.f)
111 except (OSError, IOError) as why:
112 if why.errno == errno.ENOENT:
113 return None
114 raise
115
103 def testlock(self):
116 def testlock(self):
104 """return id of locker if lock is valid, else None.
117 """return id of locker if lock is valid, else None.
105
118
106 If old-style lock, we cannot tell what machine locker is on.
119 If old-style lock, we cannot tell what machine locker is on.
107 with new-style lock, if locker is on this machine, we can
120 with new-style lock, if locker is on this machine, we can
108 see if locker is alive. If locker is on this machine but
121 see if locker is alive. If locker is on this machine but
109 not alive, we can safely break lock.
122 not alive, we can safely break lock.
110
123
111 The lock file is only deleted when None is returned.
124 The lock file is only deleted when None is returned.
112
125
113 """
126 """
114 try:
127 locker = self._readlock()
115 locker = self.vfs.readlock(self.f)
128 if locker is None:
116 except (OSError, IOError) as why:
129 return None
117 if why.errno == errno.ENOENT:
118 return None
119 raise
120 try:
130 try:
121 host, pid = locker.split(":", 1)
131 host, pid = locker.split(":", 1)
122 except ValueError:
132 except ValueError:
123 return locker
133 return locker
124 if host != lock._host:
134 if host != lock._host:
125 return locker
135 return locker
126 try:
136 try:
127 pid = int(pid)
137 pid = int(pid)
128 except ValueError:
138 except ValueError:
129 return locker
139 return locker
130 if util.testpid(pid):
140 if util.testpid(pid):
131 return locker
141 return locker
132 # if locker dead, break lock. must do this with another lock
142 # if locker dead, break lock. must do this with another lock
133 # held, or can race and break valid lock.
143 # held, or can race and break valid lock.
134 try:
144 try:
135 l = lock(self.vfs, self.f + '.break', timeout=0)
145 l = lock(self.vfs, self.f + '.break', timeout=0)
136 self.vfs.unlink(self.f)
146 self.vfs.unlink(self.f)
137 l.release()
147 l.release()
138 except error.LockError:
148 except error.LockError:
139 return locker
149 return locker
140
150
141 def release(self):
151 def release(self):
142 """release the lock and execute callback function if any
152 """release the lock and execute callback function if any
143
153
144 If the lock has been acquired multiple times, the actual release is
154 If the lock has been acquired multiple times, the actual release is
145 delayed to the last release call."""
155 delayed to the last release call."""
146 if self.held > 1:
156 if self.held > 1:
147 self.held -= 1
157 self.held -= 1
148 elif self.held == 1:
158 elif self.held == 1:
149 self.held = 0
159 self.held = 0
150 if os.getpid() != self.pid:
160 if os.getpid() != self.pid:
151 # we forked, and are not the parent
161 # we forked, and are not the parent
152 return
162 return
153 try:
163 try:
154 if self.releasefn:
164 if self.releasefn:
155 self.releasefn()
165 self.releasefn()
156 finally:
166 finally:
157 try:
167 try:
158 self.vfs.unlink(self.f)
168 self.vfs.unlink(self.f)
159 except OSError:
169 except OSError:
160 pass
170 pass
161 for callback in self.postrelease:
171 for callback in self.postrelease:
162 callback()
172 callback()
163
173
164 def release(*locks):
174 def release(*locks):
165 for lock in locks:
175 for lock in locks:
166 if lock is not None:
176 if lock is not None:
167 lock.release()
177 lock.release()
General Comments 0
You need to be logged in to leave comments. Login now