##// END OF EJS Templates
lock: make trylock private
Matt Mackall -
r26082:b188f60b default
parent child Browse files
Show More
@@ -1,167 +1,167 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 testlock(self):
103 def testlock(self):
104 """return id of locker if lock is valid, else None.
104 """return id of locker if lock is valid, else None.
105
105
106 If old-style lock, we cannot tell what machine locker is on.
106 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
107 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
108 see if locker is alive. If locker is on this machine but
109 not alive, we can safely break lock.
109 not alive, we can safely break lock.
110
110
111 The lock file is only deleted when None is returned.
111 The lock file is only deleted when None is returned.
112
112
113 """
113 """
114 try:
114 try:
115 locker = self.vfs.readlock(self.f)
115 locker = self.vfs.readlock(self.f)
116 except (OSError, IOError) as why:
116 except (OSError, IOError) as why:
117 if why.errno == errno.ENOENT:
117 if why.errno == errno.ENOENT:
118 return None
118 return None
119 raise
119 raise
120 try:
120 try:
121 host, pid = locker.split(":", 1)
121 host, pid = locker.split(":", 1)
122 except ValueError:
122 except ValueError:
123 return locker
123 return locker
124 if host != lock._host:
124 if host != lock._host:
125 return locker
125 return locker
126 try:
126 try:
127 pid = int(pid)
127 pid = int(pid)
128 except ValueError:
128 except ValueError:
129 return locker
129 return locker
130 if util.testpid(pid):
130 if util.testpid(pid):
131 return locker
131 return locker
132 # if locker dead, break lock. must do this with another lock
132 # if locker dead, break lock. must do this with another lock
133 # held, or can race and break valid lock.
133 # held, or can race and break valid lock.
134 try:
134 try:
135 l = lock(self.vfs, self.f + '.break', timeout=0)
135 l = lock(self.vfs, self.f + '.break', timeout=0)
136 self.vfs.unlink(self.f)
136 self.vfs.unlink(self.f)
137 l.release()
137 l.release()
138 except error.LockError:
138 except error.LockError:
139 return locker
139 return locker
140
140
141 def release(self):
141 def release(self):
142 """release the lock and execute callback function if any
142 """release the lock and execute callback function if any
143
143
144 If the lock has been acquired multiple times, the actual release is
144 If the lock has been acquired multiple times, the actual release is
145 delayed to the last release call."""
145 delayed to the last release call."""
146 if self.held > 1:
146 if self.held > 1:
147 self.held -= 1
147 self.held -= 1
148 elif self.held == 1:
148 elif self.held == 1:
149 self.held = 0
149 self.held = 0
150 if os.getpid() != self.pid:
150 if os.getpid() != self.pid:
151 # we forked, and are not the parent
151 # we forked, and are not the parent
152 return
152 return
153 try:
153 try:
154 if self.releasefn:
154 if self.releasefn:
155 self.releasefn()
155 self.releasefn()
156 finally:
156 finally:
157 try:
157 try:
158 self.vfs.unlink(self.f)
158 self.vfs.unlink(self.f)
159 except OSError:
159 except OSError:
160 pass
160 pass
161 for callback in self.postrelease:
161 for callback in self.postrelease:
162 callback()
162 callback()
163
163
164 def release(*locks):
164 def release(*locks):
165 for lock in locks:
165 for lock in locks:
166 if lock is not None:
166 if lock is not None:
167 lock.release()
167 lock.release()
General Comments 0
You need to be logged in to leave comments. Login now