Gentoo Archives: gentoo-portage-dev

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

Replies