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