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