##// END OF EJS Templates
lock: turn prepinherit/reacquire into a single context manager...
Siddharth Agarwal -
r26473:5f94e64f default
parent child Browse files
Show More
@@ -1,231 +1,230 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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import contextlib
10 import errno
11 import errno
11 import os
12 import os
12 import socket
13 import socket
13 import time
14 import time
14 import warnings
15 import warnings
15
16
16 from . import (
17 from . import (
17 error,
18 error,
18 util,
19 util,
19 )
20 )
20
21
21 class lock(object):
22 class lock(object):
22 '''An advisory lock held by one process to control access to a set
23 '''An advisory lock held by one process to control access to a set
23 of files. Non-cooperating processes or incorrectly written scripts
24 of files. Non-cooperating processes or incorrectly written scripts
24 can ignore Mercurial's locking scheme and stomp all over the
25 can ignore Mercurial's locking scheme and stomp all over the
25 repository, so don't do that.
26 repository, so don't do that.
26
27
27 Typically used via localrepository.lock() to lock the repository
28 Typically used via localrepository.lock() to lock the repository
28 store (.hg/store/) or localrepository.wlock() to lock everything
29 store (.hg/store/) or localrepository.wlock() to lock everything
29 else under .hg/.'''
30 else under .hg/.'''
30
31
31 # lock is symlink on platforms that support it, file on others.
32 # lock is symlink on platforms that support it, file on others.
32
33
33 # symlink is used because create of directory entry and contents
34 # symlink is used because create of directory entry and contents
34 # are atomic even over nfs.
35 # are atomic even over nfs.
35
36
36 # old-style lock: symlink to pid
37 # old-style lock: symlink to pid
37 # new-style lock: symlink to hostname:pid
38 # new-style lock: symlink to hostname:pid
38
39
39 _host = None
40 _host = None
40
41
41 def __init__(self, vfs, file, timeout=-1, releasefn=None, acquirefn=None,
42 def __init__(self, vfs, file, timeout=-1, releasefn=None, acquirefn=None,
42 desc=None, parentlock=None):
43 desc=None, parentlock=None):
43 self.vfs = vfs
44 self.vfs = vfs
44 self.f = file
45 self.f = file
45 self.held = 0
46 self.held = 0
46 self.timeout = timeout
47 self.timeout = timeout
47 self.releasefn = releasefn
48 self.releasefn = releasefn
48 self.acquirefn = acquirefn
49 self.acquirefn = acquirefn
49 self.desc = desc
50 self.desc = desc
50 self.parentlock = parentlock
51 self.parentlock = parentlock
51 self._parentheld = False
52 self._parentheld = False
52 self._inherited = False
53 self._inherited = False
53 self.postrelease = []
54 self.postrelease = []
54 self.pid = self._getpid()
55 self.pid = self._getpid()
55 self.delay = self.lock()
56 self.delay = self.lock()
56 if self.acquirefn:
57 if self.acquirefn:
57 self.acquirefn()
58 self.acquirefn()
58
59
59 def __del__(self):
60 def __del__(self):
60 if self.held:
61 if self.held:
61 warnings.warn("use lock.release instead of del lock",
62 warnings.warn("use lock.release instead of del lock",
62 category=DeprecationWarning,
63 category=DeprecationWarning,
63 stacklevel=2)
64 stacklevel=2)
64
65
65 # ensure the lock will be removed
66 # ensure the lock will be removed
66 # even if recursive locking did occur
67 # even if recursive locking did occur
67 self.held = 1
68 self.held = 1
68
69
69 self.release()
70 self.release()
70
71
71 def _getpid(self):
72 def _getpid(self):
72 # wrapper around os.getpid() to make testing easier
73 # wrapper around os.getpid() to make testing easier
73 return os.getpid()
74 return os.getpid()
74
75
75 def lock(self):
76 def lock(self):
76 timeout = self.timeout
77 timeout = self.timeout
77 while True:
78 while True:
78 try:
79 try:
79 self._trylock()
80 self._trylock()
80 return self.timeout - timeout
81 return self.timeout - timeout
81 except error.LockHeld as inst:
82 except error.LockHeld as inst:
82 if timeout != 0:
83 if timeout != 0:
83 time.sleep(1)
84 time.sleep(1)
84 if timeout > 0:
85 if timeout > 0:
85 timeout -= 1
86 timeout -= 1
86 continue
87 continue
87 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
88 raise error.LockHeld(errno.ETIMEDOUT, inst.filename, self.desc,
88 inst.locker)
89 inst.locker)
89
90
90 def _trylock(self):
91 def _trylock(self):
91 if self.held:
92 if self.held:
92 self.held += 1
93 self.held += 1
93 return
94 return
94 if lock._host is None:
95 if lock._host is None:
95 lock._host = socket.gethostname()
96 lock._host = socket.gethostname()
96 lockname = '%s:%s' % (lock._host, self.pid)
97 lockname = '%s:%s' % (lock._host, self.pid)
97 retry = 5
98 retry = 5
98 while not self.held and retry:
99 while not self.held and retry:
99 retry -= 1
100 retry -= 1
100 try:
101 try:
101 self.vfs.makelock(lockname, self.f)
102 self.vfs.makelock(lockname, self.f)
102 self.held = 1
103 self.held = 1
103 except (OSError, IOError) as why:
104 except (OSError, IOError) as why:
104 if why.errno == errno.EEXIST:
105 if why.errno == errno.EEXIST:
105 locker = self._readlock()
106 locker = self._readlock()
106 # special case where a parent process holds the lock -- this
107 # special case where a parent process holds the lock -- this
107 # is different from the pid being different because we do
108 # is different from the pid being different because we do
108 # want the unlock and postrelease functions to be called,
109 # want the unlock and postrelease functions to be called,
109 # but the lockfile to not be removed.
110 # but the lockfile to not be removed.
110 if locker == self.parentlock:
111 if locker == self.parentlock:
111 self._parentheld = True
112 self._parentheld = True
112 self.held = 1
113 self.held = 1
113 return
114 return
114 locker = self._testlock(locker)
115 locker = self._testlock(locker)
115 if locker is not None:
116 if locker is not None:
116 raise error.LockHeld(errno.EAGAIN,
117 raise error.LockHeld(errno.EAGAIN,
117 self.vfs.join(self.f), self.desc,
118 self.vfs.join(self.f), self.desc,
118 locker)
119 locker)
119 else:
120 else:
120 raise error.LockUnavailable(why.errno, why.strerror,
121 raise error.LockUnavailable(why.errno, why.strerror,
121 why.filename, self.desc)
122 why.filename, self.desc)
122
123
123 def _readlock(self):
124 def _readlock(self):
124 """read lock and return its value
125 """read lock and return its value
125
126
126 Returns None if no lock exists, pid for old-style locks, and host:pid
127 Returns None if no lock exists, pid for old-style locks, and host:pid
127 for new-style locks.
128 for new-style locks.
128 """
129 """
129 try:
130 try:
130 return self.vfs.readlock(self.f)
131 return self.vfs.readlock(self.f)
131 except (OSError, IOError) as why:
132 except (OSError, IOError) as why:
132 if why.errno == errno.ENOENT:
133 if why.errno == errno.ENOENT:
133 return None
134 return None
134 raise
135 raise
135
136
136 def _testlock(self, locker):
137 def _testlock(self, locker):
137 if locker is None:
138 if locker is None:
138 return None
139 return None
139 try:
140 try:
140 host, pid = locker.split(":", 1)
141 host, pid = locker.split(":", 1)
141 except ValueError:
142 except ValueError:
142 return locker
143 return locker
143 if host != lock._host:
144 if host != lock._host:
144 return locker
145 return locker
145 try:
146 try:
146 pid = int(pid)
147 pid = int(pid)
147 except ValueError:
148 except ValueError:
148 return locker
149 return locker
149 if util.testpid(pid):
150 if util.testpid(pid):
150 return locker
151 return locker
151 # if locker dead, break lock. must do this with another lock
152 # if locker dead, break lock. must do this with another lock
152 # held, or can race and break valid lock.
153 # held, or can race and break valid lock.
153 try:
154 try:
154 l = lock(self.vfs, self.f + '.break', timeout=0)
155 l = lock(self.vfs, self.f + '.break', timeout=0)
155 self.vfs.unlink(self.f)
156 self.vfs.unlink(self.f)
156 l.release()
157 l.release()
157 except error.LockError:
158 except error.LockError:
158 return locker
159 return locker
159
160
160 def testlock(self):
161 def testlock(self):
161 """return id of locker if lock is valid, else None.
162 """return id of locker if lock is valid, else None.
162
163
163 If old-style lock, we cannot tell what machine locker is on.
164 If old-style lock, we cannot tell what machine locker is on.
164 with new-style lock, if locker is on this machine, we can
165 with new-style lock, if locker is on this machine, we can
165 see if locker is alive. If locker is on this machine but
166 see if locker is alive. If locker is on this machine but
166 not alive, we can safely break lock.
167 not alive, we can safely break lock.
167
168
168 The lock file is only deleted when None is returned.
169 The lock file is only deleted when None is returned.
169
170
170 """
171 """
171 locker = self._readlock()
172 locker = self._readlock()
172 return self._testlock(locker)
173 return self._testlock(locker)
173
174
174 def prepinherit(self):
175 @contextlib.contextmanager
175 """prepare for the lock to be inherited by a Mercurial subprocess
176 def inherit(self):
177 """context for the lock to be inherited by a Mercurial subprocess.
176
178
177 Returns a string that will be recognized by the lock in the
179 Yields a string that will be recognized by the lock in the subprocess.
178 subprocess. Communicating this string to the subprocess needs to be done
180 Communicating this string to the subprocess needs to be done separately
179 separately -- typically by an environment variable.
181 -- typically by an environment variable.
180 """
182 """
181 if not self.held:
183 if not self.held:
182 raise error.LockInheritanceContractViolation(
184 raise error.LockInheritanceContractViolation(
183 'prepinherit can only be called while lock is held')
185 'inherit can only be called while lock is held')
184 if self._inherited:
186 if self._inherited:
185 raise error.LockInheritanceContractViolation(
187 raise error.LockInheritanceContractViolation(
186 'prepinherit cannot be called while lock is already inherited')
188 'inherit cannot be called while lock is already inherited')
187 if self.releasefn:
189 if self.releasefn:
188 self.releasefn()
190 self.releasefn()
189 if self._parentheld:
191 if self._parentheld:
190 lockname = self.parentlock
192 lockname = self.parentlock
191 else:
193 else:
192 lockname = '%s:%s' % (lock._host, self.pid)
194 lockname = '%s:%s' % (lock._host, self.pid)
193 self._inherited = True
195 self._inherited = True
194 return lockname
196 try:
195
197 yield lockname
196 def reacquire(self):
198 finally:
197 if not self._inherited:
198 raise error.LockInheritanceContractViolation(
199 'reacquire can only be called after prepinherit')
200 if self.acquirefn:
199 if self.acquirefn:
201 self.acquirefn()
200 self.acquirefn()
202 self._inherited = False
201 self._inherited = False
203
202
204 def release(self):
203 def release(self):
205 """release the lock and execute callback function if any
204 """release the lock and execute callback function if any
206
205
207 If the lock has been acquired multiple times, the actual release is
206 If the lock has been acquired multiple times, the actual release is
208 delayed to the last release call."""
207 delayed to the last release call."""
209 if self.held > 1:
208 if self.held > 1:
210 self.held -= 1
209 self.held -= 1
211 elif self.held == 1:
210 elif self.held == 1:
212 self.held = 0
211 self.held = 0
213 if self._getpid() != self.pid:
212 if self._getpid() != self.pid:
214 # we forked, and are not the parent
213 # we forked, and are not the parent
215 return
214 return
216 try:
215 try:
217 if self.releasefn:
216 if self.releasefn:
218 self.releasefn()
217 self.releasefn()
219 finally:
218 finally:
220 if not self._parentheld:
219 if not self._parentheld:
221 try:
220 try:
222 self.vfs.unlink(self.f)
221 self.vfs.unlink(self.f)
223 except OSError:
222 except OSError:
224 pass
223 pass
225 for callback in self.postrelease:
224 for callback in self.postrelease:
226 callback()
225 callback()
227
226
228 def release(*locks):
227 def release(*locks):
229 for lock in locks:
228 for lock in locks:
230 if lock is not None:
229 if lock is not None:
231 lock.release()
230 lock.release()
@@ -1,256 +1,254 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import copy
3 import copy
4 import os
4 import os
5 import silenttestrunner
5 import silenttestrunner
6 import tempfile
6 import tempfile
7 import types
7 import types
8 import unittest
8 import unittest
9
9
10 from mercurial import (
10 from mercurial import (
11 lock,
11 lock,
12 scmutil,
12 scmutil,
13 )
13 )
14
14
15 testlockname = 'testlock'
15 testlockname = 'testlock'
16
16
17 # work around http://bugs.python.org/issue1515
17 # work around http://bugs.python.org/issue1515
18 if types.MethodType not in copy._deepcopy_dispatch:
18 if types.MethodType not in copy._deepcopy_dispatch:
19 def _deepcopy_method(x, memo):
19 def _deepcopy_method(x, memo):
20 return type(x)(x.im_func, copy.deepcopy(x.im_self, memo), x.im_class)
20 return type(x)(x.im_func, copy.deepcopy(x.im_self, memo), x.im_class)
21 copy._deepcopy_dispatch[types.MethodType] = _deepcopy_method
21 copy._deepcopy_dispatch[types.MethodType] = _deepcopy_method
22
22
23 class lockwrapper(lock.lock):
23 class lockwrapper(lock.lock):
24 def __init__(self, pidoffset, *args, **kwargs):
24 def __init__(self, pidoffset, *args, **kwargs):
25 # lock.lock.__init__() calls lock(), so the pidoffset assignment needs
25 # lock.lock.__init__() calls lock(), so the pidoffset assignment needs
26 # to be earlier
26 # to be earlier
27 self._pidoffset = pidoffset
27 self._pidoffset = pidoffset
28 super(lockwrapper, self).__init__(*args, **kwargs)
28 super(lockwrapper, self).__init__(*args, **kwargs)
29 def _getpid(self):
29 def _getpid(self):
30 return os.getpid() + self._pidoffset
30 return os.getpid() + self._pidoffset
31
31
32 class teststate(object):
32 class teststate(object):
33 def __init__(self, testcase, dir, pidoffset=0):
33 def __init__(self, testcase, dir, pidoffset=0):
34 self._testcase = testcase
34 self._testcase = testcase
35 self._acquirecalled = False
35 self._acquirecalled = False
36 self._releasecalled = False
36 self._releasecalled = False
37 self._postreleasecalled = False
37 self._postreleasecalled = False
38 self.vfs = scmutil.vfs(dir, audit=False)
38 self.vfs = scmutil.vfs(dir, audit=False)
39 self._pidoffset = pidoffset
39 self._pidoffset = pidoffset
40
40
41 def makelock(self, *args, **kwargs):
41 def makelock(self, *args, **kwargs):
42 l = lockwrapper(self._pidoffset, self.vfs, testlockname,
42 l = lockwrapper(self._pidoffset, self.vfs, testlockname,
43 releasefn=self.releasefn, acquirefn=self.acquirefn,
43 releasefn=self.releasefn, acquirefn=self.acquirefn,
44 *args, **kwargs)
44 *args, **kwargs)
45 l.postrelease.append(self.postreleasefn)
45 l.postrelease.append(self.postreleasefn)
46 return l
46 return l
47
47
48 def acquirefn(self):
48 def acquirefn(self):
49 self._acquirecalled = True
49 self._acquirecalled = True
50
50
51 def releasefn(self):
51 def releasefn(self):
52 self._releasecalled = True
52 self._releasecalled = True
53
53
54 def postreleasefn(self):
54 def postreleasefn(self):
55 self._postreleasecalled = True
55 self._postreleasecalled = True
56
56
57 def assertacquirecalled(self, called):
57 def assertacquirecalled(self, called):
58 self._testcase.assertEqual(
58 self._testcase.assertEqual(
59 self._acquirecalled, called,
59 self._acquirecalled, called,
60 'expected acquire to be %s but was actually %s' % (
60 'expected acquire to be %s but was actually %s' % (
61 self._tocalled(called),
61 self._tocalled(called),
62 self._tocalled(self._acquirecalled),
62 self._tocalled(self._acquirecalled),
63 ))
63 ))
64
64
65 def resetacquirefn(self):
65 def resetacquirefn(self):
66 self._acquirecalled = False
66 self._acquirecalled = False
67
67
68 def assertreleasecalled(self, called):
68 def assertreleasecalled(self, called):
69 self._testcase.assertEqual(
69 self._testcase.assertEqual(
70 self._releasecalled, called,
70 self._releasecalled, called,
71 'expected release to be %s but was actually %s' % (
71 'expected release to be %s but was actually %s' % (
72 self._tocalled(called),
72 self._tocalled(called),
73 self._tocalled(self._releasecalled),
73 self._tocalled(self._releasecalled),
74 ))
74 ))
75
75
76 def assertpostreleasecalled(self, called):
76 def assertpostreleasecalled(self, called):
77 self._testcase.assertEqual(
77 self._testcase.assertEqual(
78 self._postreleasecalled, called,
78 self._postreleasecalled, called,
79 'expected postrelease to be %s but was actually %s' % (
79 'expected postrelease to be %s but was actually %s' % (
80 self._tocalled(called),
80 self._tocalled(called),
81 self._tocalled(self._postreleasecalled),
81 self._tocalled(self._postreleasecalled),
82 ))
82 ))
83
83
84 def assertlockexists(self, exists):
84 def assertlockexists(self, exists):
85 actual = self.vfs.lexists(testlockname)
85 actual = self.vfs.lexists(testlockname)
86 self._testcase.assertEqual(
86 self._testcase.assertEqual(
87 actual, exists,
87 actual, exists,
88 'expected lock to %s but actually did %s' % (
88 'expected lock to %s but actually did %s' % (
89 self._toexists(exists),
89 self._toexists(exists),
90 self._toexists(actual),
90 self._toexists(actual),
91 ))
91 ))
92
92
93 def _tocalled(self, called):
93 def _tocalled(self, called):
94 if called:
94 if called:
95 return 'called'
95 return 'called'
96 else:
96 else:
97 return 'not called'
97 return 'not called'
98
98
99 def _toexists(self, exists):
99 def _toexists(self, exists):
100 if exists:
100 if exists:
101 return 'exist'
101 return 'exist'
102 else:
102 else:
103 return 'not exist'
103 return 'not exist'
104
104
105 class testlock(unittest.TestCase):
105 class testlock(unittest.TestCase):
106 def testlock(self):
106 def testlock(self):
107 state = teststate(self, tempfile.mkdtemp(dir=os.getcwd()))
107 state = teststate(self, tempfile.mkdtemp(dir=os.getcwd()))
108 lock = state.makelock()
108 lock = state.makelock()
109 state.assertacquirecalled(True)
109 state.assertacquirecalled(True)
110 lock.release()
110 lock.release()
111 state.assertreleasecalled(True)
111 state.assertreleasecalled(True)
112 state.assertpostreleasecalled(True)
112 state.assertpostreleasecalled(True)
113 state.assertlockexists(False)
113 state.assertlockexists(False)
114
114
115 def testrecursivelock(self):
115 def testrecursivelock(self):
116 state = teststate(self, tempfile.mkdtemp(dir=os.getcwd()))
116 state = teststate(self, tempfile.mkdtemp(dir=os.getcwd()))
117 lock = state.makelock()
117 lock = state.makelock()
118 state.assertacquirecalled(True)
118 state.assertacquirecalled(True)
119
119
120 state.resetacquirefn()
120 state.resetacquirefn()
121 lock.lock()
121 lock.lock()
122 # recursive lock should not call acquirefn again
122 # recursive lock should not call acquirefn again
123 state.assertacquirecalled(False)
123 state.assertacquirecalled(False)
124
124
125 lock.release() # brings lock refcount down from 2 to 1
125 lock.release() # brings lock refcount down from 2 to 1
126 state.assertreleasecalled(False)
126 state.assertreleasecalled(False)
127 state.assertpostreleasecalled(False)
127 state.assertpostreleasecalled(False)
128 state.assertlockexists(True)
128 state.assertlockexists(True)
129
129
130 lock.release() # releases the lock
130 lock.release() # releases the lock
131 state.assertreleasecalled(True)
131 state.assertreleasecalled(True)
132 state.assertpostreleasecalled(True)
132 state.assertpostreleasecalled(True)
133 state.assertlockexists(False)
133 state.assertlockexists(False)
134
134
135 def testlockfork(self):
135 def testlockfork(self):
136 state = teststate(self, tempfile.mkdtemp(dir=os.getcwd()))
136 state = teststate(self, tempfile.mkdtemp(dir=os.getcwd()))
137 lock = state.makelock()
137 lock = state.makelock()
138 state.assertacquirecalled(True)
138 state.assertacquirecalled(True)
139
139
140 # fake a fork
140 # fake a fork
141 forklock = copy.deepcopy(lock)
141 forklock = copy.deepcopy(lock)
142 forklock._pidoffset = 1
142 forklock._pidoffset = 1
143 forklock.release()
143 forklock.release()
144 state.assertreleasecalled(False)
144 state.assertreleasecalled(False)
145 state.assertpostreleasecalled(False)
145 state.assertpostreleasecalled(False)
146 state.assertlockexists(True)
146 state.assertlockexists(True)
147
147
148 # release the actual lock
148 # release the actual lock
149 lock.release()
149 lock.release()
150 state.assertreleasecalled(True)
150 state.assertreleasecalled(True)
151 state.assertpostreleasecalled(True)
151 state.assertpostreleasecalled(True)
152 state.assertlockexists(False)
152 state.assertlockexists(False)
153
153
154 def testinheritlock(self):
154 def testinheritlock(self):
155 d = tempfile.mkdtemp(dir=os.getcwd())
155 d = tempfile.mkdtemp(dir=os.getcwd())
156 parentstate = teststate(self, d)
156 parentstate = teststate(self, d)
157 parentlock = parentstate.makelock()
157 parentlock = parentstate.makelock()
158 parentstate.assertacquirecalled(True)
158 parentstate.assertacquirecalled(True)
159
159
160 # set up lock inheritance
160 # set up lock inheritance
161 lockname = parentlock.prepinherit()
161 with parentlock.inherit() as lockname:
162 parentstate.assertreleasecalled(True)
162 parentstate.assertreleasecalled(True)
163 parentstate.assertpostreleasecalled(False)
163 parentstate.assertpostreleasecalled(False)
164 parentstate.assertlockexists(True)
164 parentstate.assertlockexists(True)
165
165
166 childstate = teststate(self, d, pidoffset=1)
166 childstate = teststate(self, d, pidoffset=1)
167 childlock = childstate.makelock(parentlock=lockname)
167 childlock = childstate.makelock(parentlock=lockname)
168 childstate.assertacquirecalled(True)
168 childstate.assertacquirecalled(True)
169
169
170 # release the child lock -- the lock file should still exist on disk
171 childlock.release()
170 childlock.release()
172 childstate.assertreleasecalled(True)
171 childstate.assertreleasecalled(True)
173 childstate.assertpostreleasecalled(True)
172 childstate.assertpostreleasecalled(True)
174 childstate.assertlockexists(True)
173 childstate.assertlockexists(True)
175
174
176 parentstate.resetacquirefn()
175 parentstate.resetacquirefn()
177 parentlock.reacquire()
176
178 parentstate.assertacquirecalled(True)
177 parentstate.assertacquirecalled(True)
179
178
180 parentlock.release()
179 parentlock.release()
181 parentstate.assertreleasecalled(True)
180 parentstate.assertreleasecalled(True)
182 parentstate.assertpostreleasecalled(True)
181 parentstate.assertpostreleasecalled(True)
183 parentstate.assertlockexists(False)
182 parentstate.assertlockexists(False)
184
183
185 def testmultilock(self):
184 def testmultilock(self):
186 d = tempfile.mkdtemp(dir=os.getcwd())
185 d = tempfile.mkdtemp(dir=os.getcwd())
187 state0 = teststate(self, d)
186 state0 = teststate(self, d)
188 lock0 = state0.makelock()
187 lock0 = state0.makelock()
189 state0.assertacquirecalled(True)
188 state0.assertacquirecalled(True)
190
189
191 lock0name = lock0.prepinherit()
190 with lock0.inherit() as lock0name:
192 state0.assertreleasecalled(True)
191 state0.assertreleasecalled(True)
193 state0.assertpostreleasecalled(False)
192 state0.assertpostreleasecalled(False)
194 state0.assertlockexists(True)
193 state0.assertlockexists(True)
195
194
196 state1 = teststate(self, d, pidoffset=1)
195 state1 = teststate(self, d, pidoffset=1)
197 lock1 = state1.makelock(parentlock=lock0name)
196 lock1 = state1.makelock(parentlock=lock0name)
198 state1.assertacquirecalled(True)
197 state1.assertacquirecalled(True)
199
198
200 # from within lock1, acquire another lock
199 # from within lock1, acquire another lock
201 lock1name = lock1.prepinherit()
200 with lock1.inherit() as lock1name:
202 # since the file on disk is lock0's this should have the same name
201 # since the file on disk is lock0's this should have the same
202 # name
203 self.assertEqual(lock0name, lock1name)
203 self.assertEqual(lock0name, lock1name)
204
204
205 state2 = teststate(self, d, pidoffset=2)
205 state2 = teststate(self, d, pidoffset=2)
206 lock2 = state2.makelock(parentlock=lock1name)
206 lock2 = state2.makelock(parentlock=lock1name)
207 state2.assertacquirecalled(True)
207 state2.assertacquirecalled(True)
208
208
209 lock2.release()
209 lock2.release()
210 state2.assertreleasecalled(True)
210 state2.assertreleasecalled(True)
211 state2.assertpostreleasecalled(True)
211 state2.assertpostreleasecalled(True)
212 state2.assertlockexists(True)
212 state2.assertlockexists(True)
213
213
214 state1.resetacquirefn()
214 state1.resetacquirefn()
215 lock1.reacquire()
215
216 state1.assertacquirecalled(True)
216 state1.assertacquirecalled(True)
217
217
218 lock1.release()
218 lock1.release()
219 state1.assertreleasecalled(True)
219 state1.assertreleasecalled(True)
220 state1.assertpostreleasecalled(True)
220 state1.assertpostreleasecalled(True)
221 state1.assertlockexists(True)
221 state1.assertlockexists(True)
222
222
223 lock0.reacquire()
224 lock0.release()
223 lock0.release()
225
224
226 def testinheritlockfork(self):
225 def testinheritlockfork(self):
227 d = tempfile.mkdtemp(dir=os.getcwd())
226 d = tempfile.mkdtemp(dir=os.getcwd())
228 parentstate = teststate(self, d)
227 parentstate = teststate(self, d)
229 parentlock = parentstate.makelock()
228 parentlock = parentstate.makelock()
230 parentstate.assertacquirecalled(True)
229 parentstate.assertacquirecalled(True)
231
230
232 # set up lock inheritance
231 # set up lock inheritance
233 lockname = parentlock.prepinherit()
232 with parentlock.inherit() as lockname:
234 childstate = teststate(self, d, pidoffset=1)
233 childstate = teststate(self, d, pidoffset=1)
235 childlock = childstate.makelock(parentlock=lockname)
234 childlock = childstate.makelock(parentlock=lockname)
236 childstate.assertacquirecalled(True)
235 childstate.assertacquirecalled(True)
237
236
238 # fork the child lock
237 # fork the child lock
239 forkchildlock = copy.deepcopy(childlock)
238 forkchildlock = copy.deepcopy(childlock)
240 forkchildlock._pidoffset += 1
239 forkchildlock._pidoffset += 1
241 forkchildlock.release()
240 forkchildlock.release()
242 childstate.assertreleasecalled(False)
241 childstate.assertreleasecalled(False)
243 childstate.assertpostreleasecalled(False)
242 childstate.assertpostreleasecalled(False)
244 childstate.assertlockexists(True)
243 childstate.assertlockexists(True)
245
244
246 # release the child lock
245 # release the child lock
247 childlock.release()
246 childlock.release()
248 childstate.assertreleasecalled(True)
247 childstate.assertreleasecalled(True)
249 childstate.assertpostreleasecalled(True)
248 childstate.assertpostreleasecalled(True)
250 childstate.assertlockexists(True)
249 childstate.assertlockexists(True)
251
250
252 parentlock.reacquire()
253 parentlock.release()
251 parentlock.release()
254
252
255 if __name__ == '__main__':
253 if __name__ == '__main__':
256 silenttestrunner.main(__name__)
254 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now