commit:     87c079175c7a504ae893ed7d6ced03638d4cc853
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Fri Apr 20 05:39:31 2018 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Fri Apr 20 15:50:42 2018 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=87c07917

AbstractEbuildProcess: use async_unlock (bug 614108)

Override the _async_wait method to asynchronously unlock
self._build_dir when necessary, override the _wait method in order
to function as a failsafe if _async_wait has not been called for
some reason, and fix the SubProcess superclass to call _async_wait
when appropriate.

Execution of the _wait method's failsafe code will automatically
become a fatal error at the same time as event loop recursion is
disabled.

Bug: https://bugs.gentoo.org/614108

 pym/_emerge/AbstractEbuildProcess.py | 65 +++++++++++++++++++++++++++++++++---
 pym/_emerge/SubProcess.py            |  2 +-
 2 files changed, 61 insertions(+), 6 deletions(-)

diff --git a/pym/_emerge/AbstractEbuildProcess.py 
b/pym/_emerge/AbstractEbuildProcess.py
index 8bd30a640..2aa0c4a35 100644
--- a/pym/_emerge/AbstractEbuildProcess.py
+++ b/pym/_emerge/AbstractEbuildProcess.py
@@ -2,6 +2,7 @@
 # Distributed under the terms of the GNU General Public License v2
 
 import errno
+import functools
 import io
 import platform
 import stat
@@ -23,7 +24,8 @@ from portage.util import apply_secpass_permissions
 class AbstractEbuildProcess(SpawnProcess):
 
        __slots__ = ('phase', 'settings',) + \
-               ('_build_dir', '_ipc_daemon', '_exit_command', 
'_exit_timeout_id')
+               ('_build_dir', '_build_dir_unlock', '_ipc_daemon',
+               '_exit_command', '_exit_timeout_id')
 
        _phases_without_builddir = ('clean', 'cleanrm', 'depend', 'help',)
        _phases_interactive_whitelist = ('config',)
@@ -247,7 +249,7 @@ class AbstractEbuildProcess(SpawnProcess):
 
        def _cancel_timeout_cb(self):
                self._exit_timeout_id = None
-               self.wait()
+               self._async_wait()
                return False # only run once
 
        def _orphan_process_warn(self):
@@ -354,9 +356,7 @@ class AbstractEbuildProcess(SpawnProcess):
                                        self.returncode = 1
                                        if not self.cancelled:
                                                self._unexpected_exit()
-                       if self._build_dir is not None:
-                               self._build_dir.unlock()
-                               self._build_dir = None
+
                elif not self.cancelled:
                        exit_file = 
self.settings.get('PORTAGE_EBUILD_EXIT_FILE')
                        if exit_file and not os.path.exists(exit_file):
@@ -367,3 +367,58 @@ class AbstractEbuildProcess(SpawnProcess):
                                        self.returncode = 1
                                        if not self.cancelled:
                                                self._unexpected_exit()
+
+       def _wait(self):
+               """
+               Override _wait to unlock self._build_dir if necessary. 
Normally, it
+               should already be unlocked, so this functions only as a 
failsafe.
+               Execution of the failsafe code will automatically become a fatal
+               error at the same time as event loop recursion is disabled.
+               """
+               SpawnProcess._wait(self)
+
+               if self._build_dir is not None:
+                       self._build_dir_unlock = self._build_dir.async_unlock()
+                       # Unlock only once.
+                       self._build_dir = None
+
+               if not (self._build_dir_unlock is None or
+                       self._build_dir_unlock.done()):
+                       # This will automatically become a fatal error at the 
same
+                       # time as event loop recursion is disabled.
+                       
self.scheduler.run_until_complete(self._build_dir_unlock)
+
+               return self.returncode
+
+       def _async_wait(self):
+               """
+               Override _async_wait to asynchronously unlock self._build_dir
+               when necessary.
+               """
+               if self._build_dir is None:
+                       SpawnProcess._async_wait(self)
+               elif self._build_dir_unlock is None:
+                       self._async_unlock_builddir(returncode=self.returncode)
+
+       def _async_unlock_builddir(self, returncode=None):
+               """
+               Release the lock asynchronously, and if a returncode parameter
+               is given then set self.returncode and notify exit listeners.
+               """
+               if self._build_dir_unlock is not None:
+                       raise AssertionError('unlock already in progress')
+               if returncode is not None:
+                       # The returncode will be set after unlock is complete.
+                       self.returncode = None
+               self._build_dir_unlock = self._build_dir.async_unlock()
+               # Unlock only once.
+               self._build_dir = None
+               self._build_dir_unlock.add_done_callback(
+                       functools.partial(self._unlock_builddir_exit, 
returncode=returncode))
+
+       def _unlock_builddir_exit(self, unlock_future, returncode=None):
+               # Normally, async_unlock should not raise an exception here.
+               unlock_future.result()
+               if returncode is not None:
+                       self.returncode = returncode
+                       SpawnProcess._async_wait(self)

diff --git a/pym/_emerge/SubProcess.py b/pym/_emerge/SubProcess.py
index b81cfd5f6..141181d9f 100644
--- a/pym/_emerge/SubProcess.py
+++ b/pym/_emerge/SubProcess.py
@@ -116,7 +116,7 @@ class SubProcess(AbstractPollTask):
                if pid != self.pid:
                        raise AssertionError("expected pid %s, got %s" % 
(self.pid, pid))
                self._set_returncode((pid, condition))
-               self.wait()
+               self._async_wait()
 
        def _waitpid_loop(self):
                source_id = self.scheduler.child_watch_add(

Reply via email to