Gentoo Archives: gentoo-commits

From: Zac Medico <zmedico@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: pym/portage/tests/locks/, pym/_emerge/
Date: Mon, 03 Apr 2017 20:09:07
Message-Id: 1491250079.916a0733c7201b7a8b22f5262bd5be8cbc8992a6.zmedico@gentoo
1 commit: 916a0733c7201b7a8b22f5262bd5be8cbc8992a6
2 Author: Zac Medico <zmedico <AT> gentoo <DOT> org>
3 AuthorDate: Sun Apr 2 20:50:09 2017 +0000
4 Commit: Zac Medico <zmedico <AT> gentoo <DOT> org>
5 CommitDate: Mon Apr 3 20:07:59 2017 +0000
6 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=916a0733
7
8 AsynchronousLock: add async_unlock method (bug 614108)
9
10 Add an async_unlock method, in order to avoid event loop
11 recursion which is incompatible with asyncio.
12
13 X-Gentoo-bug: 614108
14 X-Gentoo-bug-url: https://bugs.gentoo.org/show_bug.cgi?id=614108
15 Acked-by: Brian Dolbec <dolsen <AT> gentoo.org>
16
17 pym/_emerge/AsynchronousLock.py | 89 +++++++++++++++++++++--
18 pym/portage/tests/locks/test_asynchronous_lock.py | 15 +++-
19 2 files changed, 92 insertions(+), 12 deletions(-)
20
21 diff --git a/pym/_emerge/AsynchronousLock.py b/pym/_emerge/AsynchronousLock.py
22 index c0b9b26dc..6a32d2d40 100644
23 --- a/pym/_emerge/AsynchronousLock.py
24 +++ b/pym/_emerge/AsynchronousLock.py
25 @@ -35,7 +35,7 @@ class AsynchronousLock(AsynchronousTask):
26
27 __slots__ = ('path', 'scheduler',) + \
28 ('_imp', '_force_async', '_force_dummy', '_force_process', \
29 - '_force_thread')
30 + '_force_thread', '_unlock_future')
31
32 _use_process_by_default = True
33
34 @@ -84,6 +84,11 @@ class AsynchronousLock(AsynchronousTask):
35 return self.returncode
36
37 def unlock(self):
38 + """
39 + This method is deprecated in favor of async_unlock, since waiting
40 + for the child process to respond can trigger event loop recursion
41 + which is incompatible with asyncio.
42 + """
43 if self._imp is None:
44 raise AssertionError('not locked')
45 if isinstance(self._imp, (_LockProcess, _LockThread)):
46 @@ -92,6 +97,28 @@ class AsynchronousLock(AsynchronousTask):
47 unlockfile(self._imp)
48 self._imp = None
49
50 + def async_unlock(self):
51 + """
52 + Release the lock asynchronously. Release notification is available
53 + via the add_done_callback method of the returned Future instance.
54 +
55 + @returns: Future, result is None
56 + """
57 + if self._imp is None:
58 + raise AssertionError('not locked')
59 + if self._unlock_future is not None:
60 + raise AssertionError("already unlocked")
61 + if isinstance(self._imp, (_LockProcess, _LockThread)):
62 + unlock_future = self._imp.async_unlock()
63 + else:
64 + unlockfile(self._imp)
65 + unlock_future = self.scheduler.create_future()
66 + self.scheduler.call_soon(unlock_future.set_result, None)
67 + self._imp = None
68 + self._unlock_future = unlock_future
69 + return unlock_future
70 +
71 +
72 class _LockThread(AbstractPollTask):
73 """
74 This uses the portage.locks module to acquire a lock asynchronously,
75 @@ -105,7 +132,7 @@ class _LockThread(AbstractPollTask):
76 """
77
78 __slots__ = ('path',) + \
79 - ('_force_dummy', '_lock_obj', '_thread',)
80 + ('_force_dummy', '_lock_obj', '_thread', '_unlock_future')
81
82 def _start(self):
83 self._registered = True
84 @@ -132,13 +159,35 @@ class _LockThread(AbstractPollTask):
85 pass
86
87 def unlock(self):
88 + """
89 + This method is deprecated in favor of async_unlock, for compatibility
90 + with _LockProcess.
91 + """
92 + self._unlock()
93 + self._unlock_future.set_result(None)
94 +
95 + def _unlock(self):
96 if self._lock_obj is None:
97 raise AssertionError('not locked')
98 if self.returncode is None:
99 raise AssertionError('lock not acquired yet')
100 + if self._unlock_future is not None:
101 + raise AssertionError("already unlocked")
102 + self._unlock_future = self.scheduler.create_future()
103 unlockfile(self._lock_obj)
104 self._lock_obj = None
105
106 + def async_unlock(self):
107 + """
108 + Release the lock asynchronously. Release notification is available
109 + via the add_done_callback method of the returned Future instance.
110 +
111 + @returns: Future, result is None
112 + """
113 + self._unlock()
114 + self.scheduler.call_soon(self._unlock_future.set_result, None)
115 + return self._unlock_future
116 +
117 def _unregister(self):
118 self._registered = False
119
120 @@ -156,7 +205,8 @@ class _LockProcess(AbstractPollTask):
121 """
122
123 __slots__ = ('path',) + \
124 - ('_acquired', '_kill_test', '_proc', '_files', '_reg_id', '_unlocked')
125 + ('_acquired', '_kill_test', '_proc', '_files',
126 + '_reg_id','_unlock_future')
127
128 def _start(self):
129 in_pr, in_pw = os.pipe()
130 @@ -223,13 +273,16 @@ class _LockProcess(AbstractPollTask):
131 return
132
133 if not self.cancelled and \
134 - not self._unlocked:
135 + self._unlock_future is None:
136 # We don't want lost locks going unnoticed, so it's
137 # only safe to ignore if either the cancel() or
138 # unlock() methods have been previously called.
139 raise AssertionError("lock process failed with returncode %s" \
140 % (proc.returncode,))
141
142 + if self._unlock_future is not None:
143 + self._unlock_future.set_result(None)
144 +
145 def _cancel(self):
146 if self._proc is not None:
147 self._proc.cancel()
148 @@ -271,16 +324,36 @@ class _LockProcess(AbstractPollTask):
149 os.close(pipe_in)
150
151 def unlock(self):
152 + """
153 + This method is deprecated in favor of async_unlock, since waiting
154 + for the child process to respond can trigger event loop recursion
155 + which is incompatible with asyncio.
156 + """
157 + self._unlock()
158 + self._proc.wait()
159 + self._proc = None
160 +
161 + def _unlock(self):
162 if self._proc is None:
163 raise AssertionError('not locked')
164 - if self.returncode is None:
165 + if not self._acquired:
166 raise AssertionError('lock not acquired yet')
167 if self.returncode != os.EX_OK:
168 raise AssertionError("lock process failed with returncode %s" \
169 % (self.returncode,))
170 - self._unlocked = True
171 + if self._unlock_future is not None:
172 + raise AssertionError("already unlocked")
173 + self._unlock_future = self.scheduler.create_future()
174 os.write(self._files['pipe_out'], b'\0')
175 os.close(self._files['pipe_out'])
176 self._files = None
177 - self._proc.wait()
178 - self._proc = None
179 +
180 + def async_unlock(self):
181 + """
182 + Release the lock asynchronously. Release notification is available
183 + via the add_done_callback method of the returned Future instance.
184 +
185 + @returns: Future, result is None
186 + """
187 + self._unlock()
188 + return self._unlock_future
189
190 diff --git a/pym/portage/tests/locks/test_asynchronous_lock.py b/pym/portage/tests/locks/test_asynchronous_lock.py
191 index 3a2ccfb84..ab67242d5 100644
192 --- a/pym/portage/tests/locks/test_asynchronous_lock.py
193 +++ b/pym/portage/tests/locks/test_asynchronous_lock.py
194 @@ -1,6 +1,7 @@
195 # Copyright 2010-2011 Gentoo Foundation
196 # Distributed under the terms of the GNU General Public License v2
197
198 +import itertools
199 import signal
200 import tempfile
201
202 @@ -17,7 +18,8 @@ class AsynchronousLockTestCase(TestCase):
203 tempdir = tempfile.mkdtemp()
204 try:
205 path = os.path.join(tempdir, 'lock_me')
206 - for force_async in (True, False):
207 + for force_async, async_unlock in itertools.product(
208 + (True, False), repeat=2):
209 for force_dummy in (True, False):
210 async_lock = AsynchronousLock(path=path,
211 scheduler=scheduler, _force_async=force_async,
212 @@ -26,7 +28,10 @@ class AsynchronousLockTestCase(TestCase):
213 async_lock.start()
214 self.assertEqual(async_lock.wait(), os.EX_OK)
215 self.assertEqual(async_lock.returncode, os.EX_OK)
216 - async_lock.unlock()
217 + if async_unlock:
218 + scheduler.run_until_complete(async_lock.async_unlock())
219 + else:
220 + async_lock.unlock()
221
222 async_lock = AsynchronousLock(path=path,
223 scheduler=scheduler, _force_async=force_async,
224 @@ -34,8 +39,10 @@ class AsynchronousLockTestCase(TestCase):
225 async_lock.start()
226 self.assertEqual(async_lock.wait(), os.EX_OK)
227 self.assertEqual(async_lock.returncode, os.EX_OK)
228 - async_lock.unlock()
229 -
230 + if async_unlock:
231 + scheduler.run_until_complete(async_lock.async_unlock())
232 + else:
233 + async_lock.unlock()
234 finally:
235 shutil.rmtree(tempdir)