##// END OF EJS Templates
lock: the correct way to do a trylock() is to use a timeout of 0
Benoit Boissinot -
r9858:ea38a2c1 default
parent child Browse files
Show More
@@ -1,138 +1,137 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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 import util, error
8 import util, error
9 import errno, os, socket, time
9 import errno, os, socket, time
10 import warnings
10 import warnings
11
11
12 class lock(object):
12 class lock(object):
13 '''An advisory lock held by one process to control access to a set
13 '''An advisory lock held by one process to control access to a set
14 of files. Non-cooperating processes or incorrectly written scripts
14 of files. Non-cooperating processes or incorrectly written scripts
15 can ignore Mercurial's locking scheme and stomp all over the
15 can ignore Mercurial's locking scheme and stomp all over the
16 repository, so don't do that.
16 repository, so don't do that.
17
17
18 Typically used via localrepository.lock() to lock the repository
18 Typically used via localrepository.lock() to lock the repository
19 store (.hg/store/) or localrepository.wlock() to lock everything
19 store (.hg/store/) or localrepository.wlock() to lock everything
20 else under .hg/.'''
20 else under .hg/.'''
21
21
22 # lock is symlink on platforms that support it, file on others.
22 # lock is symlink on platforms that support it, file on others.
23
23
24 # symlink is used because create of directory entry and contents
24 # symlink is used because create of directory entry and contents
25 # are atomic even over nfs.
25 # are atomic even over nfs.
26
26
27 # old-style lock: symlink to pid
27 # old-style lock: symlink to pid
28 # new-style lock: symlink to hostname:pid
28 # new-style lock: symlink to hostname:pid
29
29
30 _host = None
30 _host = None
31
31
32 def __init__(self, file, timeout=-1, releasefn=None, desc=None):
32 def __init__(self, file, timeout=-1, releasefn=None, desc=None):
33 self.f = file
33 self.f = file
34 self.held = 0
34 self.held = 0
35 self.timeout = timeout
35 self.timeout = timeout
36 self.releasefn = releasefn
36 self.releasefn = releasefn
37 self.desc = desc
37 self.desc = desc
38 self.lock()
38 self.lock()
39
39
40 def __del__(self):
40 def __del__(self):
41 if self.held:
41 if self.held:
42 warnings.warn("use lock.release instead of del lock",
42 warnings.warn("use lock.release instead of del lock",
43 category=DeprecationWarning,
43 category=DeprecationWarning,
44 stacklevel=2)
44 stacklevel=2)
45
45
46 # ensure the lock will be removed
46 # ensure the lock will be removed
47 # even if recursive locking did occur
47 # even if recursive locking did occur
48 self.held = 1
48 self.held = 1
49
49
50 self.release()
50 self.release()
51
51
52 def lock(self):
52 def lock(self):
53 timeout = self.timeout
53 timeout = self.timeout
54 while 1:
54 while 1:
55 try:
55 try:
56 self.trylock()
56 self.trylock()
57 return 1
57 return 1
58 except error.LockHeld, inst:
58 except error.LockHeld, inst:
59 if timeout != 0:
59 if timeout != 0:
60 time.sleep(1)
60 time.sleep(1)
61 if timeout > 0:
61 if timeout > 0:
62 timeout -= 1
62 timeout -= 1
63 continue
63 continue
64 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
64 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
65 inst.locker)
65 inst.locker)
66
66
67 def trylock(self):
67 def trylock(self):
68 if self.held:
68 if self.held:
69 self.held += 1
69 self.held += 1
70 return
70 return
71 if lock._host is None:
71 if lock._host is None:
72 lock._host = socket.gethostname()
72 lock._host = socket.gethostname()
73 lockname = '%s:%s' % (lock._host, os.getpid())
73 lockname = '%s:%s' % (lock._host, os.getpid())
74 while not self.held:
74 while not self.held:
75 try:
75 try:
76 util.makelock(lockname, self.f)
76 util.makelock(lockname, self.f)
77 self.held = 1
77 self.held = 1
78 except (OSError, IOError), why:
78 except (OSError, IOError), why:
79 if why.errno == errno.EEXIST:
79 if why.errno == errno.EEXIST:
80 locker = self.testlock()
80 locker = self.testlock()
81 if locker is not None:
81 if locker is not None:
82 raise error.LockHeld(errno.EAGAIN, self.f, self.desc,
82 raise error.LockHeld(errno.EAGAIN, self.f, self.desc,
83 locker)
83 locker)
84 else:
84 else:
85 raise error.LockUnavailable(why.errno, why.strerror,
85 raise error.LockUnavailable(why.errno, why.strerror,
86 why.filename, self.desc)
86 why.filename, self.desc)
87
87
88 def testlock(self):
88 def testlock(self):
89 """return id of locker if lock is valid, else None.
89 """return id of locker if lock is valid, else None.
90
90
91 If old-style lock, we cannot tell what machine locker is on.
91 If old-style lock, we cannot tell what machine locker is on.
92 with new-style lock, if locker is on this machine, we can
92 with new-style lock, if locker is on this machine, we can
93 see if locker is alive. If locker is on this machine but
93 see if locker is alive. If locker is on this machine but
94 not alive, we can safely break lock.
94 not alive, we can safely break lock.
95
95
96 The lock file is only deleted when None is returned.
96 The lock file is only deleted when None is returned.
97
97
98 """
98 """
99 locker = util.readlock(self.f)
99 locker = util.readlock(self.f)
100 try:
100 try:
101 host, pid = locker.split(":", 1)
101 host, pid = locker.split(":", 1)
102 except ValueError:
102 except ValueError:
103 return locker
103 return locker
104 if host != lock._host:
104 if host != lock._host:
105 return locker
105 return locker
106 try:
106 try:
107 pid = int(pid)
107 pid = int(pid)
108 except ValueError:
108 except ValueError:
109 return locker
109 return locker
110 if util.testpid(pid):
110 if util.testpid(pid):
111 return locker
111 return locker
112 # if locker dead, break lock. must do this with another lock
112 # if locker dead, break lock. must do this with another lock
113 # held, or can race and break valid lock.
113 # held, or can race and break valid lock.
114 try:
114 try:
115 l = lock(self.f + '.break')
115 l = lock(self.f + '.break', timeout=0)
116 l.trylock()
117 os.unlink(self.f)
116 os.unlink(self.f)
118 l.release()
117 l.release()
119 except error.LockError:
118 except error.LockError:
120 return locker
119 return locker
121
120
122 def release(self):
121 def release(self):
123 if self.held > 1:
122 if self.held > 1:
124 self.held -= 1
123 self.held -= 1
125 elif self.held == 1:
124 elif self.held == 1:
126 self.held = 0
125 self.held = 0
127 if self.releasefn:
126 if self.releasefn:
128 self.releasefn()
127 self.releasefn()
129 try:
128 try:
130 os.unlink(self.f)
129 os.unlink(self.f)
131 except OSError:
130 except OSError:
132 pass
131 pass
133
132
134 def release(*locks):
133 def release(*locks):
135 for lock in locks:
134 for lock in locks:
136 if lock is not None:
135 if lock is not None:
137 lock.release()
136 lock.release()
138
137
General Comments 0
You need to be logged in to leave comments. Login now