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