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