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